简体   繁体   中英

Understanding MySQL InnoDB locks

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 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:

  1. Parallel inserts or updates does not lock tables
  2. Parallel insert 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM