S-JIS[2023-10-06/2024-02-17]
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の「実行時間が短い」が具体的にどれくらいの長さかと言うと、ウェブのオンライン処理の実行時間くらいを想定しているようだ。
トランザクションが 1 epoch未満で完了する(処理件数が数百件程度)なら、OCCはLTXより高速。という構造になっているらしい。
(epochはTsurugi内部でコミット処理を行う時間の単位で、デフォルトでは3ms)[/2024-02-17]
Tsurugi内部の用語では、「RTXはsafe snapshot(SS)を読む」。[2023-11-29]
書籍のp.533を参照。
SSは(一定間隔で)周期的に作られるため、コミットのタイミングとは一致しない。[2024-02-17]
そのため、あるトランザクションがコミットされてもSSが更新されていない間は、RTXではそのトランザクションの結果を読めない。
「とりあえずTsurugiを試してみよう」という際は、とりあえずOCCを使っておけば大丈夫。
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 preserveとread 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 long transaction write preserve 〜; |
begin transaction read only deferrable; |
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; |
||||
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 |
LTXでDDLを実行する場合は、include ddlを指定する。
LTXのみで指定可能。
include ddl | |
---|---|
tgsql | begin long transaction include ddl; |
Iceaxe | txOption.includeDdl(true); |
Tsubakuro(SQL) | optionBuilder.setModifiesDefinitions(true); |
Tsubakuro(KVS) | kvsOptionBuilder.withModifiesDefinitions(true); |
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; |
|
Tsubakuro(KVS) | kvsOptionBuilder.addWritePreserve("table1"); |
|
embulk input tsurugidb embulk output tsurugidb |
tx_write_preserve: [table1, table2] |
2023-10-08 |
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.addExclusiveReadAreas(ReadArea.newBuilder().setTableName("table3").build()); |
||
Tsubakuro(KVS) | kvsOptionBuilder.addInclusiveReadArea("table1"); |
kvsOptionBuilder.addExclusiveReadArea("table3"); |
|
embulk input tsurugidb embulk output tsurugidb |
tx_inclusive_read_area: [table1, table2] |
tx_exclusive_read_area: [table3, table4] |
2023-10-08 |
priorityはトランザクションの優先度を指定する。
優先度というよりは排他的実行に近い。
LTXおよびRTXで指定可能。
設定値の内容は以下の通り。
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の指定は出来るが、効果は無い)