mysql gap lock causes java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction

The MySQL transaction isolation level is Repeatable Read (RR). The RR isolation level ensures that the read record is locked (record lock). At the same time, the record and the previous record, and the lock between this record and the next record (gap Lock), new records that meet the query conditions cannot be inserted. Gap lock will only block the insert operation

The following is an example to illustrate that the gap lock causes the insert to fail:

import org.junit.Before;
import org.junit.Test;

import java.sql.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestCache {
    
    


    @Before
    public void init(){

        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void test(){

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(new Thread2());

        Connection conn = null;
        try {
            conn = DriverManager.getConnection("jdbc:mysql://10.x.xx.xx:3306/test_qjd?useUnicode=true&characterEncoding=utf-8","db_owner", "password");
            conn.setAutoCommit(false);
            Statement st = conn.createStatement();
            st.executeUpdate("UPDATE test SET token = 'test678' WHERE merchant_id = 110");
        } catch (SQLException e) {
            e.printStackTrace();
            try {
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }finally {
            try {
                Thread.sleep(60000);
                conn.commit();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }


    class Thread2 implements Runnable{

        @Override
        public void run() {
            Connection conn = null;
            try {
                Thread.sleep(2000);
                conn = DriverManager.getConnection("jdbc:mysql://10.x.xx.xx:3306/test_qjd?useUnicode=true&characterEncoding=utf-8","db_owner", "password");
                conn.setAutoCommit(false);
                PreparedStatement st = conn.prepareStatement("INSERT INTO test (merchant_id, token) VALUES(103, 'insert')");
                st.executeUpdate();
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }finally {
                try {
                    conn.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

Results of the:
Write picture description here

Now look at the information_schema.innodb_locks and information_schema.INNODB_TRX tables, you can see
Write picture description here

Table record:
Write picture description here

When we want to insert merchant_id=103, an exception occurs: Lock wait timeout exceeded; try restarting transaction, because the UPDATE test SET token ='test678' WHERE merchant_id = 110 statement produces a gap lock, and merchant_id=103 falls within the scope of the lock. So the insertion fails; if the inserted merchant_id=118, it can be successful.
Write picture description here

To solve this gap lock, you can update through the primary key (if the where condition takes the primary key index or the unique index, the gap lock will not be generated), so it must be divided into two steps, first query the primary key id to be updated, and then go according to the primary key Just update, the downside of this is that you have to query one more time, which affects performance.

Guess you like

Origin blog.csdn.net/huangdi1309/article/details/80649081