之前有一个同事问到我,为什么多个线程同时去做删除同一行数据的操作,老是报死锁,在线上已经出现好多次了,我问了他几个问题:
1. 是不是在一个事务中做了好几件事情?
答:不是,只做一个删除操作,自动提交
2. 有多少个线程在做删除?
答:差不多10个
3. 是什么隔离级别?
答:可重复读
当时觉得不可思议,按说自动提交的话行锁,如果已经有事务加锁了,则会等待,等提交之后再去做,发现已经删除了,就会返回,删除0条,为什么会死锁?
但事情已经出了,必须研究一下,不然终究是心头之苦啊。
然后想到既然线上有这么简单的操作就可以死锁,那么应该写个简单的程序就可以重现,然后同事李润杰兄弟咔嚓咔嚓没多时就给我了,在这里谢谢他。
首先环境是这样的:
CREATE TABLE `abcdefg` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`abc` varchar(30),
`def` varchar(30) ,
`ghi` date,
`jkl` date,
`mnp` tinyint(4),
PRIMARY KEY (`id`),
UNIQUE KEY `uniqdefghijkl` (`def`,`ghi`,`jkl`)
);
这个表包括2个索引,一个是聚簇索引,另一个是uniqdefghijkl的二级唯一索引。
事先插入很多数据,然后3个线程同时做对同一条记录的删除,这里只做删除操作,并且都是自动提交,为了得到一批要删除的数据,事先查询很多条出来备用。
删除语句是这样的:
delete from abcdefg WHERE abc= '我是变量' and def= '我是变量' and ghi= '2013-12-19 00:00:00' and jkl= '2013-12-20 00:00:00';
那么现在就开始重现。。。
果然很快,死锁真的出现了,下面是执行show engine innodb status的结果:
===================================================
LATEST DETECTED DEADLOCK
------------------------
140123 12:20:50
*** (1) TRANSACTION:
TRANSACTION 2E10, ACTIVE 4917 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s)
MySQL thread id 3, OS thread handle 0x1008, query id 43 192.168.xx.x username upd
ating
delete from abcdefg WHERE abc= '我是变量' and def= '我是变量' and ghi= '2013-12-19 00:00:00' and jkl= '2013-12-20 00:00:00';
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 12295 n bits 528 index `uniqdefghijkl` of table
`deadlock`.`abcdefg` trx id 2E10 lock_mode X locks rec but not gap waiti
ng
Record lock, heap no 167 PHYSICAL RECORD: n_fields 4; compact format;
*** (2) TRANSACTION:
TRANSACTION 2E0E, ACTIVE 4917 sec starting index read
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1248, 2 row lock(s)
MySQL thread id 1, OS thread handle 0x1190, query id 41 192.168.xx.xx username upd
ating
delete from abcdefg WHERE abc= '我是变量' and def= '我是变量' and ghi= '2013-12-19 00:00:00' and jkl= '2013-12-20 00:00:00';
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 12295 n bits 528 index `uniqdefghijkl` of table
`deadlock`.`abcdefg` trx id 2E0E lock_mode X locks rec but not gap
Record lock, heap no 167 PHYSICAL RECORD: n_fields 4; compact format;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 12295 n bits 528 index `uniqdefghijkl` of table
`deadlock`.`abcdefg` trx id 2E0E lock_mode X waiting
Record lock, heap no 167 PHYSICAL RECORD: n_fields 4; compact format;
*** WE ROLL BACK TRANSACTION (1)
===================================================
这是在三个线程的情况下是可以重现死锁的,但是为了更容易调试,试了一下在2个线程的情况下如何,最终发现重现不了。
这下坏了,多线程调试很麻烦,有时候这个走那个不走的,如果冻结某个线程,有可能导致线程之间死锁,或者自然执行,那又不能出现死锁的情况,因为这个死锁也是偶然性的,所以最终只有一种方法,那就是在mysql代码中打印log信息,将锁、记录与事务这块的函数中具有分歧点的地方都加了注释,并且将有用的信息打印出来,最终分析log文件,才发现了真正死锁的猫腻。
现在将三个导致死锁的事务的时序图画出来: