项目经常会出现mysql的死锁问题,当年年少总是想通过SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; 查看被锁的事务,然后kill掉他,或者重启mysql,唉,治标不治本啊,下次还会出现这些问题,其实造成死锁大多数情况就是我们的sql写的不大好。
我们先来模拟一组死锁,然后通过查看死锁日志再反向推出死锁的原因。
首先我们新建一个表:
CREATE TABLE `t1` (
`a` int(11) NOT NULL DEFAULT '0',
`b` varchar(20) DEFAULT NULL,
`c` varchar(100) DEFAULT NULL,
PRIMARY KEY (`a`),
KEY `a_mes` (`b`) USING HASH
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
这里引擎用InnoDB,并且在b字段建了一个索引‘a_mes’,先在表里加几行数据:
INSERT INTO `test`.`t1` (`a`, `b`, `c`) VALUES ('1', 'aaa', NULL);
INSERT INTO `test`.`t1` (`a`, `b`, `c`) VALUES ('2', 'asd', NULL);
INSERT INTO `test`.`t1` (`a`, `b`, `c`) VALUES ('3', 'axd', NULL);
INSERT INTO `test`.`t1` (`a`, `b`, `c`) VALUES ('4', 'bbb', NULL);
然后我这里的事务级别是RR,可以查看下,select @@tx_isolation;
这里有个姿势点,事务级别是RR的时候,当进行IUD操作的时候,如果指定的是非唯一索引的那个字段,RR级别下,会加GAP锁,比如我这里b字段上有非唯一索引,当我执行语句DELETE FROM t1 WHERE b='aaa';的时候就会加GAP锁,也就是相当于在0-aaa,aaa-asd的范围加上了GAP锁,同时aaa也会加X锁。所以接下来模拟两个事务:
事务一:set autocommit=0;
1.DELETE FROM t1 WHERE b='aaa';
2.INSERT INTO t1 VALUES(19,'baa','1');
事务二:set autocommit=0;
1.DELETE FROM t1 WHERE b='bbb';
2.INSERT INTO t1 VALUES(16,'aab','1');
当事务一执行第1条语句时,正如前面所说aaa索引加了X锁,并且在aaa范围内加了GAP锁,这时事务二执行弟1条语句,同理bbb索引也会加了X锁,并且在axd-bbb,bbb-无穷大范围加了GAP锁,然后事务一继续执行第二条语句,这个时候由于事务二加了锁,事务一就等待锁,事务二执行弟2条语句后,就报错了,这个就模拟了一组死锁。
然后我们查看死锁日志:show engine innodb status \G
通过日志我们看到事务一在执行INSERT INTO t1 VALUES(19,'baa','1');这句的时候,他要去获得索引‘a_mes’上的X锁,但是获得不到,所以在waiting。。。而事务二,在索引a_mes上持有X锁,并且事务二在执行INSERT INTO t1 VALUES(16,'aab','1');这句的时候也是获得不到锁,然后处于waiting状态。。。
会查看死锁日志,懂得mysql的加锁机制,去反推业务的逻辑,去查找真正造成死锁的原因,然后解决他,如果不了解GAP锁推荐这篇文章,写的很好http://hedengcheng.com/?p=771#_Toc374698322