mysql - 为什么 MySQL 显式谓词锁定不允许在谓词锁之外插入语句

标签 mysql concurrency transactions locking

假设我们有以下数据库表:

create table department (
    id bigint not null, 
    budget bigint not null, 
    name varchar(255), 
    primary key (id)
) ENGINE=InnoDB

create table employee (
    id bigint not null, 
    name varchar(255), 
    salary bigint not null, 
    department_id bigint, primary key (id)
) ENGINE=InnoDB

alter table employee 
add constraint FK_department_id 
foreign key (department_id) 
references department (id)

我们有 2 个部门:

insert into department (name, budget, id) 
values ('Hypersistence', 100000, 1)

insert into department (name, budget, id) 
values ('Bitsystem', 10000, 2)

第一个部门有 3 个员工:

insert into employee (department_id, name, salary, id) 
values (1, 'John Doe 0', 30000, 0)

insert into employee (department_id, name, salary, id) 
values (1, 'John Doe 1', 30000, 1)

insert into employee (department_id, name, salary, id) 
values (1, 'John Doe 2', 30000, 2)

假设我们有两个并发用户:Alice 和 Bob。

首先,Alice 锁定属于第一个 department 的所有员工,并获得该特定 department 的工资总和:

SELECT * 
FROM employee 
WHERE department_id = 1 
FOR UPDATE

SELECT SUM(salary) 
FROM employee 
where department_id = 1 

现在,与此同时,预计 Bob 不能使用相同的 department_id 插入新的 employee:

insert into employee (department_id, name, salary, id) 
values (1, `Carol`, 9000, 4)

com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: 
    Lock wait timeout exceeded; try restarting transaction

因此,锁阻止了 Bob 对同一个谓词发出插入。

但是,即使 Bob 尝试在不同的 department 中插入一个 employee,也会抛出相同的异常:

insert into employee (department_id, name, salary, id) 
values (2, `Dave`, 9000, 5)

com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: 
    Lock wait timeout exceeded; try restarting transaction

最后一个插入语句使用第二个 department_id,因此该行不应与我们为其获取谓词锁的 select 语句重叠。

为什么MySQL会阻止与第一个事务获取的谓词锁不重叠的第二个insert?

在 SQL Server 上也可以观察到相同的行为。

更新

当将隔离级别更改为 READ_COMMITTED 时,谓词锁不会阻止 Bob 发出的两个插入语句中的任何一个。

如果考虑到 this Percona blog post 中的以下陈述,就可以解释这一点:

In REPEATABLE READ every lock acquired during a transaction is held for the duration of the transaction.

In READ COMMITTED the locks that did not match the scan are released after the STATEMENT completes.

但是,找出谓词锁定为何像在可重复读上那样工作仍然很有趣。

最佳答案

SELECT FOR UPDATE 锁定在 1 和 employee 表中的下一个值之间。由于没有下一个值,它一直锁定到 supremum pseudo-record。这可以在 information_schema.innodb_locks 中看到:

mysql> select * from innodb_locks;
+----------------+-------------+-----------+-----------+-------------------+------------+------------+-----------+----------+------------------------+
| lock_id        | lock_trx_id | lock_mode | lock_type | lock_table        | lock_index | lock_space | lock_page | lock_rec | lock_data              |
+----------------+-------------+-----------+-----------+-------------------+------------+------------+-----------+----------+------------------------+
| 28275:1448:3:1 | 28275       | X         | RECORD    | `test`.`employee` | PRIMARY    |       1448 |         3 |        1 | supremum pseudo-record |
| 28273:1448:3:1 | 28273       | X         | RECORD    | `test`.`employee` | PRIMARY    |       1448 |         3 |        1 | supremum pseudo-record |
+----------------+-------------+-----------+-----------+-------------------+------------+------------+-----------+----------+------------------------+
2 rows in set, 1 warning (0.00 sec)

如果您稍微更改测试用例,以便在 dept-id=2 的员工中有一行,然后尝试为 dept-id=3 添加一个员工,它将起作用。示例:

create table department (
    id bigint not null, 
    budget bigint not null, 
    name varchar(255), 
    primary key (id)
) ENGINE=InnoDB;

create table employee (
    id bigint not null, 
    name varchar(255), 
    salary bigint not null, 
    department_id bigint, primary key (id)
) ENGINE=InnoDB;


alter table employee 
add constraint FK_department_id 
foreign key (department_id) 
references department (id);


insert into department (name, budget, id) 
values ('Hypersistence', 100000, 1);

insert into department (name, budget, id) 
values ('Bitsystem', 10000, 2);

insert into department (name, budget, id) 
values ('XX', 10000, 3);


insert into employee (department_id, name, salary, id) 
values (1, 'John Doe 0', 30000, 0);

insert into employee (department_id, name, salary, id) 
values (1, 'John Doe 1', 30000, 1);

insert into employee (department_id, name, salary, id) 
values (2, 'John Doe 2', 30000, 2);


start transaction;

SELECT * 
FROM employee 
WHERE department_id = 1 
FOR UPDATE;


# new session

insert into employee (department_id, name, salary, id) 
values (3, 'Dave', 9000, 5)

关于mysql - 为什么 MySQL 显式谓词锁定不允许在谓词锁之外插入语句,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42316848/

相关文章:

go - 无法理解电子书中的并发示例

java - Spring ThreadPoolTask​​Executor 导致 Tomcat 内存泄漏

python - Asyncio 与另一个协程同时运行 Dash (Flask) 服务器

c# - 如何在单个事务中将文件写入磁盘并插入数据库记录?

python - PostgreSQL pgdb 驱动程序引发 "can' t 回滚“异常

sql-server - 在连接关闭的情况下在 SQL Server 上保持事务打开

mysql - 将数据从生产拉到开发以进行调试 (Django)

mysql - Docker CentOS - mariadb(退出状态 0;预期)

MySQL 准备好的语句我还需要使用 mysql_escape_string

sql - 用于时区转换的mysql查询