【问题标题】:Why does select for update locked almost the table entirely?为什么 select for update 几乎完全锁定了表?
【发布时间】:2021-07-27 06:16:09
【问题描述】:

我已经学习了一段时间的 MySQL 和 InnoDB,但是这个 SQL 和它的锁定仍然让我感到困惑。

当我在repeatable read 时,使用一个小表,就像 MySQL doc 提供的一样。


mysql> desc child;
+-------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------+------+-----+---------+-------+
| id    | int  | NO   | PRI | NULL    |       |
+-------+------+------+-----+---------+-------+
1 row in set (0.01 sec)

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from child;
+-----+
| id  |
+-----+
|  89 |
|  90 |
| 102 |
| 151 |
+-----+
4 rows in set (0.00 sec)

在这种情况下,我开始一个事务并键入一个 SQL,输入但尚未提交会话。

然后,我使用performance_schema.data_locks 查询锁。

如您所见,此时此表中有6把锁。

  • 表 IX
  • 下一键锁定(151,正无穷大)
  • 下一键锁 (89, 90]
  • 下一键锁 (90, 102]
  • 下一键锁 (102, 151]
  • Next-Key Lock(负无穷大,89]

我们注意到这张表只有4条记录,而且这张表也有5个Next-Key Locks,因此几乎整个表都被锁定了。

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM child WHERE id >= 102 for update;
+-----+
| id  |
+-----+
| 102 |
| 151 |
+-----+
2 rows in set (0.00 sec)

mysql> select * from performance_schema.data_locks\G
*************************** 1. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140616303284368:1158:140616462870928
ENGINE_TRANSACTION_ID: 4592
            THREAD_ID: 80
             EVENT_ID: 14
        OBJECT_SCHEMA: xhinliang_test
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140616462870928
            LOCK_TYPE: TABLE
            LOCK_MODE: IX
          LOCK_STATUS: GRANTED
            LOCK_DATA: NULL // IX of table
*************************** 2. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140616303284368:3:4:1:140616474499104
ENGINE_TRANSACTION_ID: 4592
            THREAD_ID: 80
             EVENT_ID: 14
        OBJECT_SCHEMA: xhinliang_test
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140616474499104
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: supremum pseudo-record // Next-Key Lock (151, positive infinity)
*************************** 3. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140616303284368:3:4:2:140616474499104
ENGINE_TRANSACTION_ID: 4592
            THREAD_ID: 80
             EVENT_ID: 14
        OBJECT_SCHEMA: xhinliang_test
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140616474499104
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: 90 // Next-Key Lock (89, 90]
*************************** 4. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140616303284368:3:4:3:140616474499104
ENGINE_TRANSACTION_ID: 4592
            THREAD_ID: 80
             EVENT_ID: 14
        OBJECT_SCHEMA: xhinliang_test
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140616474499104
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: 102 // Next-Key Lock (90, 102]
*************************** 5. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140616303284368:3:4:4:140616474499104
ENGINE_TRANSACTION_ID: 4592
            THREAD_ID: 80
             EVENT_ID: 14
        OBJECT_SCHEMA: xhinliang_test
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140616474499104
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: 151 // Next-Key Lock (102, 151]
*************************** 6. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140616303284368:3:4:5:140616474499104
ENGINE_TRANSACTION_ID: 4592
            THREAD_ID: 80
             EVENT_ID: 14
        OBJECT_SCHEMA: xhinliang_test
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140616474499104
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: 89 // Next-Key Lock (negative infinity, 89]

我同时开始另一个会话,尝试插入一些行,但都失败了。

mysql> insert into child (id) values (88);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into child (id) values (88);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into child (id) values (91);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into child (id) values (100);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into child (id) values (103);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into child (id) values (152);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted

在我看来,应该是 3 个 Next-Key 锁,但在这种情况下不是 5 个。

因为 InnoDB 的文档说

InnoDB 使用间隙锁或 next-key 锁定扫描的索引范围

所以 (89, 90] 的 Next-Key Lock 应该不会出现,并且 (90, 102] 的 Next-Key Lock 应该替换为 id 102 的 Record Lock。

看看这个 SQL 和响应,有人能告诉我发生了什么吗?

【问题讨论】:

  • “有人能告诉我发生了什么吗?”,您回答了自己的问题:“几乎整张桌子都被锁定了”
  • 为什么“几乎整个表都被锁定了”,这是我的问题。
  • 这是因为您的表中只有 4 条记录。尝试使用更多记录进行测试,您会看到,当只选择 2 条记录时,您将只有 2 个(记录)锁。

标签: mysql innodb


【解决方案1】:

感谢@Luuk,我插入了更多行并重试。

这一次我得到了我应该得到的回应。

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from child;
+-----+
| id  |
+-----+
|  89 |
|  90 |
| 102 |
| 151 |
| 388 |
| 390 |
| 452 |
| 882 |
+-----+
8 rows in set (0.00 sec)

mysql> SELECT * FROM child WHERE id >= 388 for update;
+-----+
| id  |
+-----+
| 388 |
| 390 |
| 452 |
| 882 |
+-----+
4 rows in set (0.00 sec)

mysql> select * from performance_schema.data_locks\G
*************************** 1. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140616303284368:1158:140616462870928
ENGINE_TRANSACTION_ID: 4609
            THREAD_ID: 80
             EVENT_ID: 36
        OBJECT_SCHEMA: xhinliang_test
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140616462870928
            LOCK_TYPE: TABLE
            LOCK_MODE: IX
          LOCK_STATUS: GRANTED
            LOCK_DATA: NULL // IX for table
*************************** 2. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140616303284368:3:4:6:140616474499104
ENGINE_TRANSACTION_ID: 4609
            THREAD_ID: 80
             EVENT_ID: 36
        OBJECT_SCHEMA: xhinliang_test
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140616474499104
            LOCK_TYPE: RECORD
            LOCK_MODE: X,REC_NOT_GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 388 // Record Lock for 388
*************************** 3. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140616303284368:3:4:1:140616474499448
ENGINE_TRANSACTION_ID: 4609
            THREAD_ID: 80
             EVENT_ID: 36
        OBJECT_SCHEMA: xhinliang_test
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140616474499448
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: supremum pseudo-record // Next-Key Lock for (882, +inf)
*************************** 4. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140616303284368:3:4:7:140616474499448
ENGINE_TRANSACTION_ID: 4609
            THREAD_ID: 80
             EVENT_ID: 36
        OBJECT_SCHEMA: xhinliang_test
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140616474499448
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: 390 // Next-Key Lock for (388, 390]
*************************** 5. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140616303284368:3:4:8:140616474499448
ENGINE_TRANSACTION_ID: 4609
            THREAD_ID: 80
             EVENT_ID: 36
        OBJECT_SCHEMA: xhinliang_test
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140616474499448
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: 452 // Next-Key Lock for (390, 452]
*************************** 6. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140616303284368:3:4:9:140616474499448
ENGINE_TRANSACTION_ID: 4609
            THREAD_ID: 80
             EVENT_ID: 36
        OBJECT_SCHEMA: xhinliang_test
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140616474499448
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: 882 // Next-Key Lock for (452, 882]
6 rows in set (0.00 sec)

在这种情况下,我们可以看到我们想看到的锁。

  • 表 IX
  • 388 的记录锁定
  • (882,+inf)的下一键锁定
  • (388、390] 的下一键锁定
  • (390、452] 的下一键锁定
  • (452, 882] 的下一键锁定

【讨论】:

    【解决方案2】:

    这是执行 SELECT FOR UPDATE 时的正常行为。

    您可以在此处查看官方文档: https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html

    【讨论】:

    • 不,我不希望插入 88 会被阻止。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-29
    • 1970-01-01
    • 2015-03-08
    • 2023-04-08
    • 2014-03-10
    • 1970-01-01
    相关资源
    最近更新 更多