mysqltransactionsdeadlock

Why I'm getting a deadlock from mysql using SELECT ... FOR UPDATE lock?


I have two threads, they have to update the same table but the first one is using a primary key to lock a single record, the second thread have to lock a set of records using another index. The lock is made with SELECT ... FOR UPDATE steatment, I cannot understand why they run into a deadlock.

This is the table:

CREATE TABLE `ingressi` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `evento` int(10) unsigned NOT NULL,
 `stato` int(10) unsigned NOT NULL,
 ....,
 ....,
 PRIMARY KEY (`id`),
  KEY `evento` (`evento`,`stato`) USING BTREE,
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

This is the query log (Please mind the connection):

    43 Query    SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
    43 Query    set autocommit=0
    43 Query    SELECT stato FROM ingressi WHERE id=1 FOR UPDATE
    39 Query    SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
    39 Query    set autocommit=0
    39 Query    SELECT count(*) FROM ingressi WHERE evento=66 FOR UPDATE
    43 Query    UPDATE `ingressi` SET stato=0 WHERE id=1
    43 Query    COMMIT

Just after the last query the deadlock error is thrown.

(conn=39) Deadlock found when trying to get lock; try restarting transaction

The 43 and 39 connection are JDBC connection of a Spring-Java application using a connection pool.

Why the second connection doesn't wait and it was deadlocked?


Solution

  • When you use a secondary index to locate and lock a row, MySQL will first lock the entry in the secondary index, and, after that, the corresponding row in the primary key. This double step can cause your deadlock.

    Let's assume the following row:

    +----+--------+-------+
    | id | evento | stato |
    +----+--------+-------+
    |  1 |     66 |    10 |
    +----+--------+-------+
    

    So how to prevent it?

    For your specific situation, you could use a different secondary index to find your row, e.g. KEY evento1 (evento). If MySQL uses this index (and to make sure, you can use SELECT count(*) FROM ingressi FORCE INDEX (evento1) WHERE evento=66 FOR UPDATE), this should prevent this specific deadlock:

    If that is a reasonable solution for your specific situation will depend on, well, your specific situation. It might e.g. be overkill to add an index you don't actually want or need just to prevent a deadlock that happens twice a year. Or maybe you have more of those situations which are all slightly different and that may need a different approach. You may find some additional general guidelines at e.g. How to Minimize and Handle Deadlocks.