MySQL通过BINLOG记录执行成功的INSERT,UPDATE,DELETE等DML语句。并由此实现数据库的恢复(point-in-time)和复制(其原理与恢复类似,通过复制和执行二进制日志使一台远程的MySQLl数据库,多称为slave,进行实时同步)。MySQL 5.5.x以后的版本支持3种日志格式。通过binlog_format参数设置。该参数影响了记录二进制日志的格式,十分重要。

1.STATEMENT格式和之前的MySQL版本一样,二进制日志文件记录的是日志的逻辑SQL语句。

2.ROW格式记录的不再是简单的SQL语句,而是记录表的每行记录更改的情况。

3.在MIXED格式下,MySQL默认采用STATEMENT格式进行二进制日志文件的记录。但是在一些特殊情况下会使用ROW格式,可能的情况如下:

(1)表的存储引擎为NDB,这时对表的DML操作都会以ROW格式记录。

(2)使用了UUID(),USER(),CURRENT_USER(),FOUND_ROWS(),ROW_COUNT()等不确定函数。

(3) 使用了INSERT DELAY语句。

(4)使用了用户自定义函数(UDF).

(5)使用了临时表(temporary table) 。

对于基于语句的日志格式(STATEMENT)的恢复和复制而言,由于MySQL的BINLONG是按照事务(transaction)提交(committed)的先后顺序记录的,因此要正确恢复或者复制数据,就必须满足:在一个事务未提交前,其他并发事务不能插入满足其锁定条件的任何记录,也就是不允许出现幻读(Phantom Problem)。这已经超过了ISO/ANSI SQL92"可重复读(Repeatable Read)"隔离级别的要求,实际上是要求事务要串行化。这也是许多情况下,InnoDB要用到Next-Key Lock锁的原因,比如用在范围条件更新记录时,无论是在Read Committed或者是Repeatable Read隔离级别下,InnoDB都要使用Next-key Lock锁。既然说到Next-key Lock锁机制,我这里简单说一下,演示各种效果就让童鞋们自己去测试了^_^

InnoDB锁的算法
innodb引擎有三种锁的算法设计:
Record lock:对单个索引项加锁
Gap lock:间隙锁,对索引项之间的"间隙",第一条记录前的"间隙"或最后一条记录后的"间隙"加锁,不包括索引项本身
Next-key lock:Gap lock+Next-key lock 锁定索引项范围。对记录及其前面的间隙加锁
 
注意:
对于唯一索引,其加上的是Record Lock,仅锁住记录本身。但也有特别情况,那就是唯一索引由多个列组成,而查询仅是查找多个唯一索引列中的其中一个,那么加锁的情况依然是Next-key lock。
 
对于辅助索引,其加上的是Next-Key Lock,锁定的是范围,包含记录本身。
另外如果使用相等的条件给一个不存在的记录加锁,innodb也会使用Next-key lock
 
特别注意:
innodb存储引擎是通过给索引上的索引项加锁来实现,这意味着:只有通过索引条件检索数据,innodb才会使用行锁,否则,innodb将使用表锁。(Repeatable Read隔离级别下)
如果是在表没有主键或者没有任何索引的情况下(并且是在read committed隔离级别)。如果一个表有主键,没有其他的索引,检索条件又不是主键,SQL会走聚簇索引的全扫描进行过滤,由于过滤是由MySQL Server层面进行的。因此每条记录,无论是否满足条件,都会被加上X锁。但是,为了效率考量,MySQL做了优化,对于不满足条件的记录,会在判断后放锁,最终持有的,是满足条件的记录上的锁,但是不满足条件的记录上的加锁/放锁动作不会省略。同时,优化也违背了2PL的约束。
 
对于"INSERT INTO target_tab SELECT * FROM source_tab WHERE...." 和"CREATE TABLE new_tab...SELECT....FROM source_tab WHERE...(CTAS)"这种SQL语句,用户并没有对source_tab做任何操作,但是MySQL会对这种SQL语句做特别的处理。我们来看一个实际的例子:
mysql> select * from source_tab;
+------+------+--------+
| id | age | name |
+------+------+--------+
| 1 | 24 | yayun |
| 2 | 24 | atlas |
| 3 | 25 | david |
| 4 | 24 | dengyy |
+------+------+--------+
4 rows in set (0.00 sec) mysql> select * from target_tab;
Empty set (0.00 sec) mysql> desc source_tab;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| age | int(11) | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec) mysql> desc target_tab;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| age | int(11) | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec) mysql>

CTAS操作给原表加锁的例子

session1操作

mysql> begin;
Query OK, 0 rows affected (0.00 sec) mysql> select * from source_tab;
+------+------+--------+
| id | age | name |
+------+------+--------+
| 1 | 24 | yayun |
| 2 | 24 | atlas |
| 3 | 25 | david |
| 4 | 24 | dengyy |
+------+------+--------+
4 rows in set (0.00 sec) mysql> insert into target_tab select * from source_tab where name='yayun'; #该语句执行以后,session2中的update操作将会等待
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0 mysql> commit;
Query OK, 0 rows affected (0.04 sec) mysql>

session2操作

mysql> begin;
Query OK, 0 rows affected (0.00 sec) mysql> select * from source_tab;
+------+------+--------+
| id | age | name |
+------+------+--------+
| 1 | 24 | yayun |
| 2 | 24 | atlas |
| 3 | 25 | david |
| 4 | 24 | dengyy |
+------+------+--------+
4 rows in set (0.00 sec) mysql> update source_tab set name='dengyayun' where name='yayun'; #一直等待,除非session1执行commit提交。
Query OK, 1 row affected (49.24 sec) #可以看见用了49秒,这就是在等待session1提交,当session1提交后,顺利更新
Rows matched: 1 Changed: 1 Warnings: 0 mysql> commit;
Query OK, 0 rows affected (0.00 sec) mysql>

在上面示例中,只是简单的读source_tab表的数据,相当于执行一个普通的SELECT语句,用一致性读就可以了。Oracle正是这么做的,它通过MVCC技术实现的多版本并发控制实现一致性读,不需要给source_tab加任何锁。大家都知道InnoDB也实现了多版本并发控制(MVCC),对普通的SELECT一致性读,也不需要加任何锁;但是这里InnoDB却给source_tab表加了共享锁,并没有使用多版本一致性读技术。

MySQL为什么这么做呢?why?其原因还是为了保证恢复和复制的正确性。因为在不加锁的情况下,如果上述语句执行过程中,其他事务对原表(source_tab)做了更新操作,就可能导致数据恢复结果错误。为了演示错误的发生,再重复上面的例子,先将系统变量innodb_locks_unsafe_for_binlog的值设为"on",默认值是off

innodb_locks_unsafe_for_binlog

设定InnoDB是否在搜索和索引扫描中使用间隙锁(gap locking)。InnoDB使用行级锁(row-level locking),通常情况下,InnoDB在搜索或扫描索引的行锁机制中使用“下一键锁定(next-key locking)”算法来锁定某索引记录及其前部的间隙(gap),以阻塞其它用户紧跟在该索引记录之前插入其它索引记录。站在这个角度来说,行级锁也叫索引记录锁(index-record lock)。
默认情况下,此变量的值为OFF,意为禁止使用非安全锁,也即启用间隙锁功能。将其设定为ON表示禁止锁定索引记录前的间隙,也即禁用间隙锁,InnoDB仅使用索引记录锁(index-record lock)进行索引搜索或扫描,不过,这并不禁止InnoDB在执行外键约束检查或重复键检查时使用间隙锁。
启用innodb_locks_unsafe_for_binlog的效果类似于将MySQL的事务隔离级别设定为READ-COMMITTED,但二者并不完全等同:innodb_locks_unsafe_for_binlog是全局级别的设定且只能在服务启动时设定,而事务隔离级别可全局设定并由会话级别继承,然而会话级别也以按需在运行时对其进行调整。类似READ-COMMITTED事务隔离级别,启用innodb_locks_unsafe_for_binlog也会带来“幻影问题(phantom problem)”,但除此之外,它还能带来如下特性:
(1)对UPDATE或DELETE语句来说,InnoDB仅锁定需要更新或删除的行,对不能够被WHERE条件匹配的行施加的锁会在条件检查后予以释放。这可以有效地降低死锁出现的概率;
(2)执行UPDATE语句时,如果某行已经被其它语句锁定,InnoDB会启动一个“半一致性(semi-consistent)”读操作从MySQL最近一次提交版本中获得此行,并以之判定其是否能够并当前UPDATE的WHERE条件所匹配。如果能够匹配,MySQL会再次对其进行锁定,而如果仍有其它锁存在,则需要先等待它们退出。

其无法动态修改,需要修改配置文件,演示如下:

CTAS操作不给原表加锁带来的安全问题

mysql> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | MIXED |
+---------------+-------+
1 row in set (0.00 sec) mysql> show variables like 'innodb_locks_unsafe%';
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_locks_unsafe_for_binlog | ON |
+--------------------------------+-------+
1 row in set (0.00 sec) mysql>

session1操作

mysql> begin;
Query OK, 0 rows affected (0.00 sec) mysql> select * from source_tab where id=1;
+------+------+-----------+
| id | age | name |
+------+------+-----------+
| 1 | 24 | dengyayun |
+------+------+-----------+
1 row in set (0.00 sec) mysql> insert into target_tab select * from source_tab where id=1;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0 mysql> commit; #插入操作后提交
Query OK, 0 rows affected (0.01 sec) mysql> select * from source_tab where name='good yayun'; #此时查看数据,target_tab中可以插入source_tab更新前的结果,这复合应用逻辑
+------+------+------------+
| id | age | name |
+------+------+------------+
| 1 | 24 | good yayun |
+------+------+------------+
1 row in set (0.00 sec) mysql> select * from target_tab;
+------+------+-----------+
| id | age | name |
+------+------+-----------+
| 1 | 24 | dengyayun |
+------+------+-----------+
1 row in set (0.00 sec)

session2操作

mysql> begin;
Query OK, 0 rows affected (0.00 sec) mysql> select * from source_tab where id=1;
+------+------+-----------+
| id | age | name |
+------+------+-----------+
| 1 | 24 | dengyayun |
+------+------+-----------+
1 row in set (0.00 sec) mysql> update source_tab set name='good yayun' where id=1; # session1未提交,可以对session1中的select记录进行更新操作
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0 mysql> commit; # 更新操作先提交
Query OK, 0 rows affected (0.02 sec) mysql> select * from source_tab where name='good yayun';
+------+------+------------+
| id | age | name |
+------+------+------------+
| 1 | 24 | good yayun |
+------+------+------------+
1 row in set (0.00 sec) mysql> select * from target_tab;
+------+------+-----------+
| id | age | name |
+------+------+-----------+
| 1 | 24 | dengyayun |
+------+------+-----------+
1 row in set (0.00 sec) mysql>

从上面的测试结果可以发现,设置系统变量innodb_locks_unsafe_for_binlog的值为"ON"后,innodb不再对原表(source_tab)加锁,结果也符合应用的逻辑,但是如果我们分析一下BINLOG内容,就可以发现问题所在

[root@MySQL- mysql]# mysqlbinlog mysql-bin. | grep -A  'update source_tab'
update source_tab set name='good yayun' where id=1

/*!*/;
# at
# :: server id end_log_pos Xid =
COMMIT/*!*/;
# at
# :: server id end_log_pos Query thread_id= exec_time= error_code=
SET TIMESTAMP=/*!*/;
BEGIN
/*!*/;
# at
# :: server id end_log_pos Query thread_id= exec_time= error_code=
SET TIMESTAMP=/*!*/;
insert into target_tab select * from source_tab where id=1
/*!*/;
# at
# :: server id end_log_pos Xid =
COMMIT/*!*/;
DELIMITER ;
# End of log file
ROLLBACK /* added by mysqlbinlog */;
[root@MySQL- mysql]#

可以清楚的看到在BINLOG的记录中,更新操作的位置在INSERT......SELECT之前,如果使用这个BINLOG进行数据库恢复,恢复的结果则与实际的应用逻辑不符;如果进行复制,就会导致主从数据不一致!

通过上面的例子,相信童鞋们不难理解为什么MySQL在处理

"INSERT INTO target_tab SELECT * FROM source_tab WHERE...."

"CREATE TABLE new_tab....SELECT.....FROM source_tab WHERE...."

时要给原表(source_tab)加锁,而不是使用对并发影响最小的多版本数据来实现一致性读。还要特别说明的是,如果上述语句的SELECT是范围条件,innodb还会给原表加上Next-Key Lock锁。

因此,INSERT....SELECT和CREATE TABLE....SELECT.....语句,可能会阻止对原表的并发更新。如果查询比较复杂,会照成严重的性能问题,生产环境需要谨慎使用。

总结如下:

如果应用中一定要用这种SQL来实现业务逻辑,又不希望对源表的并发更新产生影响,可以使用下面3种方法:

1.将innodb_locks_unsafe_for_binlog的值设置为"ON",强制MySQL使用多版本数据一致性读。但付出的代价是可能无法使用BINLOG正确的进行数据恢复或者主从复制。因此,此方法是不推荐使用的。

2.通过使用SELECT * FROM source_tab ..... INTO OUTFILE 和LOAD DATA INFILE.....语句组合来间接实现。采用这种放松MySQL不会给(源表)source_tab加锁。

3.使用基于行(ROW)的BINLOG格式和基于行的数据的复制。此方法是推荐使用的方法。

参考资料:

https://www.facebook.com/note.php?note_id=131719925932

http://dev.mysql.com/doc/refman/5.0/en/innodb-parameters.html#sysvar_innodb_locks_unsafe_for_binlog

MySQL数据恢复和复制对InnoDB锁机制的影响的更多相关文章

  1. MySQL- InnoDB锁机制

    InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION):二是采用了行级锁.行级锁与表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题.下面我们先介绍一点背景知识 ...

  2. mysql 开发进阶篇系列 11 锁问题 (恢复和复制的需要,对锁机制的影响)

    1. 恢复和复制的需要,对innodb锁机制的影响 mysql 通过binlog文件对增删除改等更新数据的sql语句,实现数据库的恢复和主从复制.mysql的恢复机制(复制其实就是在slave mys ...

  3. MySQL的innoDB锁机制以及死锁处理

    MySQL的nnoDB锁机制 InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION):二是采用了行级锁.行级锁与表级锁本来就有许多不同之处,innodb正常的select ...

  4. InnoDB锁机制分析

    InnoDB锁机制常常困扰大家,不同的条件下往往表现出不同的锁竞争,在实际工作中经常要分析各种锁超时.死锁的问题.本文通过不同条件下的实验,利用InnoDB系统给出的各种信息,分析了锁的工作机制.通过 ...

  5. [转载] 数据库分析手记 —— InnoDB锁机制分析

    作者:倪煜 InnoDB锁机制常常困扰大家,不同的条件下往往表现出不同的锁竞争,在实际工作中经常要分析各种锁超时.死锁的问题.本文通过不同条件下的实验,利用InnoDB系统给出的各种信息,分析了锁的工 ...

  6. innodb 锁机制

    InnoDB锁问题 InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION):二是采用了行级锁.行级锁与表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题.下面我 ...

  7. MySQL事务、并发问题、锁机制

    MySQL事务,并发问题,锁机制 1.什么是事务 事务是一条或多条数据库操作语句的组合,具备ACID,4个特点. 原子性:要不全部成功,要不全部撤销 隔离性:事务之间相互独立,互不干扰 一致性:数据库 ...

  8. MySQL事务及事务隔离级别 锁机制

    什么是事务? 当多个用户访问同一份数据时,一个用户在更改数据的过程中可能有其他用户同时发起更改请求,为保证数据库记录的更新从一个一致性状态更改为另一个一致性状态,这样的操作过程就是事务.事务具有的AC ...

  9. InnoDB锁机制-转载

    InnoDB锁机制 1. 锁类型 锁是数据库区别与文件系统的一个关键特性,锁机制用于管理对共享资源的并发访问. InnoDB使用的锁类型,分别有: 共享锁(S)和排他锁(X) 意向锁(IS和IX) 自 ...

随机推荐

  1. LoadRunner如何监控Tomcat性能

    使用LoadRunner做性能测试,一般的直觉是LR只能完成脚本录制和编写模拟用户的请求行为,但是在某些情况下,要监控一些中间件或web服务器的性能时,就不能通过录制脚本来完成了,那么就需要手工来编写 ...

  2. 那些Android中的性能优化

    性能优化是一个大的范畴,如果有人问你在Android中如何做性能优化的,也许都不知道从哪开始说起. 首先要明白的是,为什么我们的App需要优化,最显而易见的时刻:用户say,什么狗屎,刷这么久都没反应 ...

  3. DHCP欺骗(DHCP Sproofing)

    DHCP欺骗(DHCP Sproofing)   DHCP Sproofing同样是一种中间人攻击方式.DHCP是提供IP地址分配的服务.当局域网中的计算机设置为自动获取IP,就会在启动后发送广播包请 ...

  4. Delphi7 客户端调用WebService(天气预报)

    客户程序:第一步:新建一个Application. 第二步:File----->New----->Other------>WebServices----->WSDL Impor ...

  5. Graphics 导出图片使用【这个主要是画图类图的使用,记录一下】

    /// <summary> /// 导出信令流程矢量图 /// </summary> /// <param name="signalFlowInfos" ...

  6. Maven的简单使用,HelloWorld

    安装好Maven后就用一个简单的HelloWorld程序来测试一下,体验一下Maven.至于不懂的地方,请查看<Maven实战>书籍. 书籍网址:http://download.csdn. ...

  7. 使用Maven构建Android项目

    http://www.ikoding.com/build-android-project-with-maven/ 之前一直在做WEB前端项目,前段时间接手第一个Android项目,拿到代码之后,先试着 ...

  8. Codeforces Round #336 (Div. 2)

    水 A - Saitama Destroys Hotel 简单的模拟,小贪心.其实只要求max (ans, t + f); #include <bits/stdc++.h> using n ...

  9. BZOJ1001 [BeiJing2006]狼抓兔子(平面图最小割转最短路)

    ..和HDU3870类似..注意n=1和m=1的情况. #include<cstdio> #include<cstring> #include<queue> #in ...

  10. iOS学习21之UILabel, UITextField, UIButton, UIImageView

    1.UILabel 1> 概述 UILabel (标签): 是显示文本的控件.在App中 UILabel 是出现频率最高的控件 UILabel 是 UIView 子类,作为子类一般是为了扩充父类 ...