S_a_k_Uの日記みたいなDB

~サクゥーと呼ばないで~

インデックスのないテーブルへのUPDATE/DELETEは表ロック?

HiRDBオンラインマニュアル > スケーラブルデータベースサーバ HiRDB Version 8 UAP開発ガイド > 3.4.9 SQL文の種類とインデクスの種別による排他制御の順序
ここの「…DELETE文,又はUPDATE文で条件に合うデータを探す場合」のフローを見ると、”該当するインデックスキーの有無の判定”で”なし”となった場合、どうなるんじゃ?


下記のプログラムでの動きを見ると
「インデックスがない」→「全件検索」→「その時点でとりあえず全件ロック」→「(結果的に)表ロックになる」
って動きじゃないか?と。
普通に頭で考えると
「インデックスがない」→「全件検索」→「条件に一致する行だけロック」→「普通に行ロック」
って動きを予想してたので。
デッドロックになる理由が他に見当たりません…


テーブルMOGE_Tにはインデックスを定義してない状態で下記プログラムを実行すると、ここと同じように、デッドロックとかタイムアウトになったりする。
WHERE句で、レコードが一意になるようにしてるんだけどね。
もちろん、テーブルMOGE_TにカラムCOL_X, COL_Y, COL_Zでインデックスを設定すると、デッドロックにもタイムアウトにもなりません。


同じスレッドで、2つのConnectionオブジェクトによりデッドロックを起こそうとするプログラム

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class DeadlockTest3 {
    
    public static void main(String[] args) {
        try {
            
            Class.forName("JP.co.Hitachi.soft.HiRDB.JDBC.HiRDBDriver");

            String url = "jdbc:hitachi:hirdb://DB=HiRDB,DBID=22200,DBHOST=xxx.xxx.xxx.xxx";
            
            // コネクション1を取得
            Connection con1 = DriverManager.getConnection(url, "HOGE", "HOGE");
            con1.setAutoCommit(false);

            // コネクション2を取得
            Connection con2 = DriverManager.getConnection(url, "HOGE", "HOGE");
            con2.setAutoCommit(false);
            
            PreparedStatement pstmt1 = null;
            PreparedStatement pstmt2 = null;
            
            // コネクション1でレコード1をINSERT
            pstmt1 = con1.prepareStatement("INSERT INTO MOGE_T (COL_X, COL_Y, COL_Z) values ('11', '12', '13')");
            pstmt1.execute();

            // コネクション2でレコード2をINSERT
            pstmt2 = con2.prepareStatement("INSERT INTO MOGE_T (COL_X, COL_Y, COL_Z) values ('21', '22', '23')");
            pstmt2.execute();

            // コネクション2でレコード2をDELETE【ここでデッドロック(実際はタイムアウト)】
            pstmt2 = con2.prepareStatement("DELETE FROM MOGE_T WHERE COL_X = '21' AND COL_Y = '22' AND COL_Z = '23'");
            pstmt2.execute();

            // コネクション1でレコード1をDELETE
            pstmt1 = con1.prepareStatement("DELETE FROM MOGE_T WHERE COL_X = '11' AND COL_Y = '12' AND COL_Z = '13'");
            pstmt1.execute();

            con1.commit();
            con2.commit();
            con1.close();
            con2.close();
            
        } catch (Throwable t) {
            t.printStackTrace();
        } finally {
            try {
                if((con1 != null) && (con1.isClosed() == false)) {
                    con1.close();
                }
                if((con2 != null) && (con2.isClosed() == false)) {
                    con2.close();
                }
            } catch(Throwable t) {
                t.printStackTrace();
            }
        }

    }
    
}


別のスレッドで、2つのConnectionオブジェクトによりデッドロックを起こそうとするプログラム

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

public class DeadlockTest4 {

    private String URL = "jdbc:hitachi:hirdb://DB=HiRDB,DBID=22200,DBHOST=xxx.xxx.xxx.xxx";    
    
    public static void main(String[] args) {
        new DeadlockTest4().test();
    }
    
    public void test() {
        try {
            Class.forName("JP.co.Hitachi.soft.HiRDB.JDBC.HiRDBDriver");
            new Test1().start();
            new Test2().start();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
        
    private class Test1 extends Thread implements Runnable {
            
        public void run() {
        
            Connection con = null;
            
            try {
            
                // スレッド1でコネクションを取得
                con = DriverManager.getConnection(URL, "HOGE", "HOGE");
                con.setAutoCommit(false);
                
                PreparedStatement pstmt = null;
                
                // スレッド1でレコード1をINSERT
                pstmt = con.prepareStatement("INSERT INTO MOGE_T (COL_X, COL_Y, COL_Z) values ('11', '12', '13')");
                pstmt.execute();
                
                Thread.sleep(100);

                // スレッド1でレコード1をDELETE【ここでデッドロック(test2と早い方)】
                pstmt = con.prepareStatement("DELETE FROM MOGE_T WHERE COL_X = '11' AND COL_Y = '11' AND COL_Z = '11'");
                pstmt.execute();
    
                con.commit();
                con.close();
                
            } catch (Throwable t) {
                t.printStackTrace();
            } finally {
                try {
                    if ((con != null) && (con.isClosed() == false)) {
                        con.close();
                    }
                }  catch (Throwable t) {
                    t.printStackTrace();
                }
            }
        }
        
    }
    
    private class Test2 extends Thread implements Runnable {
            
        public void run() {
            
            Connection con = null;
            
            try {
            
                // スレッド2でコネクションを取得
                con = DriverManager.getConnection(URL, "COMMON", "COMMON");
                con.setAutoCommit(false);
                
                PreparedStatement pstmt = null;
                
                // スレッド2でレコード2をINSERT
                pstmt = con.prepareStatement("INSERT INTO MOGE_T (COL_X, COL_Y, COL_Z) values ('21', '22', '23')");
                pstmt.execute();
                
                Thread.sleep(100);

                // スレッド2でレコード2をDELETE【ここでデッドロック(test1と早い方)】
                pstmt = con.prepareStatement("DELETE FROM MOGE_T WHERE COL_X = '21' AND COL_Y = '22' AND COL_Z = '23'");
                pstmt.execute();
    
                con.commit();
                con.close();
                
            } catch (Throwable t) {
                t.printStackTrace();
            } finally {
                try {
                    if ((con != null) && (con.isClosed() == false)) {
                        con.close();
                    }
                }  catch (Throwable t) {
                    t.printStackTrace();
                }
            }
        }
        
    }
    
}