MySQLトランザクション分離レベルはRepeatableRead(RR)です。RR分離レベルは、読み取りレコードがロックされることを保証します(レコードロック)。同時に、レコードと前のレコード、およびこのレコードと次のレコードの間のロック(ギャップロック)、クエリ条件を満たす新しいレコードを挿入できません。ギャップロックは挿入操作のみをブロックします
以下は、ギャップロックによって挿入が失敗することを示す例です。
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を挿入する場合、例外が発生します。ロック待機タイムアウトを超えました。UPDATEテストSETトークン= 'test678' WHERE Merchant_id = 110ステートメントがギャップロックを生成し、merchant_id = 103がスコープ内にあるため、トランザクションを再開してみてください。したがって、挿入は失敗します。挿入されたmerchant_id = 118の場合、成功する可能性があります。
この種のギャップロックを解決するには、主キーを使用して更新できます(where条件が主キーインデックスまたは一意のインデックスを使用する場合、ギャップロックはありません)。したがって、2つのステップに分割する必要があります。最初に、更新する主キーIDを確認し、主キーに従って移動する更新するだけです。これの欠点は、もう一度クエリを実行する必要があることです。これはパフォーマンスに影響します。