一个数据库死锁问题

一【场景

之前系统在运行过程中,老是报一个诡异的死锁检测异常: 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锁,破坏了请求和保持的必要条件,预防了死锁的发生。

不过,现在互联网企业为了方便分表,分库,数据迁移等,已经越来越少的去建立表的外键约束,而是靠上层应用自己去保证了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章