Slide 1

Slide 1 text

並行性の問題を防げ! 実践トランザクション入門 Shoichi Ochi (@occhi) PHPカンファレンス愛媛2026 2026-10-03

Slide 2

Slide 2 text

自己紹介 おち しょういち 越智 翔一 SmartBank, Inc. Software Engineer 出身: 愛媛県西条市 趣味: 筋トレ @ochi11181101 @sho-work 2

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

トランザクション 4

Slide 5

Slide 5 text

Introduction 「トランザクション」 複数の読み書きを、 1つの論理的な処理単位と して扱う仕組み。 トランザクション中のすべての読み書きは、 1つ の処理単位として扱われ、オールオアナッシン グの保証を提供してくれる。 つまり、すべて成功するか( commit)、すべて なかった(rollback)ことにするかしかない。 5

Slide 6

Slide 6 text

Introduction 「トランザクション」 複数の読み書きを、 1つの論理的な処理単位と して扱う仕組み。 トランザクション中のすべての読み書きは、 1つ の処理単位として扱われ、オールオアナッシン グの保証を提供してくれる。 複数のクエリを、 BEGIN ~ COMMITで囲む つまり、すべて成功するか( commit)、すべて なかった(rollback)ことにするかしかない。 6

Slide 7

Slide 7 text

今回はそんなトランザクションが、 複数実行された時に起こる問題の話 7

Slide 8

Slide 8 text

では、実際にどんな問題が起こる?🤔 8

Slide 9

Slide 9 text

簡単な例を見てみよう!💪💪 9

Slide 10

Slide 10 text

Introduction 並行性の問題の簡単な例 カウンタの同一レコード更新をしようとする 2つのトランザクションを考える * https://www.oreilly.co.jp/books/9784873118703/ 10

Slide 11

Slide 11 text

Introduction 並行性の問題の簡単な例 カウンタの同一レコード更新をしようとする 2つのトランザクションを考える BEGIN * https://www.oreilly.co.jp/books/9784873118703/ COMMIT 11

Slide 12

Slide 12 text

Introduction 並行性の問題の簡単な例 カウンタの同一レコード更新をしようとする 2つのトランザクションを考える BEGIN * https://www.oreilly.co.jp/books/9784873118703/ COMMIT 12

Slide 13

Slide 13 text

Introduction 並行性の問題の簡単な例 カウンタの同一レコード更新をしようとする 2つのトランザクションを考える Tx A Tx B * https://www.oreilly.co.jp/books/9784873118703/ 13

Slide 14

Slide 14 text

Introduction 並行性の問題の簡単な例 どちらのユーザーも現在のカウンタ +1したい よって、以下のようになっていて欲しい ・ユーザー 1のCOMMIT直後には 43 ・ユーザー 2のCOMMIT直後には 44 * https://www.oreilly.co.jp/books/9784873118703/ 14

Slide 15

Slide 15 text

Introduction 並行性の問題の簡単な例 1. Tx Aが現在のカウンタを読む( =42) * https://www.oreilly.co.jp/books/9784873118703/ 15

Slide 16

Slide 16 text

Introduction 並行性の問題の簡単な例 1. Tx Aが現在のカウンタを読む(=42) 2. Tx Bが現在のカウンタを読む( =42) * https://www.oreilly.co.jp/books/9784873118703/ 16

Slide 17

Slide 17 text

Introduction 並行性の問題の簡単な例 1. Tx Aが現在のカウンタを読む(=42) 3. カウンタを 42+1し、43でUPDATE 2. Tx Bが現在のカウンタを読む(=42) * https://www.oreilly.co.jp/books/9784873118703/ 17

Slide 18

Slide 18 text

Introduction 並行性の問題の簡単な例 1. Tx Aが現在のカウンタを読む(=42) 3. カウンタを42+1し、43でUPDATE 2. Tx Bが現在のカウンタを読む(=42) * https://www.oreilly.co.jp/books/9784873118703/ 4. カウンタを 42+1し、43でUPDATE 18

Slide 19

Slide 19 text

Introduction 並行性の問題の簡単な例 最終結果が 44ではなく、 43になってしまった! 😭 3. カウンタを42+1し、43でUPDATE 4. カウンタを 42+1し、43でUPDATE * https://www.oreilly.co.jp/books/9784873118703/ 19

Slide 20

Slide 20 text

むむむ・・・🤔 2020

Slide 21

Slide 21 text

どうやって防げばいいんやろか?🤔 2121

Slide 22

Slide 22 text

今日のトーク 22

Slide 23

Slide 23 text

今日紹介するトランザクション分離レベルとレース条件 23

Slide 24

Slide 24 text

今日紹介するトランザクション分離レベルとレース条件 24

Slide 25

Slide 25 text

今日のトーク 25

Slide 26

Slide 26 text

1. トランザクション分離レベル 26

Slide 27

Slide 27 text

トランザクション分離レベル

Slide 28

Slide 28 text

トランザクション分離レベル 28

Slide 29

Slide 29 text

分離レベルは「他のTxの変更が、いつから見えるか」の設定 29

Slide 30

Slide 30 text

他の Tx の変更が見え始めるタイミングは、分離レベルで違う 30

Slide 31

Slide 31 text

他の Tx の変更が見え始めるタイミングは、分離レベルで違う 31

Slide 32

Slide 32 text

他の Tx の変更が見え始めるタイミングは、分離レベルで違う 32

Slide 33

Slide 33 text

トランザクション分離レベル 33

Slide 34

Slide 34 text

Serializableの実現方法は 3 つある 34

Slide 35

Slide 35 text

Serializableの実現方法は 3 つあり、どれも見え方が異なる 35

Slide 36

Slide 36 text

完全な順次実行:そもそも同時に走らせない 36

Slide 37

Slide 37 text

完全な順次実行:そもそも同時に走らせない 37

Slide 38

Slide 38 text

Serializableの実現方法は 3 つあり、どれも見え方が異なる 38

Slide 39

Slide 39 text

ロックには、共有ロックと排他ロックがある 39

Slide 40

Slide 40 text

2PL:ロックを取って、競合する読み書きを待たせる 40

Slide 41

Slide 41 text

2PL:ロックを取って、競合する読み書きを待たせる 41

Slide 42

Slide 42 text

2PL:ロックを取って、競合する読み書きを待たせる 42

Slide 43

Slide 43 text

2PL:ロックを取って、競合する読み書きを待たせる 43

Slide 44

Slide 44 text

2PL:ロックを取って、競合する読み書きを待たせる 44

Slide 45

Slide 45 text

Serializableの実現方法は 3 つあり、どれも見え方が異なる 45

Slide 46

Slide 46 text

SSI(直列化可能なスナップショット分離):スナップショットから更新されていたらロールバックさせる 46

Slide 47

Slide 47 text

SSI(直列化可能なスナップショット分離):スナップショットから更新されていたらロールバックさせる 47

Slide 48

Slide 48 text

SSI(直列化可能なスナップショット分離):スナップショットから更新されていたらロールバックさせる 48

Slide 49

Slide 49 text

SSI(直列化可能なスナップショット分離):スナップショットから更新されていたらロールバックさせる 49

Slide 50

Slide 50 text

SSI(直列化可能なスナップショット分離):スナップショットから更新されていたらロールバックさせる 50

Slide 51

Slide 51 text

今日のトーク 51

Slide 52

Slide 52 text

2. 並行性の問題 52

Slide 53

Slide 53 text

並行性の問題の全体像 53

Slide 54

Slide 54 text

問題ごとに「どのレベルから防げるか」を表に埋めていく 54

Slide 55

Slide 55 text

同じ 1 行を、並行して読み書きする場合に起こる問題 55

Slide 56

Slide 56 text

ここからしばらくは、accounts テーブルで考える 56

Slide 57

Slide 57 text

Dirty Write:未コミットの値を、別のTxが上書きしてしまう 57

Slide 58

Slide 58 text

Dirty Write:未コミットの値を、別のTxが上書きしてしまう 58

Slide 59

Slide 59 text

Dirty Write:未コミットの値を、別のTxが上書きしてしまう 59

Slide 60

Slide 60 text

Dirty Write:未コミットの値を、別のTxが上書きしてしまう 60

Slide 61

Slide 61 text

Dirty Write:未コミットの値を、別のTxが上書きしてしまう 61

Slide 62

Slide 62 text

Dirty Write は、どの分離レベルでも防がれる 62

Slide 63

Slide 63 text

Dirty Write の防ぎ方:書き込む行のロックを COMMIT まで持つ 63

Slide 64

Slide 64 text

Dirty Read:未コミットの値を読んでしまう 64

Slide 65

Slide 65 text

Dirty Read:未コミットの値を読んでしまう 65

Slide 66

Slide 66 text

Dirty Read:未コミットの値を読んでしまう 66

Slide 67

Slide 67 text

Dirty Read:未コミットの値を読んでしまう 67

Slide 68

Slide 68 text

Dirty Read:未コミットの値を読んでしまう 68

Slide 69

Slide 69 text

Dirty Read は Read Uncommitted で起き、Read Committed 以上で防げる 69

Slide 70

Slide 70 text

Lost Update:古い値を前提に上書きして、更新が消える 70

Slide 71

Slide 71 text

Lost Update は Snapshot Isolation 以下で起きうる(DB による) https://www.postgresql.org/docs/current/transaction-iso.html 71

Slide 72

Slide 72 text

複数の行にまたがる整合性が崩れる問題 72

Slide 73

Slide 73 text

Read Skew:1つのTx内で、異なる時点のコミット済みデータを読んでしまう 73

Slide 74

Slide 74 text

Read Skew:1つのTx内で、異なる時点のコミット済みデータを読んでしまう 74

Slide 75

Slide 75 text

Read Skew:1つのTx内で、異なる時点のコミット済みデータを読んでしまう 75

Slide 76

Slide 76 text

Read Skew:1つのTx内で、異なる時点のコミット済みデータを読んでしまう 76

Slide 77

Slide 77 text

Read Skew:1つのTx内で、異なる時点のコミット済みデータを読んでしまう 77

Slide 78

Slide 78 text

Read Skew:1つのTx内で、異なる時点のコミット済みデータを読んでしまう 78

Slide 79

Slide 79 text

Read Skew は Read Committed 以下で起き、Snapshot Isolation 以上で防げる 79 79

Slide 80

Slide 80 text

ここからは budgets テーブルで考える 80

Slide 81

Slide 81 text

部署の予算:どちらも「あと 20 使える」と判断して、合計が上限を超える 81

Slide 82

Slide 82 text

部署の予算:どちらも「あと 20 使える」と判断して、合計が上限を超える 82

Slide 83

Slide 83 text

部署の予算:どちらも「あと 20 使える」と判断して、合計が上限を超える 83

Slide 84

Slide 84 text

部署の予算:どちらも「あと 20 使える」と判断して、合計が上限を超える 84

Slide 85

Slide 85 text

部署の予算:どちらも「あと 20 使える」と判断して、合計が上限を超える 85

Slide 86

Slide 86 text

部署の予算:どちらも「あと 20 使える」と判断して、合計が上限を超える 86

Slide 87

Slide 87 text

更新は消えていない。壊れたのは「読んだ前提」 87

Slide 88

Slide 88 text

Write Skew は「Observe → Decide → Mutate」の流れで起きる 88

Slide 89

Slide 89 text

Write Skew は「Observe → Decide → Mutate」の流れで起きる 89

Slide 90

Slide 90 text

Write Skew は「Observe → Decide → Mutate」の流れで起きる 90

Slide 91

Slide 91 text

2 つの Tx が同じ古い前提を観測すると、組み合わせた結果として不変条件が壊れる 91

Slide 92

Slide 92 text

同じ形の問題は、身近にいくつもある 92

Slide 93

Slide 93 text

会議室予約は bookings テーブルで考える 93

Slide 94

Slide 94 text

会議室予約:どちらも「重なる予約は 0 件」を見て、同じ時間に予約を入れる 94

Slide 95

Slide 95 text

会議室予約:どちらも「重なる予約は 0 件」を見て、同じ時間に予約を入れる 95

Slide 96

Slide 96 text

会議室予約:どちらも「重なる予約は 0 件」を見て、同じ時間に予約を入れる 96

Slide 97

Slide 97 text

会議室予約:どちらも「重なる予約は 0 件」を見て、同じ時間に予約を入れる 97

Slide 98

Slide 98 text

会議室予約:どちらも「重なる予約は 0 件」を見て、同じ時間に予約を入れる 98

Slide 99

Slide 99 text

Write Skew のうち、ロックすべき行がまだ存在しないケース 99

Slide 100

Slide 100 text

Write Skew は Serializable だけが防ぐ 100

Slide 101

Slide 101 text

デフォルトの分離レベルでは、どのような問題が起こる? 101

Slide 102

Slide 102 text

今日のトーク 102

Slide 103

Slide 103 text

3. 並行性の問題を防ぐトランザクション設計手段 103

Slide 104

Slide 104 text

デフォルトの分離レベルで残る問題に、自分で対処する 104

Slide 105

Slide 105 text

Read Skew の対策:一貫したスナップショットで読むか、読む行を変更させない 105

Slide 106

Slide 106 text

Lost Update の対策:アトミックな更新・悲観ロック・楽観ロック 106

Slide 107

Slide 107 text

Write Skew の対策:ロックすべき行があるか、まだないか 107

Slide 108

Slide 108 text

Write Skew の対策:ロックすべき行があるか、まだないか 108

Slide 109

Slide 109 text

Write Skew の対策:ロックすべき行があるか、まだないか 109

Slide 110

Slide 110 text

Write Skew の対策:ロックすべき行があるか、まだないか 110

Slide 111

Slide 111 text

Write Skew の対策:ロックすべき行があるか、まだないか 111

Slide 112

Slide 112 text

部署の予算は、前提にした行を FOR UPDATE でロックすれば防げる 112

Slide 113

Slide 113 text

部署の予算は、前提にした行を FOR UPDATE でロックすれば防げる 113

Slide 114

Slide 114 text

部署の予算は、前提にした行を FOR UPDATE でロックすれば防げる 114

Slide 115

Slide 115 text

部署の予算は、前提にした行を FOR UPDATE でロックすれば防げる 115

Slide 116

Slide 116 text

部署の予算は、前提にした行を FOR UPDATE でロックすれば防げる 116

Slide 117

Slide 117 text

Write Skew の対策:ロックすべき行があるか、まだないか 117

Slide 118

Slide 118 text

ユーザー名の登録は users テーブルで考える 118

Slide 119

Slide 119 text

対策①:制約で書けるなら、DB に任せる(ユーザー名の UNIQUE) 119

Slide 120

Slide 120 text

対策①:制約で書けるなら、DB に任せる(ユーザー名の UNIQUE) 120

Slide 121

Slide 121 text

対策①:制約で書けるなら、DB に任せる(ユーザー名の UNIQUE) 121

Slide 122

Slide 122 text

対策①:制約で書けるなら、DB に任せる(ユーザー名の UNIQUE) 122

Slide 123

Slide 123 text

ロックするテーブルが存在しない例 123

Slide 124

Slide 124 text

ロックするテーブルが存在しない例 124

Slide 125

Slide 125 text

ロックするテーブルが存在しない例 125

Slide 126

Slide 126 text

ロックするテーブルが存在しない例 126

Slide 127

Slide 127 text

ロックするテーブルが存在しない例 127

Slide 128

Slide 128 text

対策④:「ない」を、ロックできる行に置き換える(競合の実体化) 128

Slide 129

Slide 129 text

対策④:「ない」を、ロックできる行に置き換える(競合の実体化) 129

Slide 130

Slide 130 text

対策④:「ない」を、ロックできる行に置き換える(競合の実体化)

Slide 131

Slide 131 text

対策④:「ない」を、ロックできる行に置き換える(競合の実体化) 131

Slide 132

Slide 132 text

対策④:「ない」を、ロックできる行に置き換える(競合の実体化) 132

Slide 133

Slide 133 text

実体化の対象は、例ごとに「必ずある行」を選ぶ 133

Slide 134

Slide 134 text

Write Skew の対策は、悲観ロック → Serializable → 競合の実体化 の順に検討する 134

Slide 135

Slide 135 text

今日のトーク 135

Slide 136

Slide 136 text

4. デッドロックとその対策 136

Slide 137

Slide 137 text

デッドロック:お互いが相手のロックを待ち続けて、どちらも進めない 137

Slide 138

Slide 138 text

デッドロックが起きるメカニズム:2 つの Tx が逆の順番で行をロックする 138

Slide 139

Slide 139 text

デッドロックが起きるメカニズム:2 つの Tx が逆の順番で行をロックする 139

Slide 140

Slide 140 text

デッドロックが起きるメカニズム:2 つの Tx が逆の順番で行をロックする 140

Slide 141

Slide 141 text

デッドロックが起きるメカニズム:2 つの Tx が逆の順番で行をロックする 141

Slide 142

Slide 142 text

デッドロックが起きるメカニズム:2 つの Tx が逆の順番で行をロックする 142

Slide 143

Slide 143 text

デッドロックが起きるメカニズム:2 つの Tx が逆の順番で行をロックする 143

Slide 144

Slide 144 text

デッドロックは「起きにくくする」と「回復可能にする」の両方を設計する 144

Slide 145

Slide 145 text

回避①:ロックを取る順番をそろえる 145

Slide 146

Slide 146 text

回避①:ロックを取る順番をそろえる 146

Slide 147

Slide 147 text

回避①:ロックを取る順番をそろえる 147

Slide 148

Slide 148 text

回避①:ロックを取る順番をそろえる 148

Slide 149

Slide 149 text

回避①:ロックを取る順番をそろえる 149

Slide 150

Slide 150 text

回避①:ロックを取る順番をそろえる 150

Slide 151

Slide 151 text

回避②:Tx を短く保ち、ロックを持つ時間を減らす 151

Slide 152

Slide 152 text

回避③:インデックスで、ロックする行を絞る 152

Slide 153

Slide 153 text

余談: その他の回避方法 トランザクション分離レベルを下げるという手段もあるが、最終手段 https://kaigionrails.org/2026/talks/sho-work/ 153

Slide 154

Slide 154 text

余談: その他の回避方法 トランザクション分離レベルを下げるという手段もあるが、最終手段 後日登壇予定! https://kaigionrails.org/2026/talks/sho-work/ 154

Slide 155

Slide 155 text

回復:DB がデッドロックを検出した際の挙動 155

Slide 156

Slide 156 text

回復:デッドロックによって失敗した際のリトライの設計 156

Slide 157

Slide 157 text

回復:PHP(PDO)でリトライを書いた例 157

Slide 158

Slide 158 text

まとめ 158

Slide 159

Slide 159 text

並行性の問題が起こるトランザクションを設計する際の思考順序 159

Slide 160

Slide 160 text

並行性の問題が起こるトランザクションを設計する際の思考順序 160

Slide 161

Slide 161 text

並行性の問題が起こるトランザクションを設計する際の思考順序 161

Slide 162

Slide 162 text

並行性の問題が起こるトランザクションを設計する際の思考順序 162

Slide 163

Slide 163 text

並行性の問題を防げ! 実践トランザクション入門 Shoichi Ochi (@occhi) PHPカンファレンス愛媛2026 2026-10-03

Slide 164

Slide 164 text

Appendix - 参考資料 ● ● ● ● ● ● ● ● ● Designing Data-Intensive Applications(Martin Kleppmann, O'Reilly, 2017) ○ https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/ データ指向アプリケーションデザイン(オライリー・ジャパン , 2019) ○ https://www.oreilly.co.jp/books/9784873118703/ PostgreSQL: Transaction Isolation ○ https://www.postgresql.org/docs/current/transaction-iso.html PostgreSQL: Explicit Locking ○ https://www.postgresql.org/docs/current/explicit-locking.html PostgreSQL: Serialization Failure Handling ○ https://www.postgresql.org/docs/current/mvcc-serialization-failure-handling.html MySQL 8.4: Transaction Isolation Levels ○ https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html MySQL 8.4: InnoDB Locking ○ https://dev.mysql.com/doc/refman/8.4/en/innodb-locking.html MySQL 8.4: Locking Reads ○ https://dev.mysql.com/doc/refman/8.4/en/innodb-locking-reads.html MySQL 8.4: Deadlocks in InnoDB ○ https://dev.mysql.com/doc/refman/8.4/en/innodb-deadlocks.html 164