1.3.5、一致性非阻塞读 一致性读是MySQL的重要特点之一,InnoDB通过MVCC机制表示数据库某一时刻的查询快照,查询可以看该时刻之前提交的事务所做的改变,但是不能看到该时刻之后或者未提交事务所做的改变。但是,查询可以看到同一事务中之前语句所做的改变,例如:
例1-2
Session 1
|
Session 2
|
mysql> select * from t;
Empty set (0.00 sec)
mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.00 sec)
mysql> select * from t;
+------+
| i |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.01 sec)
mysql> update t set i=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from t;
+------+
| i |
+------+
| 3 |
+------+
1 row in set (0.00 sec)
|
|
|
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t;
+------+
| i |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
|
mysql> commit;
Query OK, 0 rows affected (0.06 sec)
|
|
|
mysql> select * from t;
+------+
| i |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
|
|
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t;
+------+
| i |
+------+
| 3 |
+------+
1 row in set (0.00 sec)
|
如果事务的隔离级别为REPEATABLE READ(默认),同一个事务中的所有一致性读都是读的事务的第一次读操作创建的快照。你可以提交当前事务,然后在新的查询中即可看到最新的快照,如上所示。 如果事务的隔离级别为READ COMMITTED,一致性读只是对事务内部的读操作和它自己的快照而言的,结果如下:
例1-3
Session 1
|
Session 2
|
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.01 sec)
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t;
+------+
| i |
+------+
| 3 |
+------+
1 row in set (0.00 sec)
|
|
|
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.01 sec)
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t;
+------+
| i |
+------+
| 3 |
+------+
1 row in set (0.00 sec)
|
mysql> update t set i=5;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
|
|
|
mysql> select * from t;
+------+
| i |
+------+
| 3 |
+------+
1 row in set (0.00 sec)
|
mysql> commit;
Query OK, 0 rows affected (0.06 sec)
|
|
|
mysql> select * from t;
+------+
| i |
+------+
| 5 |
+------+
1 row in set (0.00 sec)
|
注意,session 2发生了不可重复读。 当InnoDB在READ COMMITTED 和REPEATABLE READ隔离级别下处理SELECT语句时,一致性读是默认的模式。一致性读不会对表加任何锁,所以,其它连接可以同时改变表。 假设事务处于REPEATABLE READ级别,当你正在进行一致性读时,InnoDB根据查询看到的数据给你一个时间点。如果其它的事务在该时间点之后删除一行,且提交事务,你不会看到行已经被删除,插入和更新操作一样。但是,InnoDB与其它DBMS的不同是,在REPEATABLE READ隔离级别下并不会造成幻像。 一致性读不与DROP TABLE 或者 ALTER TABLE一起工作。 在nodb_locks_unsafe_for_binlog变量被设置或者事务的隔离级别不是SERIALIZABLE的情况下,InnoDB对于没有指定FOR UPDATE 或 LOCK IN SHARE MODE的INSERT INTO ... SELECT, UPDATE ... (SELECT), 和CREATE TABLE ... SELECT语句使用一致性读,在这种情况下,查询语句不会对表中的元组加锁。否则,InnoDB将使用锁。
1.3.6、SELECT ... FOR UPDATE和SELECT ... LOCK IN SHARE MODE的加锁读(locking read) 在一些场合,一致性读并不是很方便,此时,可以用加锁读。InnoDB支持两种加锁读: (1) SELECT ... LOCK IN SHARE MODE:对读取的元组加S锁。 (2) SELECT ... FOR UPDATE:在扫描索引记录的过程中,会阻塞其它连接的SELECT ...LOCK IN SHARE MODE和一定事务隔离级别下的读操作。 InnoDB使用两阶段封锁协议,事务直到提交或回滚时才会释放所有的锁,这都是系统自动执行的。此外,MySQL支持LOCK TABLES和UNLOCK TABLES,但这些都是在服务器层实现的,而不是在存储引擎。它们有用处,但是不能取代存储引擎完成事务处理,如果你需要事务功能,请使用事务型存储引擎。 来考虑locking read的应用,假设你要在表child插入一个新的元组,并保证child中的记录在表parent有一条父记录。如果你用一致性读来读parent表,确实可以将要插入的child row的parent row,但是可以安全的插入吗?不,因为在你读parent表时,其它连接可能已经删除该记录。(一致性读是针对事务内而言的,对于数据库的状态,它应该叫做“不一致性读”) 此时,就可以使用SELECT LOCK IN SHARE MODE,它会对读取的元组加S锁,从而防止其它连接删除或更新元组。另外,如果你想在查询的同时,进行更新操作,可以使用SELECT ... FOR UPDATE,它读取最新的数据,然后对读到的元组加X锁。此时,使用SELECT ... LOCK IN SHARE MODE不是一个好主意,因为此时如果有两个事务进行这样的操作,就会造成死锁。 注:SELECT ... FOR UPDATE仅在自动提交关闭(即手动提交)时才会对元组加锁,而在自动提交时,符合条件的元组不会被加锁。
1.3.7、记录锁(record lok)、间隙锁(gap lock)和后码锁(next-key lock) InnoDB有以下几种行级锁: (1)记录锁:对索引记录(index records)加锁,InnoDB行级锁是通过给索引的索引项加锁来实现的,而不是对记录实例本身加锁。如果表没有定义索引,InnoDB创建一个隐藏的聚簇索引,然后用它来实现记录加锁(关于索引与加锁之间的关系的详细介绍请看下一章)。 (2)间隙锁:对索引记录之间的区间,或者第一个索引记录之前的区间和最后一个索引之后的区间加锁。 (3)后码锁:对索引记录加记录锁,且对索引记录之前的区间加锁。 默认情况下,InnoDB的事务工作在REPEATABLE READ的隔离级别,而且系统变量innodb_locks_unsafe_for_binlog为关闭状态。此时,InnoDB使用next-key锁进行查找和索引扫描,从而达到防止“幻像”的目的。 Next-key锁是记录锁和间隙的结合体。当InnoDB查找或扫描表的索引时,对它遇到的索引记录加S锁或者X锁,所以,行级锁(row-level lock)实际上就是索引记录锁(index-record lock);此外,它还对索引记录之前的区间加锁。也就是说,next-key锁是索引记录锁,外加索引记录之前的区间的间隙锁。如果一个连接对索引中的记录R持有S或X锁,其它的连接不能按照索引的顺序在R之前的区间插入一个索引记录。 假设索引包含以下值:10, 11,13和20,则索引的next-key锁会覆盖以下区间(“(”表示不包含,“[”表示包含): (negative infinity, 10] (10, 11] (11, 13] (13, 20] (20, positive infinity) 对于最后一个区间,next-key锁将锁住索引最大值以上的区间,上界虚记录(“supremum” pseudo-record)的值比索引中的任何值都大,其实,上界不是一个真实的索引记录,所以,next-lock将对索引的最大值之后的区间加锁。
间隙锁对查询唯一索引中的唯一值是没有必要的,例如,id列有唯一索引,则下面的查询仅对id=100的元组加索引记录锁(index-record lock),而不管其它连接是否在之前的区间插入元组。 SELECT * FROM child WHERE id = 100; 如果id没有索引,或者非唯一索引,则语句会锁住之前的空间。
例1-4
Session 1
|
Session 2
|
mysql> create unique index i_index on t(i);
Query OK, 0 rows affected (0.19 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> select * from t;
+------+
| i |
+------+
| 4 |
| 10 |
+------+
2 rows in set (0.00 sec)
|
|
|
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select i from t where i =10 lock in share mode;
+------+
| i |
+------+
| 10 |
+------+
1 row in set (0.00 sec)
|
mysql> insert into t(i) values(9);
Query OK, 1 row affected (0.03 sec)
|
|
|
mysql> select * from t;
+------+
| i |
+------+
| 4 |
| 9 |
| 10 |
+------+
3 rows in set (0.00 sec)
|
上例中,产生了幻像问题。如果将唯一查询变成范围查询,结果如下(接上例的索引):
例1-5
Session 1
|
Session 2
|
mysql> select * from t;
+------+
| i |
+------+
| 4 |
| 9 |
| 10 |
+------+
3 rows in set (0.00 sec)
|
|
|
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select i from t where i>4 lock in share mode;
+------+
| i |
+------+
| 9 |
| 10 |
+------+
2 rows in set (0.00 sec)
|
mysql> insert into t(i) values(1);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t(i) values(8);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
|
|
可以看到,session 2 的next-key使得在i=4之前的区间和之后的插入都被阻塞。
(责任编辑:admin) |