Contents
  1. 1. 问题一
  2. 2. 问题二
  3. 3. 分析

问题一

mysql> create table guo(id int primary key);
Query OK, 0 rows affected (0.02 sec)

mysql> select * from guo;
+----+
| id |
+----+
|  1 |
|  2 |
|  8 |
| 10 |
| 11 |
| 23 |
+----+
6 rows in set (0.00 sec)

会话A:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from guo where id <8 for update;
+----+
| id |
+----+
|  1 |
|  2 |
+----+
2 rows in set (0.00 sec)

mysql> 

会话B:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into guo select 15;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> 

MySQL使用的是范围锁(Gap Lock),理论上不应该锁住不在锁定范围的。

再新建一张表,这次两个字段

mysql> create table qing(id int primary key,qid int);
Query OK, 0 rows affected (0.02 sec)

mysql> insert into qing values(1,1),(2,2),(3,3),(8,8),(9,9);
Query OK, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from qing;
+----+------+
| id | qid  |
+----+------+
|  1 |    1 |
|  2 |    2 |
|  3 |    3 |
|  8 |    8 |
|  9 |    9 |
+----+------+
5 rows in set (0.00 sec)

mysql> 

会话A:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from qing where id<8 for update;
+----+------+
| id | qid  |
+----+------+
|  1 |    1 |
|  2 |    2 |
|  3 |    3 |
+----+------+
3 rows in set (0.00 sec)

会话B:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into qing select 10,10;
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> 

执行成功。

问题二

使用上面第一张表
会话A

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from guo where id=8 for update;
+----+
| id |
+----+
|  8 |
+----+
1 row in set (0.01 sec)

会话B

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into guo select 12;
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

会话A

mysql> select * from guo;
+----+
| id |
+----+
|  1 |
|  2 |
|  8 |
| 10 |
| 11 |
| 12 |
| 19 |
| 23 |
+----+
8 rows in set (0.00 sec)    

可以看到12已经插入到表中,而此时会话A的事务还没结束。
接着使用第二张表
会话A:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from qing where id=3 for update;
+----+------+
| id | qid  |
+----+------+
|  3 |    3 |
+----+------+
1 row in set (0.00 sec)

会话B:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into qing select 99,99;
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> 

会话A:

mysql> select * from qing;
+----+------+
| id | qid  |
+----+------+
|  1 |    1 |
|  2 |    2 |
|  3 |    3 |
|  8 |    8 |
|  9 |    9 |
| 10 |   10 |
| 99 |   99 |
+----+------+
7 rows in set (0.00 sec)

mysql> 

同样在会话A没结束事务时读到了99。

分析

问题一可能是表只有一个字段的问题,问题二可能是read view的问题。后续继续探讨。

在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。
快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。
当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。

在一个支持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锁 (排它锁)。

以上所有当前读语句都会发生上面问题二的情况,如下对共享锁的情况。

会话A:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from qing where id=10 lock in share mode;
+----+------+
| id | qid  |
+----+------+
| 10 |   10 |
+----+------+
1 row in set (0.00 sec)

会话B:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into qing select 88,88;
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

会话A:

mysql> 
mysql> select * from qing;
+----+------+
| id | qid  |
+----+------+
|  1 |    1 |
|  2 |    2 |
|  3 |    3 |
|  8 |    8 |
|  9 |    9 |
| 10 |   10 |
| 88 |   88 |
| 99 |   99 |
+----+------+
8 rows in set (0.00 sec)
Contents
  1. 1. 问题一
  2. 2. 问题二
  3. 3. 分析