前回、COMMITの失敗時にトランザクションの成否は未確定で、タイミングによっては成功している場合もあるし、失敗している場合もあることを検証しました。
今回は、そのような不安定なトランザクションの状態を確認する方法としてOracle 12c R1からサポートされたトランザクションガードがどのようなものが確認してみます。
4) この状態でlog writerの障害を発生(kill)させてみます
4) この状態でlog writer workerの障害を発生(kill)させてみます
今回は、そのような不安定なトランザクションの状態を確認する方法としてOracle 12c R1からサポートされたトランザクションガードがどのようなものが確認してみます。
前回のCOMMIT問題のまとめ |
トランザクションガードの前提
トランザクションガードはOracle 12c R1以降でサポートされます。また、次に示すクライアント・ドライバをサポートしています。
- 12c JDBCタイプ4(Thin)ドライバ
- 12c OCI、OCCIクライアント・ドライバ
- 12c Oracle Data Provider for .NET (ODP.NET)、管理対象外ドライバ
- 12c ODP.NET、ODAC 12c リリース4以降の管理対象ドライバ
トランザクションガードを使うための準備
今回は、JDBC(Thin)ドライバを使ってトランザクションガードの動きを確認してみますがいくつか準備を実施します。
トランザクションガードに対応するサービスの作成(変更)
以下のようなSQLでサービスを作成しますが、COMMIT_OUTCOME=TRUEおよびRETENTION_TIMEOUTを適切なサイズにしてサービスを作成(変更)する必要があります。(以下はサービスを作成しています)
DECLARE PARAMETER_ARRAY DBMS_SERVICE.SVC_PARAMETER_ARRAY; BEGIN PARAMETER_ARRAY('COMMIT_OUTCOME'):='true'; PARAMETER_ARRAY('RETENTION_TIMEOUT'):=604800; DBMS_SERVICE.CREATE_SERVICE( 'TX_GUARD','TX_GUARD',PARAMETER_ARRAY); DBMS_SERVICE.START_SERVICE('TX_GUARD'); END; /
サンプルのJDBCプログラム
トランザクションガードを利用したJDBCプログラムの流れは以下のような感じになります。(コード全体は最後に記載)
- トランザクションガードに対応したサービスへのconnectionオブジェクトから論理トランザクションIDを取得
- 論理トランザクションIDの取得は、ドライバ提供のAPI (JDBCにはgetLTXID、OCIにはLTXID、OCI_ATTR_GET、およびODP.NETにはLogicalTransactionId)を使用して、前に失敗したセッションから(も)論理トランザクションIDを取得
- 取得した論理トランザクションIDを使用してDBMS_APP_CONT.GET_LTXID_OUTCOME PL/SQLプロシージャを起動
- 戻り状態により 、ドライバは最後のトランザクションがCOMMITTED (TRUE/FALSE)およびUSER_CALL_COMPLETED (TRUE/FALSE)であったことを確認可能
前回と同じ方法でCOMMITエラーを発生させてみる
REDOログへの書き込み完了前に障害が発生した場合
1) INSERTを実行$ java TG INSERTING INSERTED commit to enter any key2) log writerのsemtimedop(2)をブロック(上記シーケンス図の②の部分)
$ gdb -p 1870
... (gdb) catch syscall semtimedop (gdb) c3) COMMITを実行
commit to enter any key COMMITTING上記の状態でCOMMITが完了することはありません
4) この状態でlog writerの障害を発生(kill)させてみます
$ kill -9 4006また、2)のgdbのセッションをキャンセルします
(gdb) q5) サンプルプログラムにエラーが返ります
COMMITTING ERROR MESSAGE: No more data to read from socket If you want to check the transaction status, wait for completion of crash recovery.6) クラッシュリカバリ後にトランザクションガードの状態を確認します
If you want to check the transaction status, wait for completion of crash recovery. TX STATUS: UNCOMMITTED <- コミットはされていない7) データが存在しないことを確認します
SQL> select * from sample_table; no rows selected
今度はREDOログへの書き込み完了後に障害が発生した場合
1) INSERTを実行$ java TG INSERTING INSERTED commit to enter any key2) log writer workerのsemctl(2)をブロック(上記シーケンス図の④の直前部分)
$ gdb -p 4041
... (gdb) catch syscall semctl (gdb) c3) COMMITを実行
commit to enter any key COMMITTING上記の状態でCOMMITが完了することはありません
4) この状態でlog writer workerの障害を発生(kill)させてみます
$ kill -9 4041また、2)のgdbのセッションをキャンセルします
(gdb) q5) サンプルプログラムにエラーが返ります
COMMITTING ERROR MESSAGE: No more data to read from socket If you want to check the transaction status, wait for completion of crash recovery.6) クラッシュリカバリ後にトランザクションガードの状態を確認します
If you want to check the transaction status, wait for completion of crash recovery. TX STATUS: COMMITTED <- コミットは完了している7) データが存在することを確認します
SQL> select * from sample_table; ID ---------- 1
まとめ
COMMITの失敗によるトランザクションの成否はタイミングにより異なることを2回に渡って見てきました。このためCOMMITの失敗によるトランザクションのリトライを行うと比較的簡単にデータの論理破壊が発生します。より信頼性の高いアプリケーションを構築するためには特定のトランザクションがデータベースサーバーとして確定しているか否かを知ることが重要です。Oracleを使う場合はトランザクションガードが有効に機能するのでミッションクリティカルなアプリケーションで比較的簡単にトランザクションの行方を知りたい方は使ってみてください。
この動きはPostgreSQLでも気になる
今回はOracleでCOMMITの動きとCOMMIT失敗におけるトランザクションの成否の判定にトランザクションガードが使えることを見てきましたが、ふとPostgreSQLの場合はどうなんだろうと思ったので、次回はPostgreSQLで同様の検証をしてみます。サンプルのJDBCプログラム
import java.util.Scanner; import java.sql.*; import oracle.jdbc.pool.*; import oracle.jdbc.*; public class TG { static void waitForAnyKey(String msg) { System.out.println(msg); Scanner scan = new Scanner(System.in); String key = scan.nextLine(); } public static void main(String argv[]) throws SQLException { String url = "jdbc:oracle:thin:@//localhost/TX_GUARD"; OracleDataSource ods=new OracleDataSource(); ods.setURL(url); ods.setUser("ユーザー"); ods.setPassword("パスワード"); OracleConnection conn = (OracleConnection)ods.getConnection(); conn.setAutoCommit (false); LogicalTransactionId ltxid = conn.getLogicalTransactionId(); try { System.out.println("INSERTING"); conn.prepareStatement("insert into sample_table values (1)") .execute(); System.out.println("INSERTED"); waitForAnyKey("commit to enter any key"); System.out.println("COMMITTING"); conn.prepareStatement("commit").execute(); System.out.println("COMMITED"); } catch (SQLException e) { System.out.println("ERROR"); System.out.println(" MESSAGE: "+ e.getMessage()); waitForAnyKey("If you want to check the transaction status, " +"wait for completion of crash recovery."); OracleConnection conn2 = (OracleConnection) ods.getConnection(); CallableStatement c = conn2.prepareCall( "declare " +" b1 boolean; " +" b2 boolean; " +"begin " +" DBMS_APP_CONT.GET_LTXID_OUTCOME(?,b1,b2); " +" ? := case when B1 then 'COMMITTED' " +" else 'UNCOMMITTED' end; " +"end;"); c.setObject(1, ltxid); c.registerOutParameter(2, OracleTypes.VARCHAR); c.execute(); System.out.println("TX STATUS: "+ c.getString(2)); } } }
コメント
コメントを投稿