mysqlギャップロックによりjava.sql.SQLExceptionが発生します:ロック待機タイムアウトを超えました;トランザクションを再起動してみてください

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を確認し、主キーに従って移動する更新するだけです。これの欠点は、もう一度クエリを実行する必要があることです。これはパフォーマンスに影響します。

おすすめ

転載: blog.csdn.net/huangdi1309/article/details/80649081