実行計画の見方
EXPLAIN PLAN FOR
SELECT o.id, c.name
FROM orders o
JOIN customers c ON o.cust_id = c.id
WHERE o.status = 'OPEN';
------------------------------------------
| Id | Operation | Cost |
------------------------------------------
| 0 | SELECT STATEMENT | 120 |
| 1 | HASH JOIN | 120 |
| 2 | TABLE ACCESS FULL | 80 |
| 3 | INDEX RANGE SCAN | 20 |
------------------------------------------
読むときの観点
- ネストは下から上へ実行される
- FULL SCAN は意図したものか?
- INDEX RANGE / UNIQUE / SKIP の別
- JOIN は NESTED / HASH / MERGE の別
- SORT (Disk) / WINDOW (Buffer)
- RowsとCostの見積りが現実と乖離していないか(統計の鮮度を疑う)
ロックと待機事象
同時実行制御のためにDBは様々なロックを取りますが、設計を誤ると待機やデッドロックの原因になります。
デッドロック対策の鉄則: 「常に同じ順序でロックを取る」。テーブルの更新順序をコーディング規約で決める。
よくある落とし穴
- コミットを忘れる: 長時間トランザクションがUNDO/ロックを圧迫し、他セッションを巻き込む。
- 1行ずつ処理(Loop): Round Tripが爆発。SET演算で一発処理を検討。
- 全件取得 → アプリ側filter: ネットワーク帯域・メモリを浪費。WHEREはDBに任せる。
- バインド変数を使わない: 毎回ハードパース。Shared Pool競合・実行計画ぶれの原因。
- SELECT *: 不要列の転送・INDEX Only Scanの機会損失。
- TRUNCATEとDELETE混同: TRUNCATEはDDL ⇒ コミット不可・トリガ非発火。
- NULL比較に「=」を使う:
= NULLは常に偽。IS NULLを使う。 - JOIN結果の重複: 意図しない多対多が発生。DISTINCTで隠さず原因を直す。
まとめ
シリーズ全体のまとめです。
- DBは「メモリで処理し、ログで永続化する」
- Optimizerは統計を元にコスト最小の計画を選ぶ
- INDEXは読み取りを高速化し、書込コストを払う
- REDOがコミットの本体。DBの永続性はここに依存
- Temporary Spaceは「メモリで足りない時の溢れ先」
- DB Linkは便利だが、ネット越し・分散COMMITの罠あり
- 設定は「メモリ・ファイル配置・ログ多重化・監査」
実行計画を読める開発者は、DBの中で何が起きているかを語れるようになる。
参考リソース
- 公式ドキュメント: Oracle Database Concepts / PostgreSQL Documentation / SQL Server Architecture / IBM Db2 Knowledge Center
- 古典の名著: 「データベース実践入門」「SQLパフォーマンス詳解」「Use The Index, Luke!」
- 実行計画の読み方: EXPLAIN PLAN (Oracle) / EXPLAIN ANALYZE (Postgres) / SET STATISTICS PROFILE (SQL Server)
- パフォーマンス分析: AWR / Statspack (Oracle) / pg_stat_statements (PG) / Query Store (SQL Server)