按日期排序时,MySQL 8.0 SKIP LOCKED返回空集

时间:2019-05-22 17:42:48

标签: mysql mysql-8.0

我试图在我的应用程序中实现表的并发更新,但发现了一种奇怪的MySQL行为。在某些情况下,看起来FOR UPDATE + LIMIT锁定整个表,而不是仅锁定那些选定的行。假设我们有下表:

CREATE TABLE `test`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `date_created` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `test` VALUES (1, '2019-05-22 19:34:28');
INSERT INTO `test` VALUES (2, '2019-05-22 19:34:46');
INSERT INTO `test` VALUES (3, '2019-05-22 19:34:54');
INSERT INTO `test` VALUES (4, '2019-05-22 19:35:01');

现在,让我们同时检索行:

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

mysql> SELECT id, date_created FROM test ORDER BY date_created DESC LIMIT 2 FOR UPDATE SKIP LOCKED;
+----+---------------------+
| id | date_created        |
+----+---------------------+
|  4 | 2019-05-22 19:35:01 |
|  3 | 2019-05-22 19:34:54 |
+----+---------------------+
2 rows in set (0.00 sec)

并发事务中的相同请求:

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

mysql> SELECT id, date_created FROM test ORDER BY date_created DESC LIMIT 2 FOR UPDATE SKIP LOCKED;
Empty set (0.00 sec)

因此,第二个并发事务无法检索ID为1和2的行。在另一种情况下,当我按“ id”字段而不是“ date_created”对行进行排序时,它的工作原理与我预期的一样:

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

mysql> SELECT id, date_created FROM test ORDER BY id DESC LIMIT 2 FOR UPDATE SKIP LOCKED;
+----+---------------------+
| id | date_created        |
+----+---------------------+
|  4 | 2019-05-22 19:35:01 |
|  3 | 2019-05-22 19:34:54 |
+----+---------------------+
2 rows in set (0.00 sec)

并发事务:

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

mysql> SELECT id, date_created FROM test ORDER BY id DESC LIMIT 2 FOR UPDATE SKIP LOCKED;
+----+---------------------+
| id | date_created        |
+----+---------------------+
|  2 | 2019-05-22 19:34:46 |
|  1 | 2019-05-22 19:34:28 |
+----+---------------------+
2 rows in set (0.00 sec)

这是一个错误吗?有什么解决方法吗?

MySQL版本:8.0.16 MySQL Community Server-GPL

1 个答案:

答案 0 :(得分:1)

这是预期的行为。问题是date_created列上没有索引,因此按date_created排序以获得两行最终会锁定所有记录。如果您从执行SELECT ... FOR UPDATE SKIP LOCKED的同一连接中查询performance_schema.data_locks表,就会看到此信息:

mysql> SELECT OBJECT_SCHEMA AS 'Schema',
              OBJECT_NAME AS 'Table', INDEX_NAME AS 'Index',
              LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA
         FROM performance_schema.data_locks
         WHERE THREAD_ID = PS_CURRENT_THREAD_ID();
+--------+-------+---------+-----------+-----------+-------------+------------------------+
| Schema | Table | Index   | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA              |
+--------+-------+---------+-----------+-----------+-------------+------------------------+
| db1    | test  | NULL    | TABLE     | IX        | GRANTED     | NULL                   |
| db1    | test  | PRIMARY | RECORD    | X         | GRANTED     | supremum pseudo-record |
| db1    | test  | PRIMARY | RECORD    | X         | GRANTED     | 1                      |
| db1    | test  | PRIMARY | RECORD    | X         | GRANTED     | 2                      |
| db1    | test  | PRIMARY | RECORD    | X         | GRANTED     | 3                      |
| db1    | test  | PRIMARY | RECORD    | X         | GRANTED     | 4                      |
+--------+-------+---------+-----------+-----------+-------------+------------------------+
6 rows in set (0.00 sec)

这也是为什么当您通过主键排序时它起作用的原因:在这种情况下,只有所需的行将被访问并因此被锁定。

使其也适用于date_created的解决方案是在该列上添加索引:

ALTER TABLE test ADD INDEX (date_created);