mysql - InnoDB 在外键上使用 where 子句时锁定不同的索引

标签 mysql transactions locking mariadb innodb

我正在两个不同的事务中尝试这些查询:

START TRANSACTION;
DELETE FROM `StopHistory` WHERE `deviceId` = 34;

START TRANSACTION;
SELECT id FROM `StopHistory` WHERE deviceId = 33 AND endAt > '2018-06-18 17:01:32.473';

第二个查询等待,因为第一个查询正在 endAt 字段上创建锁。

MySQL [(none)]> select * from INFORMATION_SCHEMA.INNODB_LOCKS;
+-----------------------+-------------+-----------+-----------+--------------------------+------------+------------+-----------+----------+--------------------------+
| lock_id               | lock_trx_id | lock_mode | lock_type | lock_table               | lock_index | lock_space | lock_page | lock_rec | lock_data                |
+-----------------------+-------------+-----------+-----------+--------------------------+------------+------------+-----------+----------+--------------------------+
| 104222007:699:1035:16 | 104222007   | S         | RECORD    | `db`.`StopHistory`       | endAt      |        641 |      1035 |       16 | 0x99A026D21307DA, 115347 |
| 104221958:699:1035:16 | 104221958   | X         | RECORD    | `db`.`StopHistory`       | endAt      |        641 |      1035 |       16 | 0x99A026D21307DA, 115347 |
+-----------------------+-------------+-----------+-----------+--------------------------+------------+------------+-----------+----------+--------------------------+

    ---TRANSACTION 104231466, ACTIVE 3 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 376, 3 row lock(s)
MySQL thread id 59556, OS thread handle 0x2b1375f03700, query id 20584248 172.31.2.181 db Sending data
SELECT id FROM `StopHistory` WHERE deviceId = 33 AND endAt > '2018-06-18 17:01:32.473'
------- TRX HAS BEEN WAITING 3 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 641 page no 1035 n bits 16 index `endAt` of table `db`.`StopHistory` trx id 104231466 lock mode S waiting
Record lock, heap no 16 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len=7; bufptr=0x2b134db1c16c; hex= 99a026d21307da; asc   &    ;;
 1: len=4; bufptr=0x2b134db1c173; hex= 8001c293; asc     ;;

------------------
---TRANSACTION 104231429, ACTIVE 11 sec
44 lock struct(s), heap size 376, 3103 row lock(s), undo log entries 1549
MySQL thread id 59555, OS thread handle 0x2b1375f03700, query id 20584210 172.31.2.181 db delayed send ok done

StopHistory 表在 deviceId 上有一个外键,在 endAt 上有一个索引

CREATE TABLE `StopHistory` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `endAt` datetime(3) DEFAULT NULL,
  `deviceId` int(11) DEFAULT NULL,
  `beginAt` datetime(3) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_StopHystory_2_idx` (`deviceId`),
  KEY `beginAt` (`beginAt`),
  KEY `endAt` (`endAt`),
  KEY `device_beginAt` (`deviceId`,`beginAt`),
  CONSTRAINT `fk_StopHystory_2` FOREIGN KEY (`deviceId`) REFERENCES `Devices` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=115351 DEFAULT CHARSET=utf8;

解释的输出:

MySQL [db]> explain DELETE FROM `StopHistory` WHERE `deviceId` = 34 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: StopHistory
         type: range
possible_keys: PRIMARY,fk_StopHystory_1_idx,fk_StopHystory_2_idx,fk_StopHistory_1_idx,beginAt,endAt,device_beginAt
          key: fk_StopHystory_2_idx
      key_len: 5
          ref: NULL
         rows: 1548
        Extra: Using where

为什么 endAt 字段被锁定?

最佳答案

为了帮助SELECT,并可能减少锁定的机会,请更改

KEY `fk_StopHystory_2_idx` (`deviceId`),

KEY(deviceId, endAt)

另外,请注意,34 紧接在 33 之后。因此,删除所有 34 相当于触及 33 之后的“间隙”。这可能是锁争用不可避免的原因。

另请注意,DELETE 需要从 3 个 BTree 中删除项目——主树,以及以 deviceId 开头的两个索引。想必后两者都会经过“Change Buffer”,不会太挑剔。

关于mysql - InnoDB 在外键上使用 where 子句时锁定不同的索引,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50932171/

相关文章:

c - 你知道支持 COW 事务的 C 字典吗?

mysql - 如何在 MySQL 中的一个原子操作中重命名两个表

filesystems - 文件系统如何处理并发读/写?

mysql - Laravel 悲观锁定无法阻止多个重复请求中未提交行上的 SELECT

mysql - 有没有办法用一个查询来做到这一点?

mysql - 跨两个表进行选择 - 不确定是加入还是合并?

php - 更大、更少或者更小、更多的查询?

Mysql 使用 Perl DBI 跨两个表删除重复行

java - Spring REST Controller ,在事务内部反序列化

java - 如果并发运行的进程中不存在文件,如何在不使用锁定的情况下安全地创建文件?