mysql间隙锁导致java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction

MySQL事务隔离级别是Repeatable Read (RR),RR隔离级别保证对读取到的记录加锁(记录锁),同时在本记录与上一条记录,和本记录和下一条记录之间加锁(间隙锁),新的满足查询条件的记录不能够插入。间隙锁只会block住insert操作

下面以一个例子说明间隙锁导致insert插入失败:

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();
                }
            }
        }
    }

}

执行结果:
这里写图片描述

此时看information_schema.innodb_locks和information_schema.INNODB_TRX表,可以看到
这里写图片描述

表记录:
这里写图片描述

当我们要插入merchant_id=103的时候出现异常:Lock wait timeout exceeded; try restarting transaction,因为UPDATE test SET token = ‘test678’ WHERE merchant_id = 110语句产生了间隙锁,merchant_id=103落在锁的范围内,所以插入失败;如果插入的merchant_id=118,是可以成功的。
这里写图片描述

要解决这种间隙锁,可以通过主键更新(若where条件走主键索引或唯一索引,不会产生间隙锁),所以要分两步走,先查询出要更新的主键id,然后再根据主键去更新即可,这个不好的地方就是要多查询一次,影响性能。

猜你喜欢

转载自blog.csdn.net/huangdi1309/article/details/80649081
今日推荐