【问题标题】:MySQL 5.7 - Deadlock on rollback, but not on commit, when using ON DUPLICATE KEYMySQL 5.7 - 使用 ON DUPLICATE KEY 时,回滚死锁,但不是提交死锁
【发布时间】:2018-08-02 09:19:35
【问题描述】:

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

使用 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 不同于简单的 INSERT in 排他锁而不是共享锁被放置在行上 发生重复键错误时进行更新。独家 索引记录锁定用于重复的主键值。一个 对重复的唯一键值采用独占 next-key 锁。

我正在尝试做的是一个非常基本的操作:如果行不存在则创建它,如果它已经存在则更新它。怎么可能行不通?这里到底发生了什么?为什么我会在回滚而不是提交时收到死锁?

【问题讨论】:

  • 附加锁仅在发生重复键错误时发挥作用。该错误实际上并没有发生,因为死锁发生在insert-part - 与您链接的问题完全相同。不确定您是否接受仅插入的情况会发生死锁,但如果您接受,那可能是难题的缺失部分。
  • 我认为这两个程序之间存在某种差异,因为如果您执行链接问题中的查询系列,则事务 2 持有的锁是 S 锁,而事务持有的锁这个问题中的 2 是 X 锁。
  • 但可能还是如你所说,错误问题已经出现在插入时,锁类型是一个小细节。如果是这样的话,我接受与否都无关紧要,嗯? :)

标签: mysql database innodb database-deadlocks


【解决方案1】:

InnoDB 目标

  1. 数据的完整性。
  2. 快。
  3. 尽量减少边缘情况下的虚假死锁等。

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

随时在 bugs.mysql.com 提交错误;也许有一个不会危及 #1 的修复程序,也不会“过分”减慢速度。

【讨论】:

    猜你喜欢
    • 2022-11-03
    • 2018-07-27
    • 2021-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-27
    • 2015-08-04
    相关资源
    最近更新 更多