史上最全mysql锁介绍及死锁案例
1 概念
2 分类
3 存储引擎对锁的支持情况
存储引擎 |
行锁 |
页锁 |
表锁 |
InnoDB | 支持 |
不支持 |
支持 |
MyISAM | 不支持 |
不支持 |
支持 |
BDB | 不支持 | 支持 | 不支持 |
4 InnoDB存储引擎下常见的锁介绍
建表语句
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` varchar(20) NOT NULL COMMENT '员工号',
`user_name` varchar(32) NOT NULL COMMENT '姓名',
PRIMARY KEY (`id`),
UNIQUE KEY `user_id_idx` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
初始数据
共享(S)锁和排它(X)锁(表锁,行锁)
共享锁:事务执行过程中读取数据时,防止其他事务对数据进行修改,但允许其他事务可以同时读取的一种机制。
select * from user where user_id=2 lock in share mode;
排它锁:事务执行过程中修改或者删除数据时,防止其他事务对当前数据进行修改,且不允许其他事务加共享锁的一种机制。
select * from user where user_id=2 for update;
简单说即四种情况:
(1)事务A对记录R加了共享锁且还未释放时,其他事务可以对记录R加共享锁,但是不能对记录A加排它锁。
(2)事务A对记录R加了排它锁且未释放时,其他事务不可以对记录R加共享锁或者排它锁。
(3)事务A对记录R加了共享锁,事务A还可以对记录R加排它锁。
(4)事务A对记录R加了排它锁,事务A还可以对记录R加共享锁。
意向锁 Intention lock(表锁)
意向共享(IS)锁:事务在获取共享锁(行)时,必须先拿到意向共享锁
意向排它(IX)锁:事务在获取排它锁(行)时,必须先拿到意向排它锁
意向锁其实是为表级共享,排它锁而生的,因为有了意向锁加快了锁之间冲突检测效率。
表锁间的兼容性:横向是锁持有的锁,纵向是请求的锁
锁 |
共享锁 | 共享意向锁 | 排它锁 | 排它意向锁 |
共享锁 |
兼容 |
兼容 |
不兼容 |
不兼容 |
共享意向锁 |
兼容 |
兼容 |
不兼容 |
兼容 |
排它锁 |
不兼容 | 不兼容 | 不兼容 | 不兼容 |
排它意向锁 |
不兼容 | 兼容 | 不兼容 | 兼容 |
记录锁 record lock
记录锁:事务加在单个索引记录上的排它锁,阻止其他事务在此索引上的插入,更新,删除操作。如果是唯一索引或者主键索引时,会锁住单条。如果是非唯一索引时则可能是单号或者多行,此时除了记录锁还会加锁间隙锁。
update user set user_name='devin' where user_id='2';
间隙锁 gap lock
事务加在索引间隙中的锁,锁住的是索引的间隙
比如现有的记录user_id 1,2,4,5 四个值
(1)下面sql锁住的是(1,4)这个值对应的索引间隙。
select * from user where user_id BETWEEN 1 AND 4 FOR UPDATE;
(2)下面的sql锁住的是(5,+∞)对应索引的这个间隙
select * from user where user_id = 6 FOR UPDATE;
临键锁 next-key lock
锁住的是索引记录+索引间隙
临键锁=记录锁+间隙锁
插入意向锁 insert intention lock
特殊的间隙锁,针对插入而设计的一种锁机制。
锁住的是索引间隙,插入之前先获取插入意向锁。在插入意向锁的作用下即使A事务插入时锁住了索引间隙,B事务仍然可以插入索引间隙中的记录,无需等待。
insert user(user_id,user_name) values('6','devin2');
自增锁 auto-inc lock(表锁)
自增锁:实现了表的id自增
行锁间的兼容性:横向是锁持有的锁,纵向是请求的锁
Gap间隙锁 |
Insert Intention(插入意向锁) |
Record 记录锁 |
Next-Key 临键锁 |
|
Gap 间隙锁 |
兼容 |
兼容 |
兼容 |
兼容 |
Insert Intention(插入意向锁) |
不兼容 |
兼容 |
兼容 |
兼容 |
Record 记录锁 |
兼容 |
兼容 |
不兼容 |
不兼容 |
Next-Key 临键锁 |
兼容 |
兼容 |
不兼容 |
不兼容 |
5 死锁案例
两个事务(X锁和X锁不兼容)
事务T1先锁住了主键索引,然后事务T2 锁住了关联的非主键索引,T2操作尝试锁住主键索引,T1锁住非主键索引。
测试验证:
执行sql的顺序不一致导致死锁
两个不同的事务执行的sql顺序不一致(如批量插入或者不同方法sql顺序不一致)
测试验证:
间隙锁和插入意向锁不兼容
两个事务T1,T2,分别加间隙锁(1,4),插入意向锁插入值2,
并发执行T1的插入意向锁和T2的间隙锁不兼容,T2的插入意向锁和T1的间隙锁不兼容,形成死锁。
测试验证:
Insert ... on duplicate key (S锁和X锁不兼容导致死锁)
两个事务并发执行同样的sql ,当记录存在时 on duplicate key update 分两步执行,对现有的行加锁S(共享锁)锁,然后对记录进行更新加X锁。因为S锁和X锁不兼容,如果两个事务同时对记录加了锁,然后再分别尝试加X锁,此时就出现了死锁。
三个事务并发insert 同样的记录
其中一个事务A拿到了改记录的X锁,另外两个事务B,C拿到了S锁。A事务因为后续流程异常回滚,释放X锁。B,C事务争抢X锁,因为都持有改记录的S锁。申请的X锁和对方持有的S锁不兼容,导致死锁。