复合唯一索引的mysql锁是如何工作的?

问题描述 投票:0回答:0

我已经阅读了有关各种锁的mysql文档,但我不清楚为什么我在特定情况下会遇到锁和死锁。

我们有一个具有唯一复合索引的连接表:

mysql> SHOW CREATE TABLE workflow_run_assigned_users \G
*************************** 1. row ***************************
       Table: workflow_run_assigned_users
Create Table: CREATE TABLE `workflow_run_assigned_users` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `workflow_run_id` int DEFAULT NULL,
  `user_id` int DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_workflow_run_assigned_users_on_user_id_and_workflow_run_id` (`user_id`,`workflow_run_id`),
  KEY `index_workflow_run_assigned_users_on_workflow_run_id` (`workflow_run_id`),
  KEY `index_workflow_run_assigned_users_on_user_id` (`user_id`),
  CONSTRAINT `fk_rails_77cc0a4937` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_rails_9202262959` FOREIGN KEY (`workflow_run_id`) REFERENCES `workflow_runs` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=117 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.01 sec)

我们有一个

job
对于某些事件重新计算哪些
users
与特定的
workflow_run
相关联并将该数据写入事务中,例如:

BEGIN;

# delete previous data
DELETE FROM workflow_run_assigned_users WHERE id = XXX;

# insert new data
INSERT INTO `workflow_run_assigned_users` (`workflow_run_id`,`user_id`,`created_at`,`updated_at`) VALUES (A, B, C, D) ON DUPLICATE KEY UPDATE `workflow_run_id`=`workflow_run_id`;

COMMIT;

这些作业是并发的,我们开始遇到死锁,即使不同的事务处理包含不冲突数据的记录(

user_id
workflow_run_id
)。这是我在并行控制台中成功重现的示例:

mysql1>  select * from workflow_run_assigned_users where workflow_run_id = 6310;
+-----+-----------------+---------+----------------------------+----------------------------+
| id  | workflow_run_id | user_id | created_at                 | updated_at                 |
+-----+-----------------+---------+----------------------------+----------------------------+
| 111 |            6310 |    2026 | 2023-04-23 10:26:54.343125 | 2023-04-23 10:26:54.343125 |
| 112 |            6310 |    2025 | 2023-04-23 10:26:54.343125 | 2023-04-23 10:26:54.343125 |
+-----+-----------------+---------+----------------------------+----------------------------+
2 rows in set (0.00 sec)

mysql1> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql1> DELETE FROM workflow_run_assigned_users WHERE id = 111;
Query OK, 1 row affected (0.00 sec)

然后在第二个客户端:

mysql2> select * from workflow_run_assigned_users where workflow_run_id = 10570;
+-----+-----------------+---------+----------------------------+----------------------------+
| id  | workflow_run_id | user_id | created_at                 | updated_at                 |
+-----+-----------------+---------+----------------------------+----------------------------+
| 113 |           10570 |    2025 | 2023-04-23 10:26:59.773823 | 2023-04-23 10:26:59.773823 |
+-----+-----------------+---------+----------------------------+----------------------------+
1 row in set (0.00 sec)

mysql2> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql2> DELETE FROM workflow_run_assigned_users WHERE id = 113;                                                                                                                                            
Query OK, 1 row affected (0.01 sec)

mysql2> INSERT INTO `workflow_run_assigned_users` (`workflow_run_id`,`user_id`,`created_at`,`updated_at`) VALUES (10570, 2025, '2023-04-16 10:27:21.227936', '2023-04-16 10:27:21.227936') ON DUPLICATE KEY UPDATE `workflow_run_id`=`workflow_run_id`;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

这是我们在第三个控制台中看到的锁,而第二个控制台等待锁:

mysql> SELECT * FROM performance_schema.data_locks WHERE object_name = 'workflow_run_assigned_users' \G
*************************** 1. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140693718736800:774749:140693935777056
ENGINE_TRANSACTION_ID: 21941932
            THREAD_ID: 1094
             EVENT_ID: 294
        OBJECT_SCHEMA: tap-qa
          OBJECT_NAME: workflow_run_assigned_users
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140693935777056
            LOCK_TYPE: TABLE
            LOCK_MODE: IX
          LOCK_STATUS: GRANTED
            LOCK_DATA: NULL
*************************** 2. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140693718736800:772939:4:16:140693943223840
ENGINE_TRANSACTION_ID: 21941932
            THREAD_ID: 1094
             EVENT_ID: 294
        OBJECT_SCHEMA: tap-qa
          OBJECT_NAME: workflow_run_assigned_users
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140693943223840
            LOCK_TYPE: RECORD
            LOCK_MODE: X,REC_NOT_GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 113
*************************** 3. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140693718736800:772939:7:8:140693943225216
ENGINE_TRANSACTION_ID: 21941932
            THREAD_ID: 1094
             EVENT_ID: 295
        OBJECT_SCHEMA: tap-qa
          OBJECT_NAME: workflow_run_assigned_users
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: index_workflow_run_assigned_users_on_user_id_and_workflow_run_id
OBJECT_INSTANCE_BEGIN: 140693943225216
            LOCK_TYPE: RECORD
            LOCK_MODE: X,REC_NOT_GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 2025, 10570, 113
*************************** 4. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140693718736800:772939:7:8:140693943225560
ENGINE_TRANSACTION_ID: 21941932
            THREAD_ID: 1094
             EVENT_ID: 295
        OBJECT_SCHEMA: tap-qa
          OBJECT_NAME: workflow_run_assigned_users
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: index_workflow_run_assigned_users_on_user_id_and_workflow_run_id
OBJECT_INSTANCE_BEGIN: 140693943225560
            LOCK_TYPE: RECORD
            LOCK_MODE: X,GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 2025, 10570, 113
*************************** 5. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140693718736800:772939:7:11:140693943225904
ENGINE_TRANSACTION_ID: 21941932
            THREAD_ID: 1094
             EVENT_ID: 295
        OBJECT_SCHEMA: tap-qa
          OBJECT_NAME: workflow_run_assigned_users
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: index_workflow_run_assigned_users_on_user_id_and_workflow_run_id
OBJECT_INSTANCE_BEGIN: 140693943225904
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: WAITING
            LOCK_DATA: 2026, 6310, 111
*************************** 6. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140693718736008:774749:140693935776032
ENGINE_TRANSACTION_ID: 21941925
            THREAD_ID: 1093
             EVENT_ID: 288
        OBJECT_SCHEMA: tap-qa
          OBJECT_NAME: workflow_run_assigned_users
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140693935776032
            LOCK_TYPE: TABLE
            LOCK_MODE: IX
          LOCK_STATUS: GRANTED
            LOCK_DATA: NULL
*************************** 7. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140693718736008:772939:4:13:140693943219232
ENGINE_TRANSACTION_ID: 21941925
            THREAD_ID: 1093
             EVENT_ID: 288
        OBJECT_SCHEMA: tap-qa
          OBJECT_NAME: workflow_run_assigned_users
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140693943219232
            LOCK_TYPE: RECORD
            LOCK_MODE: X,REC_NOT_GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 111
*************************** 8. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140693718736008:772939:7:11:140693943219576
ENGINE_TRANSACTION_ID: 21941925
            THREAD_ID: 1094
             EVENT_ID: 290
        OBJECT_SCHEMA: tap-qa
          OBJECT_NAME: workflow_run_assigned_users
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: index_workflow_run_assigned_users_on_user_id_and_workflow_run_id
OBJECT_INSTANCE_BEGIN: 140693943219576
            LOCK_TYPE: RECORD
            LOCK_MODE: X,REC_NOT_GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 2026, 6310, 111
8 rows in set (0.00 sec)

我如何阅读这个输出:

# first transaction holds exclusive lock on index record (2026, 6310, 111)
*************************** 8. row ***************************
ENGINE_TRANSACTION_ID: 21941925
          OBJECT_NAME: workflow_run_assigned_users
           INDEX_NAME: index_workflow_run_assigned_users_on_user_id_and_workflow_run_id
OBJECT_INSTANCE_BEGIN: 140693943219576
            LOCK_TYPE: RECORD
            LOCK_MODE: X,REC_NOT_GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 2026, 6310, 111

# while second is waiting in attempt to hold the same index record, even if it tried to insert totally different dat (10570, 2025, ...)
*************************** 5. row ***************************
ENGINE_TRANSACTION_ID: 21941932
          OBJECT_NAME: workflow_run_assigned_users
           INDEX_NAME: index_workflow_run_assigned_users_on_user_id_and_workflow_run_id
OBJECT_INSTANCE_BEGIN: 140693943225904
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: WAITING
            LOCK_DATA: 2026, 6310, 111

# there is another entry about GAP lock that first transaction holds, but I'm not sure it is matters here
ENGINE_TRANSACTION_ID: 21941932
          OBJECT_NAME: workflow_run_assigned_users
           INDEX_NAME: index_workflow_run_assigned_users_on_user_id_and_workflow_run_id
            LOCK_TYPE: RECORD
            LOCK_MODE: X,GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 2025, 10570, 113

所以我的问题:

  1. 是 GAP 还是 Next-key lock?
  2. 看起来即使唯一索引是复合的,也只有第一列很重要(在这种情况下
    user_id
    )。当我将唯一索引中的列顺序从(
    user_id
    workflow_run_id
    )交换为(
    workflow_run_id
    user_id
    )时,此测试用例中没有锁。
  3. 另请注意,第二个会话首先删除该行,然后插入与包含的已删除行相同的数据。为什么DELETION的时候没有冲突,INSERT的时候却得到了?

Mysql版本为8.0.32

mysql locks unique-index
© www.soinside.com 2019 - 2024. All rights reserved.