攻略!Aurora DSQL の OCC(楽観的同時実行制御)

592 Views

September 30, 25

スライド概要

JAWS FESTA 2025 in 金沢 2025/10/11【B-5】
(社内向けプレ発表向けの資料として作成・公開中/JAWS FESTA 2025 in 金沢に向けて何度か改訂する予定)

profile-image

Qiita や Zenn でいろいろ書いてます。 https://qiita.com/hmatsu47 https://zenn.dev/hmatsu47 MySQL 8.0 の薄い本 : https://github.com/hmatsu47/mysql80_no_usui_hon Aurora MySQL v1 → v3 移行計画 : https://zenn.dev/hmatsu47/books/aurora-mysql3-plan-book https://speakerdeck.com/hmatsu47

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

攻略!Aurora DSQL の OCC (楽観的同時実行制御) JAWS FESTA 2025 in 金沢 2025/10/11【B-5】 まつひさ(hmatsu47) 1

2.

自己紹介 松久裕保(@hmatsu47) ● https://qiita.com/hmatsu47 ● 現在: ○ 名古屋で Web インフラのお守り係をしています ○ SRE チームに所属しつつ技術検証の支援をしています ○ 普段カンファレンス・勉強会では DB の話しかしていません (ほぼ) 2

3.

本日の内容 ● トランザクション関連の用語について ● Aurora DSQL とは? ● トランザクションが競合するパターン・しないパターン ● 競合対策(競合頻度の低減・競合時リトライなど) ● 書き込みスキュー異常(変更データ間の矛盾)回避 ● まとめ 3

4.

なお ● 【補足】【参考】と記されたページは軽く流します ○ 後で資料を読んで確認してください https://www.docswell.com/s/hmatsu47/ZJQYXX-aurora-occ-jaws-festa-20 251011 (右上 QR コード) 4

5.

トランザクション関連の 用語について 5

6.

トランザクション関連の用語について 1. トランザクション処理 2. 変更(変更トランザクション) 3. ロック 4. PCC と OCC 5. スナップショット分離(Snapshot Isolation) 6

7.

[1] トランザクション処理 ● 整合性・一貫性を保つために複数の処理をまとめて一つ の不可分な処理単位として扱う仕組みのこと ○ 例えば A さんの口座から B さんの口座へ 10 万円送金する場合 ■ A さんの口座の残高確認(10 万円未満なら処理中止) ■ A さんの口座から 10 万円減らす ■ B さんの口座に 10 万円加算する を一連の不可分な処理として扱う ○ 途中で障害などが起きたら一連の処理を「なかったこと」にする 7

8.

【補足】RDBMS のトランザクション処理 ● 開始からコミットまでを「不可分な処理」として扱う ○ 何らかの理由で処理をなかったことにする場合はロールバック ● オートコミット設定によって動作が変わる ○ オートコミット ON の場合、1 つの SQL 文毎にコミット処理が 行われるが、BEGIN を発行するとトランザクション開始 ■ BEGIN 以降はオートコミットされない ○ オートコミット OFF の場合、最初の SQL 文が発行された時点か らトランザクション開始 8

9.

[2] 変更(変更トランザクション) ● このセッションでは以下をまとめて「変更」と表現する ○ 挿入(INSERT) ○ 更新(UPDATE) ○ 削除(DELETE) ● 「変更」処理を含むトランザクションを「変更トランザ クション」とする ○ 言い換えると「書き込みトランザクション」 9

10.

[3] ロック ● 他のトランザクションから値の参照・変更ができないよ うにする機構 ○ 共有ロック(他トランザクションは参照のみ可・変更不可) ○ 排他ロック(他トランザクションは参照も変更も不可) ● このセッションの文脈では、いわゆる「楽観ロック」を ロックとみなさない ○ AWS のドキュメントでも「DSQL はロックを使わない」と説明 10

11.

[4] PCC と OCC ● 同時実行制御方式の違い ○ PCC(悲観的同時実行制御):ロックを使う方式 ■ 通常の RDBMS で採用されている ○ OCC(楽観的同時実行制御):ロックを使わない方式 ■ Aurora DSQL や Tsurugi(劔)で採用されている 11

12.

[4-1] PCC(悲観的同時実行制御) ● 通常の RDBMS で採用されている同時実行制御方式 ○ ロックを使う ○ 並行する複数のトランザクションが同じデータ行の変更を同時に 行わないよう、先に変更を開始したトランザクションがコミット /ロールバックするまで他のトランザクションによる変更を待機 させる 12

13.

[4-1] PCC(悲観的同時実行制御) ⚫BEGIN ◎UPDATE A ⚫BEGIN ○UPDATE A →ロック待ち ⚫BEGIN ● COMMIT⭕ ◎UPDATE A ○UPDATE A →ロック待ち COMMIT⭕ ◎UPDATE A 後続トランザクションは先行トランザクションの COMMIT / ROLLBACK を待つ 13

14.

[4-2] OCC(楽観的同時実行制御) ● Aurora DSQL で採用されている同時実行制御方式 ○ ロックを使わない ○ 並行する複数のトランザクションが同じデータ行の変更を試みた 場合、最初にコミットしたトランザクションの処理が成功する ■ 後からコミットしたトランザクションの処理は中断(アボート) 14

15.

[4-2] OCC(楽観的同時実行制御) ⚫BEGIN ◎UPDATE A ⚫BEGIN ◎UPDATE A ⚫BEGIN ● COMMIT⭕ ◎UPDATE A COMMIT❌ COMMIT❌ 色々な同時実行パターンがあるが、他のパターンについては後述 15

16.

[5] スナップショット分離(Snapshot Isolation) ● DBMS におけるトランザクション分離レベルの 1 つ ○ トランザクション開始時のコミット済みデータを読み取る ○ 並行する他のトランザクションが更新したデータを読み取らない ● 書き込みスキュー異常発生の可能性がある ○ 変更データ間の矛盾→回避策については後述 16

17.

[5] スナップショット分離(Snapshot Isolation) ⚫BEGIN ◎INSERT A (1, 100) ⚫BEGIN ◎SELECT A →空 COMMIT⭕ TxA ◎SELECT A →空 COMMIT⭕ ◎SELECT A → (1, 100) TxB ここはCOMMIT前とは 別のトランザクション ● ● トランザクションA(TxA)で COMMIT 成功⭕→ TxB で値は表示されない TxB で COMMIT →(新たなトランザクション開始) → TxA で COMMIT した値が表示される 17

18.

【参考】Aurora DSQL での実行例 ・トランザクション A(テーブル準備) postgres=> CREATE SCHEMA hoge; postgres=> CREATE TABLE hoge.fuga(id INT PRIMARY KEY UNIQUE, val INT); ・トランザクション A(開始) postgres=> BEGIN; ・トランザクション B(開始) postgres=> BEGIN; ・トランザクション A(データ挿入&コミット) postgres=*> INSERT INTO hoge.fuga VALUES(1, 100); postgres=*> COMMIT; 18

19.

【参考】Aurora DSQL での実行例 ・トランザクション B(データ参照しても見えない) postgres=*> SELECT * FROM hoge.fuga; id | val ----+----(0 rows) ・トランザクション B・B’(コミット →データ参照すると見える) postgres=*> COMMIT; postgres=> SELECT * FROM hoge.fuga; id | val ----+----1 | 100 (1 row) 19

20.

Aurora DSQL とは? 20

21.

Aurora DSQL とは? ● サーバーレス分散 SQL データベース ● それぞれの階層で負荷等に合わせて水平スケール ● シャーディングを使わない ● OCC(楽観的同時実行制御)とスナップショット分離を採用 21

22.

DSQL:サーバーレス分散 SQL データベース ● PostgreSQL ワイヤープロトコル互換 ○ psql コマンドが使える ● シングルリージョン構成とマルチリージョン構成がある ○ マルチリージョン構成は US 3 リージョン/欧州 3 リージョン/ 東京+大阪+ソウルの組み合わせでサポート ■ エンドポイントは 2 リージョン、残り 1 つは Witness リージョンで構成 ○ 次ページの図はシングルリージョン構成の例 22

23.

それぞれの階層で負荷等に合わせて水平スケール AWS Summit Japan 2025 AWS-43 資料より 引用元 : https://aws.amazon.com/jp/blogs/news/introducing-amazon-aurora-dsql/ 23

24.

シャーディング? ● データを水平分割して複数ノードに分散させる技術 ● Aurora Limitless Database ではシャーディングによっ て書き込みのスケーラビリティを向上 ● Google Spanner も自動シャーディングでスケーラビリ ティを向上 ● Aurora DSQL ではシャーディングを使わない 24

25.

シャーディングを使わずにスケーラビリティ確保 ● 各階層で負荷等に合わせて水平スケール ○ 前述のとおり ● OCC(楽観的同時実行制御)を採用 ○ 並行トランザクションをロックで待たせない ■ ロックを使う方式の場合、単純な 1 行のロック以外に複数行の範囲ロックを 取ることがある→ロック待ちの対象トランザクションが増える原因に ■ ロック待ちをしているトランザクションがさらに後続のトランザクションの ロック待ち時間を長くする→ロック待ちが積み重なるとスケールしない 25

26.

再掲:PCC でロック待ちが積み重なるイメージ ⚫BEGIN ◎UPDATE A ⚫BEGIN ○UPDATE A →ロック待ち ⚫BEGIN ● COMMIT⭕ ◎UPDATE A ○UPDATE A →ロック待ち COMMIT⭕ ◎UPDATE A 後続トランザクションほどロック待ち時間が積み重なって長くなる 26

27.

シャーディングを使わずにスケーラビリティ確保 ● OCC+スナップショット分離 ○ コミットするまで並行トランザクションの実行結果を確認しない →トランザクションの並行性を確保 27

28.

一方で:OCC の課題 ● 変更の競合が多発するとスループットが低下 ○ 競合をできるだけ避ける必要がある 28

29.

Aurora DSQL で トランザクションが 競合するパターン・ しないパターン 29

30.

おことわり ● 一部の例ではトランザクションの中で 1 つの SQL 文しか 発行しないケースを示しています ○ 目的:理解しやすくするため ● オートコミット設定 ON のケースを示しています ○ BEGIN を発行することで明示的にトランザクションを開始 30

31.

同じデータ行を変更しようとした場合 1. 最初にコミットしたトランザクションが成功する 2. コミット失敗したトランザクションとは競合しない 3. 【補足】オートコミット ON 設定でトランザクションを 実行する場合、BEGIN の発行がトランザクションの始点 になる 4. 【補足】変更対象データの一部が重なる場合 31

32.

同じデータ行を変更しようとした場合 [1] ● 最初にコミットしたトランザクションが成功する 32

33.

同じデータ行を変更しようとした場合 [1] ⚫BEGIN ◎UPDATE A ⚫BEGIN ◎UPDATE A ⚫BEGIN ● ◎UPDATE A COMMIT⭕ TxA COMMIT❌ TxB COMMIT❌ 並行トランザクションは「最初に COMMIT」したもの勝ち 33

34.

【参考】Aurora DSQL での実行例 [1] ・トランザクション A(開始〜データ更新) postgres=> BEGIN; postgres=*> UPDATE hoge.fuga SET val = 110 WHERE id = 1; ・トランザクション B(開始〜データ更新) postgres=> BEGIN; postgres=*> UPDATE hoge.fuga SET val = 120 WHERE id = 1; ・トランザクション A(コミット) postgres=*> COMMIT; ・トランザクション B(コミット失敗) postgres=*> COMMIT; ERROR: change conflicts with another transaction, please retry: (OC000) 34

35.

同じデータ行を変更しようとした場合 [2] ● コミット失敗したトランザクションとは競合しない 35

36.

同じデータ行を変更しようとした場合 [2] ⚫BEGIN ◎UPDATE A COMMIT⭕ ⚫BEGIN TxA ◎UPDATE A TxC ● ⚫BEGIN COMMIT❌ ◎UPDATE A TxB COMMIT⭕ TxA の COMMIT 成功⭕後に TxC が BEGIN し、 TxB の COMMIT 失敗❌後に TxC が COMMIT →成功⭕ 36

37.

【参考】Aurora DSQL での実行例 [2] ・トランザクション A(開始〜データ更新) postgres=> BEGIN; postgres=*> UPDATE hoge.fuga SET val = 110 WHERE id = 1; ・トランザクション B(開始〜データ更新) postgres=> BEGIN; postgres=*> UPDATE hoge.fuga SET val = 120 WHERE id = 1; ・トランザクション A(コミット) postgres=*> COMMIT; ・トランザクション C(開始〜データ更新) postgres=> BEGIN; postgres=*> UPDATE hoge.fuga SET val = 130 WHERE id = 1; 37

38.

【参考】Aurora DSQL での実行例 [2] ・トランザクション B(コミット失敗) postgres=*> COMMIT; ERROR: change conflicts with another transaction, please retry: (OC000) ・トランザクション C(コミット・データ参照 →トランザクション Cの値で上書きされている) postgres=*> COMMIT; postgres=> SELECT * FROM hoge.fuga; id | val ----+----1 | 130 (1 row) 38

39.

【補足】同じデータ行を変更しようとした場合 [3] ● オートコミット ON 設定でトランザクションを実行する 場合、BEGIN の発行がトランザクションの始点になる 39

40.

【補足】同じデータ行を変更しようとした場合 [3] ⚫BEGIN ◎UPDATE A COMMIT⭕ ⚫BEGIN TxC ● TxA ◎UPDATE A ⚫BEGIN COMMIT❌ ◎UPDATE A TxB COMMIT❌ TxA の COMMIT(成功)前に TxC が BEGIN → TxC は COMMIT 時に失敗❌ → BEGIN ~ COMMIT の期間が重なっていれば競合対象 40

41.

【補足・参考】Aurora DSQL での実行例 [3] ・トランザクション A(開始〜データ更新) postgres=> BEGIN; postgres=*> UPDATE hoge.fuga SET val = 120 WHERE id = 1; ・トランザクション B(開始〜データ更新) postgres=> BEGIN; postgres=*> UPDATE hoge.fuga SET val = 130 WHERE id = 1; ・トランザクション C(開始) postgres=> BEGIN; ・トランザクション A(コミット) postgres=*> COMMIT; 41

42.

【補足・参考】Aurora DSQL での実行例 [3] ・トランザクション C(データ更新) postgres=*> UPDATE hoge.fuga SET val = 140 WHERE id = 1; ・トランザクション B(コミット失敗) postgres=*> COMMIT; ERROR: change conflicts with another transaction, please retry: (OC000) ・トランザクション C(コミット失敗) postgres=*> COMMIT; ERROR: change conflicts with another transaction, please retry: (OC000) 42

43.

【補足】変更対象データの一部が重なる場合 [4] ● 複数テーブル・複数行を更新するトランザクション間で は、変更対象行が重なるものだけが競合する 43

44.

【補足】変更対象データの一部が重なる場合 [4] ⚫BEGIN ◎INSERT A ⚫BEGIN COMMIT⭕ ◎INSERT A TxC ● TxA ◎INSERT B ⚫BEGIN ◎INSERT B COMMIT❌ TxB COMMIT⭕ TxA と TxC は変更対象が重ならない→非競合 TxB が COMMIT 失敗❌→ TxC の COMMIT は成功⭕ 44

45.

【補足・参考】Aurora DSQL での実行例 [4] ・トランザクション A(テーブル準備) postgres=> DELETE FROM hoge.fuga; postgres=> SELECT * FROM hoge.fuga; id | val ----+----(0 rows) postgres=> CREATE TABLE hoge.piyo(id INT PRIMARY KEY UNIQUE, val INT); ・トランザクション A(開始〜データ挿入 A) postgres=> BEGIN; postgres=*> INSERT INTO hoge.fuga VALUES(1, 100); 45

46.

【補足・参考】Aurora DSQL での実行例 [4] ・トランザクション B(開始〜データ挿入 A・B) postgres=> BEGIN; postgres=*> INSERT INTO hoge.fuga VALUES(1, 110); postgres=*> INSERT INTO hoge.piyo VALUES(1, 200); ・トランザクション C(開始〜データ挿入 B) postgres=> BEGIN; postgres=*> INSERT INTO hoge.piyo VALUES(1, 210); ・トランザクション A(コミット) postgres=*> COMMIT; 46

47.

【補足・参考】Aurora DSQL での実行例 [4] ・トランザクション B(コミット失敗) postgres=*> COMMIT; ERROR: change conflicts with another transaction, please retry: (OC000) ・トランザクション C(コミット) postgres=*> COMMIT; 47

48.

【補足・参考】Aurora DSQL での実行例 [4] ・トランザクション C(データ参照 →トランザクション A・Cで挿入した値が表示される) postgres=*> COMMIT; postgres=> SELECT * FROM hoge.fuga; id | val ----+----1 | 100 (1 row) postgres=> SELECT * FROM hoge.piyo; id | val ----+----1 | 210 (1 row) 48

49.

ここまでの結果から ● トランザクションや変更処理の開始順ではなくコミット 順で成功/失敗が決まる ● トランザクションの開始・コミットのタイミング次第 で、トランザクションの開始順・コミット順のどちらで 並べて見ても成功→失敗→成功のパターンがある →「順番」が重要なケースでは、Aurora DSQL とは別にキューの サービスを併用することも検討 49

50.

ところで:オートコミットは競合する? ● 結論からいうと「競合する」 ● そのため、確実に変更を行うためには競合時のリトライ は必須 50

51.

【参考】Aurora DSQL での実行例 ・シェル A(更新用 .sqlファイルを用意) ~ $ vi update.sql UPDATE hoge.fuga SET val = val + 1 WHERE id = 1; (1000行繰り返す) ・シェル A(カウントを 0に) postgres=> UPDATE hoge.fuga SET val = 0 WHERE id = 1; postgres=> SELECT * FROM hoge.fuga WHERE id = 1; id | val ----+----1 | 0 (1 row) 51

52.

【参考】Aurora DSQL での実行例 ・シェル A(オートコミット ONで更新用 .sqlファイルを読み込み) postgres=> \i ./update.sql psql:update.sql:127: ERROR: (OC000) (中略) psql:update.sql:998: ERROR: (OC000) change conflicts with another transaction, please retry: change conflicts with another transaction, please retry: ・シェル B(オートコミット ONで更新用 .sqlファイルをシェル Aと並行で読み込み) postgres=> \i ./update.sql psql:update.sql:1: ERROR: change conflicts with another transaction, please retry: (OC000) (中略) psql:update.sql:827: ERROR: change conflicts with another transaction, please retry: (OC000) 52

53.

【参考】Aurora DSQL での実行例 ・シェル B(更新が成功した回数を確認) postgres=> SELECT * FROM hoge.fuga WHERE id = 1; id | val ----+-----1 | 1044 (1 row) ※すべての UPDATEが成功していれば 2000になるはずだが、結果は 1044なので956回のUPDATEが競合で失敗 53

54.

競合対策 54

55.

競合対策 ● 主キーの競合を避ける ● データ行の変更をできるだけ避ける ○ 変更頻度を下げる ○ 変更対象行数をできるだけ少なくする ○ 変更を伴うトランザクションの実行時間を短くする ● 競合発生時にリトライする 55

56.

主キーの競合を避ける ● 挿入(INSERT)時の競合を防ぐ ● シーケンス値のような単調増加の値を避け、UUID(v4) の ような重複・衝突が発生しづらい値を使う ○ DSQL には SERIAL が実装されていない ○ 代用としての「テーブル最終行の主キーを +1 して使う」方法は 競合を誘発するので採用しない 56

57.

データ行の変更をできるだけ避ける ● 変更頻度を下げる ○ 可能ならイミュータブルなテーブル設計により挿入(INSERT)後 の変更を行わない(理想をいえば) ■ イミュータブルは無理でも、挿入後は更新せず削除のみにできないか? ○ 高頻度で特定データ行の変更(更新)が発生する設計を避ける ■ カウンターなど ■ 必要なら別のデータストアの使用を検討 57

58.

データ行の変更をできるだけ避ける ● 変更対象行数をできるだけ少なくする ○ データの変更サイクルを意識して適切にテーブル正規化を行う ■ 一度の更新(UPDATE)で複数行のデータ変更が必要なケースを減らす ■ そもそも DSQL には「1 トランザクション 3,000 行まで」の制限がある 58

59.

変更を伴うトランザクションの実行時間を短くする ● トランザクション中の無駄な待ち時間はできるだけ削減 する ○ BEGIN 後速やかに変更処理を行い、その後速やかに COMMIT / ROLLBACK ● 前述のとおり、DSQL には「1 トランザクション 3,000 行まで」の変更制限がある ○ トランザクションを適切に分割する 59

60.

競合発生時にリトライする ● アプリケーション側で実装する ○ 必要に応じてリトライ間隔に指数バックオフやジッタを導入 ○ 障害発生時にトラブルにならないよう、リトライ回数には上限を 設ける 60

61.

書き込みスキュー異常回避 61

62.

書き込みスキュー異常回避 ● 書き込みスキュー異常の例 ● 回避方法 62

63.

書き込みスキュー異常 ● 相互に同じデータを参照しつつ更新対象が重ならない並 行トランザクションの処理では、更新後データの矛盾が 生じる可能性がある ○ 例:〇〇pay 利用時に、家族全員の残高が合計 20 万円未満なら 利用者の口座に 2 万円分加算するプレゼント企画 → 2 人の家族がほぼ同時に〇〇pay を利用したらどうなるか? 63

64.

書き込みスキュー異常の例 ⚫BEGIN ⚫BEGIN ● ● ● ● ◎SELECT A ◎SELECT B → 100000・ 80000 ◎A+B<200000 →UPDATE A=A+20000 ◎SELECT A ◎SELECT B → 100000・ 80000 COMMIT⭕ ◎A+B<200000 →UPDATE B=B+20000 TxA COMMIT⭕ TxB 「A+B が 20 万未満なら +2 万して UPDATE」 ◎SELECT A (アプリケーションのロジックで判定) ◎SELECT B ⚫BEGIN → 120000・ SELECT は競合しない 100000 UPDATE も競合しない(対象行が別) たとえば A=10 万・B=8 万のときに、本来なら TxA の処理で A が 12 万になるだけ のはずが TxB が並行で進み競合しなかった結果、誤って B も 10 万に 64

65.

書き込みスキュー異常の例 ● これを回避するには? ○ OCC ではロックを掛けられない →矛盾が生じうる並行トランザクションを意図的に失敗させる 65

66.

回避方法 ⚫BEGIN ◎SELECT A ... FOR UPDATE ◎SELECT B ... FOR UPDATE →100000・80000 ⚫BEGIN ◎A+B<200000 →UPDATE A=A+20000 ◎SELECT A ... FOR UPDATE ◎SELECT B ... FOR UPDATE →100000・80000 COMMIT⭕ ◎A+B<200000 →UPDATE B=B+20000 TxA COMMIT❌ TxB ⚫BEGIN ● ◎SELECT A ◎SELECT B → 120000・ 80000 SELECT に ... FOR UPDATE を追加して A・B それぞれの行に変更フラグを立てる → TxB は競合扱い→ COMMIT 時に失敗❌(その後リトライしても B は 8 万のまま) 66

67.

【参考】Aurora DSQL での実行例 ・トランザクション A(テーブル準備〜初期データ挿入) postgres=> CREATE TABLE hoge.account(number INT PRIMARY KEY UNIQUE, name VARCHAR(100) NOT NULL, amount INT NOT NULL); postgres=> INSERT INTO hoge.account VALUES(10000001, '佐藤一郎 ', 100000); postgres=> INSERT INTO hoge.account VALUES(11000001, '佐藤二朗 ', 80000); postgres=> SELECT * FROM hoge.account; number | name | amount ----------+----------+-------10000001 | 佐藤一郎 | 100000 11000001 | 佐藤二朗 | 80000 (2 rows) 67

68.

【参考】Aurora DSQL での実行例 ・トランザクション A(開始〜 SELECT...FOR UPDATE) postgres=> BEGIN; postgres=*> SELECT * FROM hoge.account WHERE number = 10000001 FOR UPDATE; number | name | amount ----------+----------+-------10000001 | 佐藤一郎 | 100000 (1 row) postgres=*> SELECT * FROM hoge.account WHERE number = 11000001 FOR UPDATE; number | name | amount ----------+----------+-------11000001 | 佐藤二朗 | 80000 (1 row) 68

69.

【参考】Aurora DSQL での実行例 ・トランザクション B(開始〜 SELECT...FOR UPDATE) postgres=> BEGIN; postgres=*> SELECT * FROM hoge.account WHERE number = 10000001 FOR UPDATE; number | name | amount ----------+----------+-------10000001 | 佐藤一郎 | 100000 (1 row) postgres=*> SELECT * FROM hoge.account WHERE number = 11000001 FOR UPDATE; number | name | amount ----------+----------+-------11000001 | 佐藤二朗 | 80000 (1 row) 69

70.

【参考】Aurora DSQL での実行例 ・トランザクション A(合計20万未満なので 佐藤一郎の口座を +2万してコミット ) postgres=*> UPDATE hoge.account SET amount = amount + 20000 WHERE number = 10000001; postgres=*> COMMIT; postgres=> SELECT * FROM hoge.account; number | name | amount ----------+----------+-------10000001 | 佐藤一郎 | 120000 11000001 | 佐藤二朗 | 80000 (2 rows) ・トランザクション B(合計20万未満なので 佐藤二朗の口座を +2万してコミット →失敗) postgres=*> UPDATE hoge.account SET amount = amount + 20000 WHERE number = 11000001; postgres=*> COMMIT; ERROR: change conflicts with another transaction, please retry: (OC000) ※再実行したときには合計 20万なので条件を満たさず →加算せず終了 70

71.

まとめ 71

72.

● OCC を意識したアプリケーション設計を行う ○ 変更(書き込み)トランザクションの競合をできるだけ避ける ■ テーブル設計、一度に変更する行数、実行時間など ○ 競合時にはアプリケーションでリトライする ○ 実行順が重要なケースではキューのサービスを併用する ● 書き込みスキュー異常回避のためにあえて競合させる ○ データ間の矛盾が生じうるケースでは SELECT ... FOR UPDATE で対象行に変更フラグを立ててあえて競合させトランザクション を失敗させる 72