【发布时间】:2018-08-02 09:19:35
【问题描述】:
背景:我有一个有很多并发线程的应用程序。在某个地方,我想更新某个数据库行,但我不能确定该行是否确实存在。因此,我需要创建行或更新行(如果存在)。但是这样做,我遇到了我在这里提出的问题。
使用 MySQL 5.7,我遇到了导致死锁的一系列事件,但我无法真正理解为什么。使用三个不同的客户端(c1、c2 和 c3),我们执行以下查询链:
c1 > BEGIN;
c1 > INSERT INTO `user_points` (userid, points) VALUES (1,1)
ON DUPLICATE KEY UPDATE points = points + 1;
c2 > INSERT INTO `user_points` (userid, points) VALUES (1,1)
ON DUPLICATE KEY UPDATE points = points + 1;
c3 > INSERT INTO `user_points` (userid, points) VALUES (1,1)
ON DUPLICATE KEY UPDATE points = points + 1;
c1 > ROLLBACK;
这一系列查询将导致c3 收到一个死锁错误,而c2 执行其查询没有错误。 但是,如果c1 的最后一个查询是提交(而不是回滚),那么这个系列就可以正常工作。
这是我使用的表格:
CREATE TABLE `points` (
`userid` int(11) unsigned NOT NULL,
`points` int(11) unsigned NOT NULL,
PRIMARY KEY (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
这是 InnoDB 引擎报告的死锁:
------------------------
LATEST DETECTED DEADLOCK
------------------------
180802 8:48:02
*** (1) TRANSACTION:
TRANSACTION 1D69A6, ACTIVE 3 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1248, 2 row lock(s)
MySQL thread id 135, OS thread handle 0x7f3c9829b700, query id 12166 172.30.0.1 root update
INSERT INTO points (userid, points) VALUES (1,0) ON DUPLICATE KEY UPDATE points = points + 1
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 55819 n bits 72 index `PRIMARY` of table `temp`.`points` trx id 1D69A6 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (2) TRANSACTION:
TRANSACTION 1D69A7, ACTIVE 1 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1248, 2 row lock(s)
MySQL thread id 137, OS thread handle 0x7f3c98239700, query id 12167 172.30.0.1 root update
INSERT INTO points (userid, points) VALUES (1,0) ON DUPLICATE KEY UPDATE points = points + 1
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 55819 n bits 72 index `PRIMARY` of table `temp`.`points` trx id 1D69A7 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 55819 n bits 72 index `PRIMARY` of table `temp`.`points` trx id 1D69A7 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** WE ROLL BACK TRANSACTION (2)
我搜索了一下,发现了一个与我类似的问题:Why commit does not cause deadlock。但是不会使用ON DUPLICATE KEY 来规避该问题中描述的问题吗?从MySQL reference page,我读到以下内容:
INSERT ... ON DUPLICATE KEY UPDATE 不同于简单的 INSERT in 排他锁而不是共享锁被放置在行上 发生重复键错误时进行更新。独家 索引记录锁定用于重复的主键值。一个 对重复的唯一键值采用独占 next-key 锁。
我正在尝试做的是一个非常基本的操作:如果行不存在则创建它,如果它已经存在则更新它。怎么可能行不通?这里到底发生了什么?为什么我会在回滚而不是提交时收到死锁?
【问题讨论】:
-
附加锁仅在发生重复键错误时发挥作用。该错误实际上并没有发生,因为死锁发生在
insert-part - 与您链接的问题完全相同。不确定您是否接受仅插入的情况会发生死锁,但如果您接受,那可能是难题的缺失部分。 -
我认为这两个程序之间存在某种差异,因为如果您执行链接问题中的查询系列,则事务 2 持有的锁是 S 锁,而事务持有的锁这个问题中的 2 是 X 锁。
-
但可能还是如你所说,错误问题已经出现在插入时,锁类型是一个小细节。如果是这样的话,我接受与否都无关紧要,嗯? :)
标签: mysql database innodb database-deadlocks