Assuming we have the following database tables:
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)
And we have 2 departments:
insert into department (name, budget, id)
values ('Hypersistence', 100000, 1)
insert into department (name, budget, id)
values ('Bitsystem', 10000, 2)
And 3 employees in the first department:
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)
Assuming we have two concurrent users: Alice and Bob.
First, Alice locks all the employee belonging to the 1st department and also gets the sum of salaries for that particular department:
SELECT *
FROM employee
WHERE department_id = 1
FOR UPDATE
SELECT SUM(salary)
FROM employee
where department_id = 1
Now, in the meanwhile, it's expected that Bob cannot insert a new employee using the same department_id:
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
So, the lock prevented Bob from issuing an insert against the same predicate.
However, the same exception is thrown even if Bob tries to insert an employee in a different department:
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
This last insert statement is using the 2nd department_id, so this row should not overlap with the select statement for which we acquired a predicate lock.
Why does MySQL prevent the second insert which is not overlapping with the predicate lock acquired by the first transaction?
The same behavior can be observed on SQL Server as well.
Update
When changing the isolation level to READ_COMMITTED, the predicate lock does not prevent any of the two insert statements issued by Bob.
That could be explained if taking into consideration the following statements from 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.
However, it's still interesting to find out why the predicate locking works as it does on REPEATABLE READ.