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
}
- NOTE 1: Table is always truncated before executing any of the below cases.
- NOTE 2: It is intentional that above
updatequery 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:
- Parallel
insertsorupdatesdoes not lock tables - Parallel
insertandupdatewill 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 exception is thrown immediately while executing saveOne method parallel to saveMany method.