vlambda博客
学习文章列表

史上最全mysql锁介绍及死锁案例


1 概念

mysql锁:控制数据库表数据并发访问(增删改查)的一种调度机制。

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='用户信息表';


  • 初始数据

史上最全mysql锁介绍及死锁案例


  • 共享(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锁住非主键索引。

测试验证:

史上最全mysql锁介绍及死锁案例


史上最全mysql锁介绍及死锁案例


  •  执行sql的顺序不一致导致死锁

两个不同的事务执行的sql顺序不一致(如批量插入或者不同方法sql顺序不一致)


测试验证:

史上最全mysql锁介绍及死锁案例


史上最全mysql锁介绍及死锁案例



  • 间隙锁和插入意向锁不兼容


两个事务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锁不兼容,导致死锁。