假设我们有以下数据库表:
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/