一、形成原因:一般是事务相互等待对方释放资源,最后形成环路造成的
二、场景模拟一:
-创建表test
CREATE TABLE `test` (
`id` int(20) NOT NULL,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-插入数据
insert into test values(1,1),(5,5),(10,10),(15,15),(20,20),(25,25);
id(PRIMARY ) | name |
---|---|
1 | 1 |
5 | 5 |
10 | 10 |
15 | 15 |
20 | 20 |
25 | 25 |
当数据库的隔离级别为Repeatable Read或Serializable时,我们来看这样的两个并发事务(场景一)
session1 | session2 |
---|---|
begin; | |
begin; | |
select * from test where id = 12 for update;先请求IX锁并成功。获取再请求X锁,但因行记录不存在,故得到的是间隙锁(10,15)
|
|
select * from test where id = 13 for update;先请求IX锁并成功。获取再请求X锁,但因行记录不存在,故得到的是间隙锁(10,15)
|
|
insert into test(id, name) values(12, "test1");请求插入意向锁(12),因事务二已有间隙锁,请求只能等待
|
|
锁等待中 | insert into test(id, name) values(13, "test2"); 请求插入意向锁(13),因事务一已有间隙锁,请求只能等待
|
锁等待解除 | 死锁,session 2的事务被回滚 Deadlock found when trying to get lock; try restarting transaction
|
上面两个并发事务一定会发生死锁(这里之所以限定RR和Serializable两个隔离级别,是因为只有这两个级别下才会有间隙锁/临键锁,而这是导致死锁的根本原因,后面会详细分析)。
二、场景模拟二:
session1 | session2 |
---|---|
begin; | |
begin; | |
select * from test where id = 12 for update;先请求IX锁并成功获取。再请求X锁,但因行记录不存在,故得到的是间隙锁(10,15)
|
|
select * from test where id = 16 for update;先请求IX锁并成功获取。再请求X锁,但因行记录不存在,故得到的是间隙锁(15,20)
|
|
insert into test(id, name) values(12, "test1");请求插入意向锁(12),获取成功
|
|
commit; | insert into test(id, name) values(16, "test2");请求插入意向锁(16),获取成功
|
commit; |
在这个并发场景下,两个事务均能成功提交,而不会有死锁。在上面的示例中,我们发现,select ... for update虽然可以用于解决数据库的并发操作,但在实际项目中却不建议使用,原因是当查询条件对应的记录不存在时,很容易造成死锁。而造成死锁的原因和MySQL的锁机制有关。本文将详细介绍常见的七种锁机制,了解了这些锁机制之后就能理解造成场景一死锁的根本原因以及场景一和场景二差异的原因。
参考帖子 一
参考帖子 二
参考帖子 三
三、锁等待超时与information_schema的三个表
- 当前运行的所有事务
select trx_id,trx_state,trx_isolation_level,trx_mysql_thread_id,trx_tables_locked,trx_rows_locked from information_schema.innodb_trx;
- 当前出现的锁
select * from information_schema.innodb_locks;
- 锁等待的对应关系
select * from information_schema.innodb_lock_waits;
四、查看死锁日志
show engine innodb status;
网友评论