mysql锁机制测试

研究一下mysql的锁机制,参考官方资料如下:
mysql的锁类型:
https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html

mysql的四种隔离级别:
https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
READ UNCOMMITTED:读未提交,会出现脏读
READ COMMITTED:读提交,会出现幻读,可使用mvcc一致性非锁读
REPEATABLE READ:可重复读,是默认隔离级别,某些情况也会出现幻读,可使用mvcc一致性非锁读
SERIALIZABLE:串行化,隔离级别最高,事务不可并行

测试主要是参考官方资料,测试RR隔离级别有索引情况的Record Locks,Gap Locks,Next-Key Locks
                                         还有在RC情况下的,update 使用的semi-consistent read功能
RC隔离级别下,索引只会有record lock,不会有gap lock,资料如下:

READ COMMITTED
For locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE), UPDATE statements, and 
DELETE statements, InnoDB locks only index records, not the gaps before them, and thus permits the
free insertion of new records next to locked records. Gap locking is only used for foreign-key constraint
checking and duplicate-key checking.

RR隔离级别gap lock和Next-Key Locks测试:
1.非唯一索引,范围搜索时加锁:
create table aryoyo(num int);
create index testidx on aryoyo(num);
insert into aryoyo values(6),(10),(13),(17),(22);

窗口1:

begin;
select * from aryoyo where num between 10 and 13 for update;
mysql> select * from aryoyo where num between 10 and 13 for update;
+------+
| num  |
+------+
|   10 |
|   13 |
+------+

窗口2:

mysql> insert into aryoyo values(7);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into aryoyo values(6);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into aryoyo values(5);
Query OK, 1 row affected (0.01 sec)

mysql> insert into aryoyo values(10);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into aryoyo values(12);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into aryoyo values(15);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into aryoyo values(17);
Query OK, 1 row affected (0.00 sec)

插入数字15锁超时的时候,可以查看:

mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
+--------------+-------------+-----------+-----------+-----------------+------------+------------+-
| lock_id      | lock_trx_id | lock_mode | lock_type | lock_table      | lock_index |  lock_data          
+--------------+-------------+-----------+-----------+-----------------+------------+------------+----
| 14628:84:4:5 | 14628       | X,GAP     | RECORD    | `aryo`.`aryoyo` | testidx    |  17, 0x000000000703 |
| 14613:84:4:5 | 14613       | X         | RECORD    | `aryo`.`aryoyo` | testidx    |  17, 0x000000000703 |
+--------------+-------------+-----------+-----------+-----------------+------------+------------+----

14628是插入的transaction id,该事务要加Insert Intention Locks也是gap lock的一种
14613是窗口1select for update的锁,可见17上加了Next-Key Locks
结论:
加锁范围:(6,17], 也就是在10,13,17上各加了一个Next key lock.

2.非唯一索引,唯一匹配搜索时候加锁:
窗口1:

begin;
mysql> select * from aryoyo where num=10 for update;
+------+
| num  |
+------+
|   10 |
+------+
1 row in set (0.04 sec)

窗口2:

mysql> insert into aryoyo values(13);
Query OK, 1 row affected (0.04 sec)

mysql> insert into aryoyo values(12);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into aryoyo values(9);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into aryoyo values(6);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into aryoyo values(5);
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
+--------------+-------------+-----------+-----------+-----------------+------------+------------+
| lock_id      | lock_trx_id | lock_mode | lock_type | lock_table      | lock_index |  lock_data          |
+--------------+-------------+-----------+-----------+-----------------+------------+------------+---
| 14640:84:4:4 | 14640       | X,GAP     | RECORD    | `aryo`.`aryoyo` | testidx    | 13, 0x000000000702 |
| 14629:84:4:4 | 14629       | X,GAP     | RECORD    | `aryo`.`aryoyo` | testidx    | 13, 0x000000000702 |
+--------------+-------------+-----------+-----------+-----------------+------------+------------+---
2 rows in set, 1 warning (0.00 sec)
结论是:加锁范围是(6,13) ,也就是10上加了一个next key lock,而在10 和13之间加了gap 锁

3.唯一索引,范围搜索时候加锁:
create table aryoyo(num int);
insert into aryoyo values(6),(10),(13),(17),(22);
create unique index testidx on aryoyo(num);

窗口1:

mysql> begin;
mysql> select * from aryoyo where num between 10 and 13 for update;
+------+
| num  |
+------+
|   10 |
|   13 |
+------+
2 rows in set (0.00 sec)

窗口2:

mysql> insert into aryoyo values(9);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into aryoyo values(11);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into aryoyo values(13);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into aryoyo values(14);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into aryoyo values(17);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
结论:和非唯一索引的范围搜索一样
加锁范围:(6,17], 也就是在10,13,17上各加了一个Next key lock.

4.唯一索引,唯一匹配搜索时候加锁:
测试结果步骤同上,结论:仅仅在匹配项num=10上加了record lock,也可以参考官方资料:

Gap locking is not needed for statements that lock rows using a unique index to search 
for a unique row. (This does not include the case that the search condition includes only 
some columns of a multiple-column unique index; in that case, gap locking does occur.) 


关于semi-consistent read:

Using READ COMMITTED has additional effects: 
For UPDATE statements, if a row is already locked, InnoDB performs a “semi-consistent”
read, returning the latest committed version to MySQL so that MySQL can determine
whether the row matches the WHERE condition of the UPDATE. If the row matches
(must be updated), MySQL reads the row again and this time InnoDB either locks it 
or waits for a lock on it.
1.CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB;
  INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2);
  UPDATE t SET b = 5 WHERE b = 3;
  如果创建索引key(b)情况,RR和RC隔离级别下,被锁的行是一样的 第二个会话都可以update b不等于3的行.

  但是在不创建任何索引情况下,
  RR会进行全表扫描并锁住所有读过的行,第二个会话不能update任意行直到前一个update事务提交.
  RC隔离则只是在符合update 条件的结果行上加锁,第二个会话仍然可以update b不等于3的行:
 窗口1:
mysql> begin;
mysql>  UPDATE t SET b = 5 WHERE b = 3;
Query OK, 2 rows affected (0.00 sec)

mysql> select * from t;
+---+------+
| a | b    |
+---+------+
| 1 |    2 |
| 2 |    5 |
| 3 |    2 |
| 4 |    5 |
| 5 |    2 |
+---+------+
5 rows in set (0.00 sec)

窗口2:仍可以update b不等于3的行:

mysql> UPDATE t SET b = 6 WHERE b = 2;
Query OK, 3 rows affected (0.00 sec)

mysql> select * from t;
+---+------+
| a | b    |
+---+------+
| 1 |    6 |
| 2 |    3 |
| 3 |    6 |
| 4 |    3 |
| 5 |    6 |
+---+------+
5 rows in set (0.00 sec)

2.但是如果在RC隔离有索引情况下,也可能会锁住比没加索引更多的行,例子如下:
CREATE TABLE t (a INT NOT NULL, b INT, c INT, INDEX (b)) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2,3),(2,2,4);

窗口1:

mysql> begin;
mysql> UPDATE t SET b = 3 WHERE b = 2 AND c = 3;
Query OK, 1 row affected (0.00 sec)

mysql> select * from t;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |    3 |    3 |
| 2 |    2 |    4 |
+---+------+------+
2 rows in set (0.00 sec)

窗口2:因为使用key(b)锁住了所有b=2,所以不能update b=2 and c=4

mysql> UPDATE t SET b = 4 WHERE b = 2 AND c = 4;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

猜你喜欢

转载自blog.csdn.net/aryoyo/article/details/80665080