I am confused on how MySQL InnoDB locks work.
To understand it in better way, I have conducted below experiment. I have used Spring boot and JPA to insert/update records in below table (though this question is not related to Spring boot or JPA) .
Table name: test
Column name | Type | Constraint |
---|---|---|
id | Bigint | PK AI |
name | Varchar | |
val | Int | |
br_id | Int |
Innodb lock wait timeout: 50 sec
Repository: ITestRepository.java
@Modifying
@Query(value = "update test set val=val+1 where val>:val and br_id=:brId", nativeQuery = true) // Just a random update query
public void updateVal(Integer val, Integer brId);
Service: TestService.java
@org.springframework.transaction.annotation.Transactional
public void saveMany(final int brId) { // brId is always passed as 1
for (int i = 0; i < 200; i++) {
this.testRepository.updateVal(i, brId); // ======> LINE: 1
this.testRepository.save(new TestEntity(String.valueOf(i), i, brId)); // ======> LINE: 2
try {
Thread.sleep(2000);
} catch (final InterruptedException e) {
e.printStackTrace();
}
}
}
@org.springframework.transaction.annotation.Transactional
public void saveOne(final int val, final int brId) { // brId is always passed as 2
this.testRepository.updateVal(val, brId); // ======> LINE: 3
this.testRepository.save(new TestEntity(String.valueOf(val), val, brId)); // ======> LINE: 4
}
update
query will not update anything. Case 1: Triggering only saveMany
method once
In this case, everything works fine, and 200 rows gets inserted once the transaction is completed without any error.
Case 2: Triggering saveMany
first, and then saveOne
once
In this case, while saveMany
is being executed if we trigger saveOne
, saveOne
will fail with Lock wait timeout
exception after 50 sec, and saveMany
will successfully complete once the loop is over.
Case 3: Commenting LINE: 1
& LINE: 3
OR commenting LINE: 2
& LINE: 4
and Triggering saveMany
first, and then saveOne
once
In this case, while saveMany
is being executed if we trigger saveOne
, everything will work fine and both the methods will complete successfully without any exception.
Case 4: Commenting LINE: 1
and Triggering saveMany
first, and then saveOne
once
In this case, while saveMany
is being executed if we trigger saveOne
, saveOne
will fail on LINE: 3
with lock wait timeout
exception for update operation.
Case 5: Commenting LINE: 2
and Triggering saveMany
first, and then saveOne
once
In this case, while saveMany
is being executed if we trigger saveOne
, saveOne
will fail on LINE: 4
with lock wait timeout
exception for insert operation.
From above cases here are my derivations:
inserts
or updates
does not lock tablesinsert
and update
will lock the table (whichever operation is triggered first will acquire lock first) I am not able to understand how above two derivations work. I mean from the MySQL documentation, they state that whole table is never locked in InnoDB, and only the rows are locked while performing inserts
or updates
. As you can see in above cases, br_id
is always different for both the methods, and thus updates
are being performed on different set of rows, then also why lock wait timeout
exception is raised? Also, parallel inserts
or updates
are not causing any issue, how and why?
EDIT 1:
If br_id
is not indexed, then it works like stated in above cases, but if br_id
is indexed, then Deadlock found when trying to get lock; try restarting transaction
Deadlock found when trying to get lock; try restarting transaction
exception is thrown immediately while executing saveOne
method parallel to saveMany
method.
update test
set val = ...
where val > ...
and br_id = ...
Since there are no indexes on the relevant columns, all rows are locked.
It would be better to have
INDEX(br_id)
or
INDEX(br_id, val)
The latter is better for locating the row(s) to modify, but has the drawback of needing to update the index's BTree.
Be aware that some "locks" are "gap locks". This involves locking the gap between indexed values. This can sometimes cause surprises. (I don't know if such is relevant to this update.)
One tool for looking at the locks is SHOW ENGINE INNODB STATUS;
(Since the info there is transient, it may not show what you need.)
BEGIN;
UPDATE ...
((spend lots of time before COMMIT))
COMMIT;
That will either delay or deadlock any competing connection that needs the same row locks.
Nearly always InnoDB can see that simply stalling (up to innodb_lock_wait_timeout
seconds) in order to get the necessary locks.
InnoDB can always discover a deadlock, and will ROLLBACK
one of the transactions, while letting the other finish.
There are a few cases (esp with "gap locking" where InnoDB conservatively declares a deadlock when there might not have been one. (I don't know if this was your case.)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.