-
一【场景】
之前系统在运行过程中,老是报一个诡异的死锁检测异常: Error Code: 1213
Deadlock found when trying to get lock; try restartingtransaction。最后仔细研究了一下终于解决了。场景模拟如下:
数据库中2张表:用户表:users,和订单表orders。用户表里面有个字段total用来累计每个用户的订单消费总额,同时orders通过字段user_id与users表做了外键关联。
表users:
CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(16) NOT NULL DEFAULT '' COMMENT '会员名', `total` decimal(11,2) NOT NULL DEFAULT '0.00' COMMENT '消费总额', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
表orders:
CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `amount` decimal(11,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额', PRIMARY KEY (`id`), KEY `USER_ID` (`user_id`), CONSTRAINT `USER_ID` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8
假设事务A与事务B按照以下序列进行,就会产生死锁,从而引发数据库的死锁检测异常,MySQL就会选择影响行数较少的事务进行回滚:https://dev.mysql.com/doc/refman/5.5/en/innodb-deadlock-detection.html;
序列
事务A
事务B
1
BEGIN;
INSERT INTO orders (user_id, amount) VALUES (1, 10.00);
2
BEGIN;
INSERT INTO orders (user_id, amount) VALUES (1, 25.00);
3
UPDATE users SET total=total+10.00;(发送阻塞)
4
UPDATE users SET total=total+25.00;(产生死锁)
二【分析】
先看死锁日志:
根据2个事务的WAITING FOR THIS LOCK TO BE GRANTED信息可以看出事务A(D68)和事务B(D69)同时在等着给user表中的同一行加X锁,同时D69事务已经获取了这一行的S锁。那么,这儿的问题是这个共享锁是怎么加上的?
后来查看了Mysq的官方文档:https://dev.mysql.com/doc/refman/5.6/en/innodb-foreign-key-constraints.html
就是如果存在外键约束,那么会给这张表的外键关联的表相应的行加上共享锁。那么在我们的这个场景下,就是当insert 2个订单数据的时候,MySQL已经给user表中tom那一行加上了2把共享锁,所以当后面再想着更新tom会员信息的时候,2个事务都在等着对方释放各自的共享锁,于是就产生了死锁。
三【解决】
就目前的这个场景,当然是先更新user表,再插入orders表数据就行了。这样就把user表的S锁直接替换成了X锁,破坏了请求和保持的必要条件,预防了死锁的发生。
不过,现在互联网企业为了方便分表,分库,数据迁移等,已经越来越少的去建立表的外键约束,而是靠上层应用自己去保证了。
一个数据库死锁问题
最新推荐文章于 2024-07-19 23:31:39 发布