MySQL并发插入导致(显式)事务之外的死锁

标签 mysql deadlock

我正在尝试调试以下场景:有 2 个并发进程,将完全相同的行插入到具有唯一约束的表中。这是在显式事务之外完成的(尽管我假设 InnoDB 将其作为带有内部自动提交的单个语句处理?)

架构如下:

CREATE TABLE locks (
    id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    lock_uid varchar(255) NOT NULL,
    count smallint(6) NOT NULL,
    processor_id varchar(255) DEFAULT NULL,
    created_at timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
    updated_at timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
    PRIMARY KEY (id),
    UNIQUE INDEX locks_lock_uid_unique (lock_uid)
)

如您所见,lock_uid 上有唯一索引,以防止表中出现具有相同值的多行。

正在运行的命令(对于上下文,这些是从通用查询日志中获取的,以确保完整的完整性,在整理命令之外的任何一个线程上都没有其他语句):

线程 1:

insert into `locks` (`lock_uid`, `count`, `processor_id`, `created_at`, `updated_at`) 
values ('11161567', '0', NULL, '2017-11-07 10:46:36', '2017-11-07 10:46:36')

线程 2:

insert into `locks` (`lock_uid`, `count`, `processor_id`, `created_at`, `updated_at`) 
values ('11161567', '0', NULL, '2017-11-07 10:46:36', '2017-11-07 10:46:36')

这导致了以下死锁:

LATEST DETECTED DEADLOCK
------------------------
2017-11-07 10:46:36 0x2ac88f791700
*** (1) TRANSACTION:
TRANSACTION 6089510736, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 1
MySQL thread id 177584962, OS thread handle 47059008030464, query id 13109086103 ec2-34-232-58-13.compute-1.amazonaws.com 34.232.58.13 appserver update
insert into `locks` (`lock_uid`, `count`, `processor_id`, `created_at`, `updated_at`) values ('11161567', '0', NULL, '2017-11-07 10:46:36', '2017-11-07 10:46:36')
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 6403 page no 4 n bits 176 index locks_lock_uid_unique of table `core`.`locks` trx id 6089510736 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 107 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 30; hex 636865636b6f75745265636f7665727950726f636573735f313131363137; asc 111617; (total 32 bytes);
1: len 8; hex 0000000003266637; asc      &f7;;

*** (2) TRANSACTION:
TRANSACTION 6089510734, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 5 row lock(s), undo log entries 1
MySQL thread id 177584971, OS thread handle 47040888903424, query id 13109086092 ec2-34-237-3-244.compute-1.amazonaws.com 34.237.3.244 appserver update
insert into `locks` (`lock_uid`, `count`, `processor_id`, `created_at`, `updated_at`) values ('11161567', '0', NULL, '2017-11-07 10:46:36', '2017-11-07 10:46:36')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 6403 page no 4 n bits 176 index locks_lock_uid_unique of table `core`.`locks` trx id 6089510734 lock mode S
Record lock, heap no 104 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 30; hex 636865636b6f75745265636f7665727950726f636573735f313131363135; asc 111615; (total 32 bytes);
1: len 8; hex 0000000003266632; asc      &f2;;

Record lock, heap no 105 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 30; hex 636865636b6f75745265636f7665727950726f636573735f313131363135; asc 111615; (total 32 bytes);
1: len 8; hex 0000000003266634; asc      &f4;;

Record lock, heap no 107 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 30; hex 636865636b6f75745265636f7665727950726f636573735f313131363137; asc 111617; (total 32 bytes);
1: len 8; hex 0000000003266637; asc      &f7;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 6403 page no 4 n bits 176 index locks_lock_uid_unique of table `core`.`locks` trx id 6089510734 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 107 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 30; hex 636865636b6f75745265636f7665727950726f636573735f313131363137; asc 111617; (total 32 bytes);
1: len 8; hex 0000000003266637; asc      &f7;;

*** WE ROLL BACK TRANSACTION (2)

我读过类似的答案(例如 MySQL locking in Duplicate Key Error ),但我不太明白这种情况下发生了什么。解释与死锁输出不匹配。

  • 为什么这个死锁发生在事务外的 2 个插入上?
  • 为什么 T2 在请求 X 之前已经持有 S 锁,而相同的 T1 却没有?

最佳答案

为什么这个死锁发生在事务外的 2 个插入上?

还是有交易的 https://dev.mysql.com/doc/refman/5.7/en/innodb-autocommit-commit-rollback.html

In InnoDB, all user activity occurs inside a transaction. If autocommit mode is enabled, each SQL statement forms a single transaction on its own

因此您的单个查询可以被视为短期交易

为什么 T2 在请求 X 之前已经持有 S 锁,而相同的 T1 却没有?

它有。这就是打印最新死锁信息的函数的工作原理:-) 它不会打印第一个事务持有的锁。您可以通过模拟来自 2 个并行 mysql session 的简单死锁来自己检查。 这是代码:

https://github.com/mysql/mysql-server/blob/5.7/storage/innobase/lock/lock0lock.cc#L7236

DeadlockChecker::notify(const lock_t* lock) const
{
    ut_ad(lock_mutex_own());

    start_print();

    print("\n*** (1) TRANSACTION:\n");

    print(m_wait_lock->trx, 3000);

    print("*** (1) WAITING FOR THIS LOCK TO BE GRANTED:\n");

    print(m_wait_lock);

    print("*** (2) TRANSACTION:\n");

    print(lock->trx, 3000);

    print("*** (2) HOLDS THE LOCK(S):\n");

    print(lock);

    /* It is possible that the joining transaction was granted its
    lock when we rolled back some other waiting transaction. */

    if (m_start->lock.wait_lock != 0) {
        print("*** (2) WAITING FOR THIS LOCK TO BE GRANTED:\n");

        print(m_start->lock.wait_lock);
    }

    DBUG_PRINT("ib_lock", ("deadlock detected"));
}

解释与死锁输出不符

这与您的案例中似乎发生的情况非常接近。 这是一个模拟:

准备:

create table test (id int primary key, val int not null unique) engine=innodb;
insert into test values (1, 1), (2, 2), (3, 3);

现在让我们打开 3 个 shell 并运行以下命令:

1> begin;
2> begin;
3> begin;

然后

1>insert into test values (1, 1);
2>insert into test values (1, 1); (will hang)
3>insert into test values (1, 1); (will hang)

现在

1>rollback
2>would produce: ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
3>would produce: Query OK, 1 row affected

1>show engine innodb status;
...
------------------------
LATEST DETECTED DEADLOCK
------------------------
2017-11-15 23:21:47 0x700000d95000
*** (1) TRANSACTION:
TRANSACTION 2336, ACTIVE 8 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 8, OS thread handle 123145316831232, query id 58 localhost 127.0.0.1 root update
insert into test values (1, 1)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 26 page no 3 n bits 72 index PRIMARY of table `test`.`test` trx id 2336 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 2335, ACTIVE 13 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 5, OS thread handle 123145316552704, query id 57 localhost root update
insert into test values (1, 1)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 26 page no 3 n bits 72 index PRIMARY of table `test`.`test` trx id 2335 lock mode S
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 26 page no 3 n bits 72 index PRIMARY of table `test`.`test` trx id 2335 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)

如您所见,它接近您的结果

关于MySQL并发插入导致(显式)事务之外的死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47177114/

相关文章:

mysql - 将数据放入表 OneToONE 的正确方法

mysql - NodeJS + mysql : using connection pool leads to deadlock tables

c++ - asio::async_write 在大容量流上同步非常困难

sql - 如何在死锁图中捕获实际执行计划?

mysql - SQL 查询 - 内存过载

html - 连接到服务器,没有获取任何可视表

php - 使用此处示例中的一些示例中的 ISSET 代码错误

go - 一个基本的 Golang 流( channel )死锁

Postgresql -> 来自简单更新的死锁。我找不到原因

php - 需要合并来自多个 MySql 表的信息以显示在 html 表中