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,然后再根据主键去更新即可,这个不好的地方就是要多查询一次,影响性能。