kider 发表于 2010-9-9 11:42:49

恢复和复制的需要,对InnoDB锁机制的影响

20.3.6 恢复和复制的需要,对InnoDB锁机制的影响MySQL通过BINLOG录执行成功的INSERT、UPDATE、DELETE等更新数据的SQL语句,并由此实现MySQL数据库的恢复和主从复制(可以参见本书“管理篇”的介绍)。MySQL的恢复机制(复制其实就是在Slave Mysql不断做基于BINLOG的恢复)有以下特点。·一是MySQL的恢复是SQL语句级的,也就是重新执行BINLOG中的SQL语句。这与Oracle数据库不同,Oracle是基于数据库文件块的。·二是MySQL的Binlog是按照事务提交的先后顺序记录的,恢复也是按这个顺序进行的。这点也与Oralce不同,Oracle是按照系统更新号(System Change Number,SCN)来恢复数据的,每个事务开始时,Oracle都会分配一个全局唯一的SCN,SCN的顺序与事务开始的时间顺序是一致的。从上面两点可知,MySQL的恢复机制要求:在一个事务未提交前,其他并发事务不能插入满足其锁定条件的任何记录,也就是不允许出现幻读,这已经超过了ISO/ANSI SQL92“可重复读”隔离级别的要求,实际上是要求事务要串行化。这也是许多情况下,InnoDB要用到间隙锁的原因,比如在用范围条件更新记录时,无论在Read Commited或是Repeatable Read隔离级别下,InnoDB都要使用间隙锁,但这并不是隔离级别要求的,有关InnoDB在不同隔离级别下加锁的差异在下一小节还会介绍。另外,对于“insertinto target_tab select * from source_tab where ...”和“createtable new_tab ...select ... Fromsource_tab where ...(CTAS)”这种SQL语句,用户并没有对source_tab做任何更新操作,但MySQL对这种SQL语句做了特别处理。先来看如表20-14的例子。表20-14    CTAS操作给原表加锁例子

session_1session_2
mysql> set autocommit = 0;Query OK, 0 rows affected (0.00 sec) mysql> select * from target_tab;Empty set (0.00 sec) mysql> select * from source_tab where name = '1';+----+------+----+| d1 | name | d2 |+----+------+----+|
4 | 1   
|
1 ||
5 | 1   
|
1 ||
6 | 1   
|
1 ||
7 | 1   
|
1 ||
8 | 1   
|
1 |+----+------+----+5 rows in set (0.00 sec)mysql> set autocommit = 0;Query OK, 0 rows affected (0.00 sec) mysql> select * from target_tab;Empty set (0.00 sec) mysql> select * from source_tab where name = '1';+----+------+----+| d1 | name | d2 |+----+------+----+|
4 | 1   
|
1 ||
5 | 1   
|
1 ||
6 | 1   
|
1 ||
7 | 1   
|
1 ||
8 | 1   
|
1 |+----+------+----+5 rows in set (0.00 sec)
mysql> insert into target_tab select d1,name from source_tab where name = '1';Query OK, 5 rows affected (0.00 sec)Records: 5
Duplicates: 0
Warnings: 0
mysql> update source_tab set name = '1' where name = '8';等待
commit;
返回结果commit;

在上面的例子中,只是简单地读source_tab表的数据,相当于执行一个普通的SELECT语句,用一致性读就可以了。ORACLE正是这么做的,它通过MVCC技术实现的多版本数据来实现一致性读,不需要给source_tab加任何锁。我们知道InnoDB也实现了多版本数据,对普通的SELECT一致性读,也不需要加任何锁;但这里InnoDB却给source_tab加了共享锁,并没有使用多版本数据一致性读技术!MySQL为什么要这么做呢?其原因还是为了保证恢复和复制的正确性。因为不加锁的话,如果在上述语句执行过程中,其他事务对source_tab做了更新操作,就可能导致数据恢复的结果错误。为了演示这一点,我们再重复一下前面的例子,不同的是在session_1执行事务前,先将系统变量innodb_locks_unsafe_for_binlog的值设置为“on”(其默认值为off),具体结果如表20-15所示。表20-15            CTAS操作不给原表加锁带来的安全问题例子

session_1 session_2
mysql> set autocommit = 0;Query OK, 0 rows affected (0.00 sec) mysql>set innodb_locks_unsafe_for_binlog='on'Query OK, 0 rows affected (0.00 sec)mysql> select * from target_tab;Empty set (0.00 sec) mysql> select * from source_tab where name = '1';+----+------+----+| d1 | name | d2 |+----+------+----+|
4 | 1   
|
1 ||
5 | 1   
|
1 ||
6 | 1   
|
1 ||
7 | 1

|
1 ||
8 | 1   
|
1 |+----+------+----+5 rows in set (0.00 sec)mysql> set autocommit = 0;Query OK, 0 rows affected (0.00 sec) mysql> select * from target_tab;Empty set (0.00 sec) mysql> select * from source_tab where name = '1';+----+------+----+| d1 | name | d2 |+----+------+----+|
4 | 1   
|
1 ||
5 | 1   
|
1 ||
6 | 1   
|
1 ||
7 | 1   
|
1 ||
8 | 1   
|
1 |+----+------+----+5 rows in set (0.00 sec)
mysql> insert into target_tab select d1,name from source_tab where name = '1';Query OK, 5 rows affected (0.00 sec)Records: 5
Duplicates: 0
Warnings: 0
session_1未提交,可以对session_1的select的记录进行更新操作。mysql> update source_tab set name = '8' where name = '1';Query OK, 5 rows affected (0.00 sec)Rows matched: 5
Changed: 5
Warnings: 0 mysql> select * from source_tab where name = '8';+----+------+----+| d1 | name | d2 |+----+------+----+|
4 | 8   
|
1 ||
5 | 8   
|
1 ||
6 | 8   
|
1 ||
7 | 8   
|
1 ||
8 | 8   
|
1 |+----+------+----+5 rows in set (0.00 sec)
更新操作先提交mysql> commit;Query OK, 0 rows affected (0.05 sec)
插入操作后提交mysql> commit;Query OK, 0 rows affected (0.07 sec)
此时查看数据,target_tab中可以插入source_tab更新前的结果,这符合应用逻辑:mysql> select * from source_tab where name = '8';+----+------+----+| d1 | name | d2 |+----+------+----+|
4 | 8   
|
1 ||
5 | 8   
|
1 ||
6 | 8   
|
1 ||
7 | 8   
|
1 ||
8 | 8   
|
1 |+----+------+----+5 rows in set (0.00 sec) mysql> select * from target_tab;+------+------+| id
| name |+------+------+| 4   
| 1.00 || 5   
| 1.00 || 6   
| 1.00 || 7   
| 1.00 || 8   
| 1.00 |+------+------+5 rows in set (0.00 sec)mysql> select * from tt1 where name = '1';Empty set (0.00 sec) mysql> select * from source_tab where name = '8';+----+------+----+| d1 | name | d2 |+----+------+----+|
4 | 8   
|
1 ||
5 | 8   
|
1 ||
6 | 8   
|
1 ||
7 | 8   
|
1 ||
8 | 8   
|
1 |+----+------+----+5 rows in set (0.00 sec) mysql> select * from target_tab;+------+------+| id
| name |+------+------+| 4   
| 1.00 || 5   
| 1.00 || 6   
| 1.00 || 7   
| 1.00 || 8   
| 1.00 |+------+------+5 rows in set (0.00 sec)

从上可见,设置系统变量innodb_locks_unsafe_for_binlog的值为“on”后,InnoDB不再对source_tab加锁,结果也符合应用逻辑,但是如果分析BINLOG的内容:
......
SET TIMESTAMP=1169175130;
BEGIN;
# at 274
#070119 10:51:57 server id 1end_log_pos 105   Query   
thread_id=1   exec_time=0   error_code=0
SET TIMESTAMP=1169175117;
update source_tab set name = '8' where name = '1';
# at 379
#070119 10:52:10 server id 1end_log_pos 406   Xid = 5
COMMIT;
# at 406
#070119 10:52:14 server id 1end_log_pos 474   Query   
thread_id=2   exec_time=0   error_code=0
SET TIMESTAMP=1169175134;
BEGIN;
# at 474
#070119 10:51:29 server id 1end_log_pos 119   Query   
thread_id=2   exec_time=0   error_code=0
SET TIMESTAMP=1169175089;
insert into target_tab select d1,name from source_tab where name = '1';
# at 593
#070119 10:52:14 server id 1end_log_pos 620   Xid = 7
COMMIT;
......
可以发现,在BINLOG中,更新操作的位置在INSERT...SELECT之前,如果使用这个BINLOG进行数据库恢复,恢复的结果与实际的应用逻辑不符;如果进行复制,就会导致主从数据库不一致!通过上面的例子,我们就不难理解为什么MySQL在处理“Insertinto target_tab select * from source_tab where ...”和“createtable new_tab ...select ... Fromsource_tab where ...”时要给source_tab加锁,而不是使用对并发影响最小的多版本数据来实现一致性读。还要特别说明的是,如果上述语句的SELECT是范围条件,InnoDB还会给源表加间隙锁(Next-Lock)。因此,INSERT...SELECT...和CREATE TABLE...SELECT...语句,可能会阻止对源表的并发更新,造成对源表锁的等待。如果查询比较复杂的话,会造成严重的性能问题,我们在应用中应尽量避免使用。实际上,MySQL将这种SQL叫作不确定(non-deterministic)的SQL,不推荐使用。如果应用中一定要用这种SQL来实现业务逻辑,又不希望对源表的并发更新产生影响,可以采取以下两种措施:·一是采取上面示例中的做法,将innodb_locks_unsafe_for_binlog的值设置为“on”,强制MySQL使用多版本数据一致性读。但付出的代价是可能无法用binlog正确地恢复或复制数据,因此,不推荐使用这种方式。·二是通过使用“select * from source_tab ... Into outfile”和“load data infile ...”语句组合来间接实现,采用这种方式MySQL不会给source_tab加锁。
页: [1]
查看完整版本: 恢复和复制的需要,对InnoDB锁机制的影响