select ... lock in share mode:加 S 锁
select ... for update:加 X 锁
在一个支持MVCC并发控制的系统中,哪些读操作是快照读?哪些操作又是当前读呢?以MySQL InnoDB为例:
快照读:简单的select操作,属于快照读,不加锁。(当然,也有例外,下面会分析)
select * from table where ?;
当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
所有以上的语句,都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)。
为什么将 插入/更新/删除 操作,都归为当前读?可以看看下面这个 更新 操作,在数据库中的执行流程:
从图中,可以看到,一个Update操作的具体流程。当Update SQL被发给MySQL后,MySQL Server会根据where条件,读取第一条满足条件的记录,然后InnoDB引擎会将第一条记录返回,并加锁 (current read)。待MySQL Server收到这条加锁的记录之后,会再发起一个Update请求,更新这条记录。一条记录操作完成,再读取下一条记录,直至没有满足条件的记录为止。因此,Update操作内部,就包含了一个当前读。同理,Delete操作也一样。Insert操作会稍微有些不同,简单来说,就是Insert操作可能会触发Unique Key的冲突检查,也会进行一个当前读。
注:根据上图的交互,针对一条当前读的SQL语句,InnoDB与MySQL Server的交互,是一条一条进行的,因此,加锁也是一条一条进行的。先对一条满足条件的记录加锁,返回给MySQL Server,做一些DML操作;然后在读取下一条加锁,直至读取完毕。
官方文档解释
SELECT ... LOCK IN SHARE MODE sets a shared mode lock on the rows read. A shared mode lock enables other sessions to read the rows but not to modify them. The rows read are the latest available, so if they belong to another transaction that has not yet committed, the read blocks until that transaction ends.
在读取的行上设置一个共享模式的锁。这个共享锁允许其它session读取数据但不允许修改它。 行读取的是最新的数据,如果他被其它事务使用中而没有提交,读取锁将被阻塞直到那个事务结束。
session1
1
2
3
4
5
6
7
8
9
10
11
|
mysql>
begin
;
Query OK, 0
rows
affected (0.00 sec)
mysql>
select
*
from
t1
where
b = 4000 lock
in
share mode;
+
----+------+
| a | b |
+
----+------+
| 53 | 4000 |
| 54 | 4000 |
+
----+------+
2
rows
in
set
(0.00 sec)
|
session2
1
2
3
4
5
6
7
8
9
10
11
12
13
|
mysql>
begin
;
Query OK, 0
rows
affected (0.00 sec)
mysql>
select
*
from
t1
where
b= 4000;
+
----+------+
| a | b |
+
----+------+
| 53 | 4000 |
| 54 | 4000 |
+
----+------+
2
rows
in
set
(0.00 sec)
mysql>
update
t1
set
b = 2999
where
a = 53;
|
因为session1给查询结果加了读锁,当session2更新a=53这条数据时,session1里加的读锁拒绝修改,所以session2事务在这里阻塞,即发生死锁,当超过一定时间后,该事务执行失败,这是因为mysql的死锁检测起作用。
session1
1
2
|
mysql>
commit
;
Query OK, 0
rows
affected (0.00 sec)
|
session1执行commit,提交事务,解除了对查询结果的读锁,也就是共享锁,这时,session2的update语句执行。
session2
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Query OK, 1 row affected (20.83 sec)
Rows
matched: 1 Changed: 1 Warnings: 0
mysql>
select
*
from
t1
where
a = 53;
+
----+------+
| a | b |
+
----+------+
| 53 | 2999 |
+
----+------+
1 row
in
set
(0.00 sec)
mysql>
commit
;
Query OK, 0
rows
affected (0.04 sec)
|
session1
1
2
3
4
5
6
7
8
9
10
|
mysql>
begin
;
Query OK, 0
rows
affected (0.00 sec)
mysql>
select
*
from
t1
where
a = 53;
+
----+------+
| a | b |
+
----+------+
| 53 | 2999 |
+
----+------+
1 row
in
set
(0.00 sec)
|
session1
1
2
3
4
5
6
7
8
9
10
|
mysql>
begin
;
Query OK, 0
rows
affected (0.00 sec)
mysql>
select
*
from
t1
where
b = 4000 lock
in
share mode;
+
----+------+
| a | b |
+
----+------+
| 54 | 4000 |
+
----+------+
1 row
in
set
(0.00 sec)
|
session2
1
2
3
4
5
6
7
8
9
10
|
mysql>
begin
;
Query OK, 0
rows
affected (0.00 sec)
mysql>
select
*
from
t1
where
b= 4000 lock
in
share mode;
+
----+------+
| a | b |
+
----+------+
| 54 | 4000 |
+
----+------+
1 row
in
set
(0.00 sec)
|
可以看出,当session1加了共享锁后,其他事务还可以给其加共享锁。。。。
官方解释
SELECT ... FOR UPDATE sets an exclusive lock on the rows read. An exclusive lock prevents other sessions from accessing the rows for reading or writing.
在读取行上设置一个排他锁。组织其他session读取或者写入行数据
session1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
mysql>
begin
;
Query OK, 0
rows
affected (0.00 sec)
mysql>
select
*
from
t1
where
b < 4000
for
update
;
+
----+------+
| a | b |
+
----+------+
| 51 | 2000 |
| 52 | 2999 |
| 53 | 2999 |
| 57 | 2000 |
+
----+------+
4
rows
in
set
(0.06 sec)
mysql>
|
session2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
mysql>
begin
;
Query OK, 0
rows
affected (0.00 sec)
mysql>
select
*
from
t1
where
b < 4000 ;
+
----+------+
| a | b |
+
----+------+
| 51 | 2000 |
| 52 | 2999 |
| 53 | 2999 |
| 57 | 2000 |
+
----+------+
4
rows
in
set
(0.00 sec)
mysql>
select
*
from
t1
where
b < 4000
for
update
;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting
transaction
mysql>
|
在该事务中在加排它锁,过一段时间后,报错。。但能读取数据。
================END================