S-JIS[2023-10-06/2024-02-17]

Tsurugiトランザクション

Tsurugiのトランザクションのメモ。


概要

Tsurugiのトランザクション分離レベルはSERIALIZABLEだが、Tsurugi内部のシリアライゼーションエラーの判定プロトコルは、OCC(ショートトランザクション(短いトランザクション))とLTX(ロングトランザクション(長いトランザクション))の2種類がある。

ユーザーはトランザクション開始時にトランザクション種別(transaction type)を指定する。
トランザクション種別はOCC・LTX・RTXの3種類だが、RTXはプロトコル的にはLTXの一種だそうだ。

Tsurugiでは、SQL実行時やコミット時に、トランザクションの内容が他トランザクションと競合していないかチェックされる。
競合していた場合はシリアライゼーションエラー(リトライ可能なアボート)が発生するので、アプリケーション側でトランザクション(実行したSQL全て)を再実行する必要がある。

Tsurugiではselectのみのトランザクションであってもシリアライゼーションエラーになることがあるので、selectのみのトランザクションでも最後にちゃんとコミットして、コミットが正常に終了する(シリアライゼーションエラーにならない)ことを確認しなければならない。


トランザクション種別

  OCC LTX RTX
概要 実行時間が短いトランザクション
optimistic cuncurrency control(楽観ロック)
実行時間が長いトランザクション
pessimistic concurrency controll(悲観ロック)
読み取り専用トランザクション
(read only transaction)
select文しか実行できない。
どの時点の
データを読むか
SQL実行時点の最新データを読む。
読んだデータが他トランザクションによって更新された場合、コミット時にアボートする。
トランザクション開始時点のデータを読む。 基本的にトランザクション開始時点のデータを読む。
ただしトランザクション開始時点で他にLTXが実行されていた場合、それらの開始前のデータが読まれる。
また、直前にコミットされたトランザクションの結果を読めるとは限らない。(少し待つ必要がある)[2024-02-17]
アボートしやすさ 他トランザクションとの競合時にアボートしやすい。 他トランザクションとの競合時にアボートしにくい。 他トランザクションと競合しない。
LTXと競合した場合は基本的にOCCがアボートする。
OCC同士では、後からコミットした方がアボートする。
LTX同士では、先に開始したLTXの方が優先度が高い。
(優先度が低い方がアボートする)
一番最初に実行開始したLTXはアボートしない。
RTXはアボートしない。
他トランザクションがRTXと競合してアボートすることも無い。

OCCに関する補足

OCCの「実行時間が短い」が具体的にどれくらいの長さかと言うと、ウェブのオンライン処理の実行時間くらいを想定しているようだ。

トランザクションが 1 epoch未満で完了する(処理件数が数百件程度)なら、OCCはLTXより高速。という構造になっているらしい。
(epochはTsurugi内部でコミット処理を行う時間の単位で、デフォルトでは3ms)[/2024-02-17]


RTXに関する補足

Tsurugi内部の用語では、「RTXはsafe snapshot(SS)を読む」。[2023-11-29]
書籍のp.533を参照。

SSは(一定間隔で)周期的に作られるため、コミットのタイミングとは一致しない。[2024-02-17]
そのため、あるトランザクションがコミットされてもSSが更新されていない間は、RTXではそのトランザクションの結果を読めない。


「とりあえずTsurugiを試してみよう」という際は、とりあえずOCCを使っておけば大丈夫。

トランザクション種別を指定する方法


DDLを実行する際のトランザクション種別

Tsurugiでは、DDLの実行もトランザクション内で行う。
しかしトランザクション管理されるわけではないので、例えばcreate tableが成功した時点でテーブルは作成されるし、ロールバックしてもテーブルは消えない。

DDLはOCCまたはLTXで実行可能。
(実のところ、プライマリキーのあるテーブルならRTXでもcreateできるようだが、RTXは読み取り専用という位置づけなので、DDLは実行しない方がいいだろう^^;)

DDLをLTXで実行する場合はinclude ddlを指定する必要がある。
(実のところ、include ddlを指定しなくてもプライマリキーのあるテーブルならcreateできるようだ。プライマリキーの無いテーブルはinclude ddlの指定が必須(プライマリキーが無いテーブルを作る場合、内部でシステムテーブルのデータが更新されるらしい。システムテーブルへの書き込みにもwrite preserveが必要だがユーザーにはシステムテーブルの名前は分からないので、その代替としてinclude ddlを指定するということらしい))

現在のTsurugiでは、ひとつのトランザクション内でDDLとDMLを実行することは非推奨。
つまり、create tableとinsertは別トランザクションで実行する必要がある。
(といっても、実際には同一トランザクションで実行できるようだが…、どんな不都合が発生するか分からない)


競合時にどちらがアボートするか

2つのトランザクションT1とT2が競合したときにどちらがアボートするかは、トランザクション種別によって異なる。
以下の表では、トランザクションが開始した順序はT1→T2とする。

  T2-OCC T2-LTX T2-RTX
T1-OCC 後からコミットしようとした方がアボートする 基本的にOCC(T1)がアボートする
特殊な場合はLTX(T2)がアボートする
アボートしない
(T2(RTX)はT2開始時点のデータを読む)
T1-LTX OCC(T2)がアボートする T2(後から開始した方)がアボートする
(競合しない事が確定していない場合、T2はT1が終わるまで待つ)
アボートしない
(T2(RTX)はT1開始前のデータを読む)
T1-RTX アボートしない
(T1(RTX)はT1開始時点のデータを読む)
アボートしない
(T1(RTX)はT1開始時点のデータを読む)
アボートしない

OCCと競合してLTXがアボートする特殊な場合とは、OCCの前に別のLTXがあって影響するような場合のことらしい。

LTX同士の「競合しない事の確定」とは、例えばwrite preserveread areaが指定されていてT1とT2のそれらが被っていなければ、競合しないことが明白。
read areaが指定されていない場合、優先度の高いT1(LTX)の処理が終わらないとT1がどのデータを参照/更新するか(T2の参照/更新と被っていないか)が分からない(確定できない)ので、優先度の低いT2(LTX)は待たざるを得ない。

OCCとLTXの場合、LTXのwrite preserveで指定されているテーブルをOCC側が参照するだけでアボートすることがある。


トランザクションオプション

トランザクションオプションはトランザクション開始時に指定する。

トランザクション種別

トランザクション種別は必須。

  OCC LTX RTX DDL用LTX 更新日
tgsql begin;
begin transaction;
begin long transaction write preserve 〜; begin transaction read only deferrable;
begin read only;
begin long transaction include ddl;  
Iceaxe import com.tsurugidb.iceaxe.transaction.option.TgTxOption;  
var txOption = TgTxOption.ofOCC(); var txOption = TgTxOption.ofLTX(write preserve); var txOption = TgTxOption.ofRTX(); var txOption = TgTxOption.ofDDL();
Tsubakuro(SQL) import com.tsurugidb.sql.proto.SqlRequest.TransactionOption;
import com.tsurugidb.sql.proto.SqlRequest.TransactionType;

var optionBuilder = TransactionOption.newBuilder();
 
optionBuilder.setType(TransactionType.SHORT) optionBuilder.setType(TransactionType.LONG) optionBuilder.setType(TransactionType.READ_ONLY)  
var option = optionBuilder.build();
Tsubakuro(KVS) import com.tsurugidb.kvs.TransactionOption;  
var kvsOptionBuilder = TransactionOption.forShortTransaction(); var kvsOptionBuilder = TransactionOption.forLongTransaction(); var kvsOptionBuilder = TransactionOption.forReadOnlyTransaction();  
var option = kvsOptionBuilder.build();
embulk input tsurugidb
embulk output tsurugidb
tx_type: OCC tx_type: LTX tx_type: RTX   2023-10-08

ラベル

ラベルはトランザクションに付ける名前。ユーザーがトランザクションを識別する為のもの。

ラベルは全てのトランザクション種別(OCC, LTX, RTX)で指定可能。

  ラベル 更新日
tgsql begin 〜 as 'label';  
Iceaxe txOption.label("label");  
Tsubakuro(SQL) optionBuilder.setLabel("label");  
Tsubakuro(KVS) kvsOptionBuilder.withLabel("label");  
embulk input tsurugidb
embulk output tsurugidb
tx_label: label 2023-10-08

include ddl

LTXでDDLを実行する場合は、include ddlを指定する。

LTXのみで指定可能。

  include ddl
tgsql begin long transaction include ddl;
begin long transaction include definition;  // 別名
begin long transaction include definitions; // 別名
Iceaxe txOption.includeDdl(true);
Tsubakuro(SQL) optionBuilder.setModifiesDefinitions(true);
Tsubakuro(KVS) kvsOptionBuilder.withModifiesDefinitions(true);

write preserve

write preserve(書き込み予約)は、LTXで更新(insert/update/delete)する対象のテーブル名を指定する。
write preserveで指定されていないテーブルを更新しようとすると、実行時エラーになる。

LTXのみで指定可能。
DDL実行以外のLTXでは、write preserveは必須。
ただしselectしか実行しないLTXなら、write preserveは不要。

  write preserve 更新日
tgsql begin long transaction 〜 write preserve 'table1', 'table2';  
Iceaxe var txOption = TgTxOption.ofLTX("table1", "table2");  
txOption.addWritePreserve("table1", "table2");  
Tsubakuro(SQL) import com.tsurugidb.sql.proto.SqlRequest.WritePreserve;

optionBuilder.addWritePreserves(WritePreserve.newBuilder().setTableName("table1").build());
optionBuilder.addWritePreserves(WritePreserve.newBuilder().setTableName("table2").build());
 
Tsubakuro(KVS) kvsOptionBuilder.addWritePreserve("table1");
kvsOptionBuilder.addWritePreserve("table2");
 
embulk input tsurugidb
embulk output tsurugidb
tx_write_preserve: [table1, table2] 2023-10-08

read area

read areaは、LTXで参照(select/insert/update/delete)する対象のテーブルを指定する。
read areaを指定した場合、対象外のテーブルを参照すると実行時エラーになる。

LTXのみで指定可能。

read areaの指定方法はinclusiveとexclusiveの2通りある。
inclusive read areaは参照するテーブルを指定する。
exclusive read areaは参照しないテーブルを指定する。
両方に同じテーブルを指定すると、そのテーブルは参照できない。

write preserveと異なり、read areaは必須ではない。
read areaを省略した場合は、全テーブルを参照(select)可能。
read areaを指定した場合、write preserveで指定されたテーブルが暗黙にinclusive read areaに追加される。

  inclusive read area exclusive read area 更新日
tgsql begin long transaction 〜 read area include 'table1', 'table2'; begin long transaction 〜 read area exclude 'table3', 'table4'  
begin long transaction 〜 read area include 'table1', 'table2' exclude 'table3', 'table4';
Iceaxe txOption.addInclusiveReadArea("table1", "table2"); txOption.addExclusiveReadArea("table3", "table4");  
Tsubakuro(SQL) import com.tsurugidb.sql.proto.SqlRequest.ReadArea;
optionBuilder.addInclusiveReadAreas(ReadArea.newBuilder().setTableName("table1").build());
optionBuilder.addInclusiveReadAreas(ReadArea.newBuilder().setTableName("table2").build());
optionBuilder.addExclusiveReadAreas(ReadArea.newBuilder().setTableName("table3").build());
optionBuilder.addExclusiveReadAreas(ReadArea.newBuilder().setTableName("table4").build());
 
Tsubakuro(KVS) kvsOptionBuilder.addInclusiveReadArea("table1");
kvsOptionBuilder.addInclusiveReadArea("table2");
kvsOptionBuilder.addExclusiveReadArea("table3");
kvsOptionBuilder.addExclusiveReadArea("table4");
 
embulk input tsurugidb
embulk output tsurugidb
tx_inclusive_read_area: [table1, table2] tx_exclusive_read_area: [table3, table4] 2023-10-08

priority

priorityはトランザクションの優先度を指定する。
優先度というよりは排他的実行に近い。

LTXおよびRTXで指定可能。

設定値の内容は以下の通り。

interrupt(immediate=すぐに)
実行中のトランザクションがあったら、それらを終了させる。(自分が割り込む)
wait(deferrable=延期する)
実行中のトランザクションがあったら、それらが終わるまで待つ。
excludeなし(prior)
自分の実行中に新しいトランザクションを実行可能。
excludeあり(excluding)
自分の実行中に新しいトランザクションを実行させない。
  priority interrupt wait interrupt exclude wait exclude 更新日
tgsql begin 〜 execute transactionPriority; prior immediate prior deferrable excluding immediate excluding deferrable  
Iceaxe import com.tsurugidb.sql.proto.SqlRequest.TransactionPriority;

txOption.priority(transactionPriority);
INTERRUPT WAIT INTERRUPT_EXCLUDE WAIT_EXCLUDE  
Tsubakuro(SQL) import com.tsurugidb.sql.proto.SqlRequest.TransactionPriority;

optionBuilder.setPriority(transactionPriority);
INTERRUPT WAIT INTERRUPT_EXCLUDE WAIT_EXCLUDE  
Tsubakuro(KVS) import com.tsurugidb.kvs.proto.KvsTransaction.Priority;

optionBuilder.setPriority(transactionPriority);
INTERRUPT WAIT INTERRUPT_EXCLUDE WAIT_EXCLUDE  
embulk input tsurugidb
embulk output tsurugidb
tx_priority: transactionPriority interrupt wait interrupt_exclude wait_exclude 2023-10-08

priorityはTsurugi 1.0.0-BETA1では実装されていない。(priorityの指定は出来るが、効果は無い)


Tsurugiへ戻る / 技術メモへ戻る
メールの送信先:ひしだま