アンチパターンをいくつか紹介。
N+1クエリ問題
- 関連データを取得するために、ループの中で繰り返しクエリを発行してしまうこと
- 具体例
- 10件の注文を取得し、各注文に対して個別に
SELECT * FROM order_items WHERE order_id = ?を実行→合計11回のクエリ
- 10件の注文を取得し、各注文に対して個別に
- 問題点
- クエリ数がデータ量に比例して増加し、レイテンシが急激に悪化する
- DBへのコネクション負荷が増大する
- 対策
- JOINを使って1回のクエリで取得する
-
WHERE order_id IN (...)でまとめて取得する(バッチフェッチ) - ORMのeager loading機能を活用する(例:Railsのincludes、Laravelのwith、Djangoのprefetch_related)
列指定の省略(Implicit Columns)
-
SELECT * FROM tableのように列を指定せずに全列を取得すること - 問題点
- テーブルにカラムが追加・削除されたとき、アプリケーション側で想定しているカラム順序やデータ型が壊れる
- 不要なカラムまで取得するため、ネットワーク帯域とメモリを無駄に消費する
- インデックスのみで完結するクエリ(カバリングインデックス)が使えなくなり、パフォーマンスが低下する
- 対策
- 必要なカラムを明示的に列挙する:
SELECT id, name, email FROM users - ORM(オブジェクト関係マッピング)を使う場合もeager loadingの範囲を意識する
- 必要なカラムを明示的に列挙する:
暗黙の型変換(Implicit Type Conversion)
- WHERE句などで型が一致しないカラムと値を比較し、暗黙の型変換が発生するパターン
- 具体例
-
WHERE varchar_column = 12345(文字列カラムに数値リテラルを比較)
-
- 問題点
- インデックスが無視され、フルテーブルスキャンになる
- 予期しない比較結果(
'0012' = 12がtrueになる等)が生じる
- 対策
- 比較する値の型をカラムの型に合わせる:
WHERE varchar_column = '12345' - SQLの厳密モード(strict mode)を有効にする
- 比較する値の型をカラムの型に合わせる:
インデックスショットガン(Index Shotgun)
- 「とりあえずインデックスを貼れば速くなる」と考え、闇雲にインデックスを追加するパターン
- 問題点
- INSERT / UPDATE / DELETE のたびにインデックスの更新コストが発生し、書き込み性能が低下する
- ディスク容量を圧迫する
- オプティマイザが最適なインデックスを選択しにくくなる
- 対策
-
EXPLAIN ANALYZEで実際の実行計画を確認してからインデックスを追加する - 使用されていないインデックスを定期的に棚卸しする
- 複合インデックスの列順を意識する(選択度の高い列を先頭にする)
-
SQLインジェクション(SQL Injection)
- ユーザー入力を直接SQL文に連結してしまうパターン
- 具体例
-
"SELECT * FROM users WHERE name = '" + userInput + "'"のような文字列結合
-
- 問題点
- 悪意あるユーザーが任意のSQLを実行でき、データの漏洩・改ざん・削除が可能
- OWASP Top 10に常にランクインする深刻な脆弱性
- 対策
- プリペアドステートメント(バインドパラメータ)を必ず使用する
- ORM / クエリビルダを活用する
- 入力のバリデーション・エスケープを多層で行う(防御的プログラミング)