背景:我有一个具有许多并发线程的应用程序。在某个地方,我想更新某个数据库行,但我无法确定该行是否确实存在。因此,我需要创建该行或更新该行(如果存在)。但这样做,我遇到了我在这里提出的问题。
使用 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 differs from a simple INSERT in that an exclusive lock rather than a shared lock is placed on the row to be updated when a duplicate-key error occurs. An exclusive index-record lock is taken for a duplicate primary key value. An exclusive next-key lock is taken for a duplicate unique key value.
我想做的事情感觉像是一个非常基本的操作:如果行不存在则创建它,如果已经存在则更新它。怎么会不行呢?这里到底发生了什么?为什么我在回滚时遇到死锁,而不是在提交时遇到死锁?
最佳答案
InnoDB 目标
- 数据的完整性。
- 快。
- 最大限度地减少边缘情况下的虚假死锁等。
你会放弃#1吗?我对此表示怀疑。
你会放松吗#2?或许。但这并不是针对这种边缘情况的决定。
请随时在 bugs.mysql.com 上提交错误;也许有一个修复方案既不会危及#1,也不会减慢速度“太多”。
关于MySQL 5.7 - 使用 ON DUPLICATE KEY 时,回滚时死锁,但提交时不死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51649959/