未知问题
问题一
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)