MySQL 5.7 - 使用 ON DUPLICATE KEY 时,回滚时死锁,但提交时不死锁

标签 mysql database innodb database-deadlocks

背景:我有一个具有许多并发线程的应用程序。在某个地方,我想更新某个数据库行,但我无法确定该行是否确实存在。因此,我需要创建该行或更新该行(如果存在)。但这样做,我遇到了我在这里提出的问题。

使用 MySQL 5.7,我遇到了一系列导致死锁的事件,但我无法真正理解原因。使用三个不同的客户端(c1c2c3),我们执行以下查询链:

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. 快。
  3. 最大限度地减少边缘情况下的虚假死锁等。

你会放弃#1吗?我对此表示怀疑。
你会放松吗#2?或许。但这并不是针对这种边缘情况的决定。

请随时在 bugs.mysql.com 上提交错误;也许有一个修复方案既不会危及#1,也不会减慢速度“太多”。

关于MySQL 5.7 - 使用 ON DUPLICATE KEY 时,回滚时死锁,但提交时不死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51649959/

相关文章:

mysql - 没有函数和过程可以做到这一点吗?

PHP Mysql 在 where 子句上使用 MD5 选择电子邮件

database - 将数据库复制到其他系统的最佳方法

mysql - 这个 INSERT 配置文件可以与我的面向 Mysql InnoDB 的数据库一起使用吗?

来自 AS 列的 MySQL SUM 和 GROUP BY

mysql - PHPMyadmin导入CSV创建新表

php - 想要添加一列并最终显示结果?

Android SQL 数据库 - 未找到 rawQuery() 源

php - Innodb引擎不支持全文索引,如何进行文本搜索?

MySQL Innodb 死锁 - 如何删除旧事件?