目录

MySQL中的锁


存储引擎 表级锁 行级锁页级锁
MyISAM
Memory
BDB
InnoDB

锁类型 开销 加锁速度死锁问题锁定粒度发生锁冲突的概率并发度
表级锁 不会出现 最高最低
行级锁 会出现 最低最高
页级锁 介于表锁和行锁之间 会出现 介于表锁和行锁之间一般
- 表级锁:开销小,加锁快;不会出现死锁;锁粒度大,发生锁冲突的概率最高,并发度最低。
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高
- 页级锁:开销和加锁时间介于表锁和行锁之间;会出现死锁;并发度一般
- 意向锁(Intention Locks): lock mode IX
- 记录锁(Record Locks): lock_mode X locks rec but not gap
- Gap锁(Gap Locks): lock_mode X locks gap before rec
- Next-Key Locks: lock_mode X
- 插入意向锁(Insert Intention Locks): lock_mode X locks gap before rec insert intention

获取InnoDB行锁争用情况

mysql> SHOW STATUS LIKE 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name                 | VALUE |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0     |
| Innodb_row_lock_time          | 34843 |
| Innodb_row_lock_time_avg      | 5807  |
| Innodb_row_lock_time_max      | 21657 |
| Innodb_row_lock_waits         | 6     |
+-------------------------------+-------+
5 ROWS IN SET (0.00 sec)

查询information_scheam库中的相关表

SELECT * FROM information_schema.innodb_locks;
SELECT * FROM information_schema.innodb_lock_waits;

设置InnoDBMonitors

mysql> CREATE TABLE innodb_monitor(a INT) engine=innodb;
Query OK, 0 ROWS affected (0.18 sec)
SET GLOBAL innodb_status_output_locks=ON;

mysql> BEGIN;
sQuery OK, 0 ROWS affected (2.05 sec)
 
mysql> SELECT * FROM test_table FOR UPDATE;
+----+-----+
| id | val |
+----+-----+
|  1 |   1 |
|  2 |   1 |
|  3 |   1 |
+----+-----+
3 ROWS IN SET (4.78 sec)
mysql> SHOW engine innodb STATUS\G
*************************** 1. ROW ***************************
  TYPE: InnoDB
  Name: 
STATUS: 
=====================================
2022-04-15 23:21:30 0x7f021c8ae700 INNODB MONITOR OUTPUT
=====================================
......
......
......
------------
TRANSACTIONS
------------
---TRANSACTION 549605684, ACTIVE 10 sec
2 LOCK struct(s), heap SIZE 1136, 4 ROW LOCK(s)
MySQL thread id 3592040, OS thread handle 139647045527296, query id 1729758289 10.10.183.226 root starting
SHOW engine innodb STATUS
TABLE LOCK TABLE `okami`.`test_table` trx id 549605684 LOCK mode IX
RECORD LOCKS SPACE id 5466 page no 3 n bits 72 INDEX PRIMARY OF TABLE `okami`.`test_table` trx id 549605684 lock_mode X
Record LOCK, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; ASC supremum;;
 
Record LOCK, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000001; ASC     ;;
 1: len 6; hex 000020c13de4; ASC     = ;;
 2: len 7; hex ab000001520110; ASC     R  ;;
 3: len 4; hex 80000001; ASC     ;;
 
Record LOCK, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000002; ASC     ;;
 1: len 6; hex 000020c14054; ASC     @T;;
 2: len 7; hex d80000100d0110; ASC        ;;
 3: len 4; hex 80000001; ASC     ;;
 
Record LOCK, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000003; ASC     ;;
 1: len 6; hex 000020c18b3a; ASC      :;;
 2: len 7; hex b7000002f40110; ASC        ;;
 3: len 4; hex 80000001; ASC     ;;
 
......

InnoDB的行锁模式

当前锁模式\请求锁模式X IXSIS
X 冲突 冲突 冲突 冲突
iX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容

InnoDB行锁实现方式


间隙锁(Gap锁)

间隙锁锁定一个范围,但不包含记录本身。间隙锁封锁索引记录中的间隔,或者第一条索引记录之前的范围,又或者最后一条索引记录之后的范围。
Gap Locks 的作用是为了阻止多个事务将记录插入到同一范围内,避免幻读问题的产生。

间隙锁唯一的目的是防止其他事务插入到统一范围。间隙锁可以共存,一个事务获取的间隙锁并不阻止另一个事务获取同一间隙的间隙锁。

Nexy-Key锁

Next-Key Locks 是Record Lock 和Record之前的间隙的间隙锁的一种结合。
  • 我们执行一条SQL语句:SELECT * FROM user WHERE id > 7 AND id < 11 FOR UPDATE,锁住的不是9这单个值,而是对(5,9]、(9,12] 这2个区间加了X锁。因此任何对于这个范围的插入都是不被允许的,从而避免幻读。
  • 所以触发临键锁与触发间隙锁的区别就在于:查询条件范围的端点是否在索引上。不在,用临键锁。在,用间隙锁

插入意向锁

插入意向锁是一种特殊的间隙锁(所以有的地方把它简写成 II GAP),这个锁表示插入的意向,只有在 INSERT 的时候才会有这个锁。
注意,这个锁虽然也叫意向锁,但是和上面介绍的表级意向锁是两个完全不同的概念,不要搞混淆了。
插入意向锁和插入意向锁之间互不冲突,所以可以在同一个间隙中有多个事务同时插入不同索引的记录。
插入意向锁只会和间隙锁或 Next-key 锁冲突,正如上面所说,间隙锁唯一的作用就是防止其他事务插入记录造成幻读,那么间隙锁是如何防止幻读的呢?正是由于在执行 INSERT 语句时需要加插入意向锁,而插入意向锁和间隙锁冲突,从而阻止了插入操作的执行。


死锁的案例

避免死锁的方式


参考文献