ウェブエンジニア問題集
第8章

インデックス・トランザクション・SQLインジェクション対策

8
この章の目次開く

基本的なSQLを書けるようになったら、次に必要なのは「速く、安全に、壊さずに使う」ための知識です。 遅いクエリ、不完全な更新、SQLインジェクションは、実務で大きな障害や情報漏洩につながります。

この章では、インデックス、トランザクション、SQLインジェクション対策をまとめて扱います。

学習者学習者

SQLが動くことは確認したけど、遅いかどうか、安全かどうかってどう判断するの?

インデックスとは

インデックスは、テーブルから目的の行を探しやすくするための仕組みです。 本の索引のように、全ページを先頭から読む代わりに、目的の項目へ素早くたどり着けます。

CREATE INDEX idx_users_email
ON users (email);
sql

このインデックスがあると、次のような検索が速くなりやすいです。

SELECT id, name
FROM users
WHERE email = 'alice@example.com';
sql
インデックスは、検索条件や並び替えでよく使う列に対して、目的の行へ速く到達するための仕組みです。

インデックスが効きやすい場面

インデックスは、次のような列に向いています。

  • WHERE でよく使う列
  • JOIN の条件に使う列
  • ORDER BY でよく使う列
  • UNIQUE 制約を付けたい列
CREATE INDEX idx_orders_user_id
ON orders (user_id);
sql
SELECT id, ordered_at
FROM orders
WHERE user_id = 1
ORDER BY ordered_at DESC;
sql

複数列を組み合わせた複合インデックスもあります。

CREATE INDEX idx_orders_user_id_ordered_at
ON orders (user_id, ordered_at);
sql

複合インデックスでは、列の順番が重要です。 (user_id, ordered_at) のインデックスは、user_id で絞って ordered_at で並べるようなクエリに向いています。

インデックスを貼りすぎない

インデックスは検索を速くしますが、無料ではありません。

インデックスには次のコストがあります。

  • ディスク容量を使う
  • INSERTUPDATEDELETE が遅くなる
  • 不要なインデックスが増えると管理が難しくなる

データを更新するたびに、インデックス側も更新する必要があるためです。

EXPLAINで実行計画を見る

EXPLAIN を使うと、DBMSがどのようにクエリを実行しようとしているかを確認できます。

EXPLAIN
SELECT id, name
FROM users
WHERE email = 'alice@example.com';
sql

DBMSによって表示形式は違いますが、主に次の点を見ます。

観点見ること
テーブルの読み方全件走査か、インデックスを使っているか
想定行数どのくらいの行を読む見込みか
JOIN順序どのテーブルから処理するか
ソート追加の並び替えが発生しているか
遅いSQLを直すときは、勘でインデックスを貼る前に EXPLAIN で実行計画を確認します。
学習者学習者

インデックスを貼ればとにかく速くなるの?全部の列に貼ったほうがいい?

先生先生

インデックスは検索を速くするけど、INSERT・UPDATE・DELETEが遅くなる。まずEXPLAINで遅いクエリを特定してから、必要な列にだけ貼るのが正解だよ。

インデックス・トランザクション・セキュリティの知識は、安全な運用に欠かせません

トランザクションとは

トランザクションは、複数のSQLをひとまとまりの処理として扱う仕組みです。 すべて成功したら確定し、途中で失敗したら元に戻せます。

代表例は、ポイント送金です。

BEGIN;
 
UPDATE accounts
SET balance = balance - 1000
WHERE id = 1;
 
UPDATE accounts
SET balance = balance + 1000
WHERE id = 2;
 
COMMIT;
sql

途中で失敗した場合は ROLLBACK します。

BEGIN;
 
UPDATE accounts
SET balance = balance - 1000
WHERE id = 1;
 
-- ここで何か問題が起きた
 
ROLLBACK;
sql

この場合、最初の更新も取り消されます。

COMMITとROLLBACK

命令意味
BEGINトランザクションを開始する
COMMIT変更を確定する
ROLLBACK変更を取り消す

トランザクションが必要な場面は、複数の変更がセットで意味を持つときです。

  • 注文作成と在庫減算
  • 送金元の減額と送金先の増額
  • ユーザー作成とプロフィール作成
  • 複数テーブルにまたがるステータス更新
複数の更新が「全部成功するか、全部失敗するか」でなければならない処理にはトランザクションを使います。

ACID

トランザクションの性質はACIDとして説明されます。

性質意味
Atomicity原子性。全部成功か全部失敗
Consistency一貫性。制約を満たす状態を保つ
Isolation独立性。同時実行の影響を制御する
Durability永続性。確定した変更は失われない

最初からすべてを厳密に覚える必要はありません。 まずは「途中で失敗したときに中途半端な状態を残さないための仕組み」と理解すると使いやすいです。

ロックと同時更新

複数の処理が同じ行を同時に更新しようとすると、競合が起きます。 DBMSはロックを使って、同時更新による不整合を防ぎます。

たとえば在庫を減らす処理では、同時に注文が入ると在庫数がずれる可能性があります。

BEGIN;
 
SELECT stock
FROM products
WHERE id = 1
FOR UPDATE;
 
UPDATE products
SET stock = stock - 1
WHERE id = 1;
 
COMMIT;
sql

FOR UPDATE は、対象行を更新用にロックするために使われます。 対応や書き方はDBMSによって違うため、実務では利用DBMSの仕様を確認します。

学習者学習者

ORMを使っていればSQLインジェクションは気にしなくていい?

先生先生

ORMでも生SQLを文字列連結で書いたら危険。ORMのクエリビルダやプレースホルダ機能をちゃんと使うことが大事だよ。

SQLインジェクションとは

SQLインジェクションは、ユーザー入力をSQL文字列に直接埋め込むことで、意図しないSQLを実行される脆弱性です。

// 危険な例
const sql = `SELECT * FROM users WHERE email = '${email}'`;
js

ユーザーが次のような値を入力すると、SQLの構造が壊されます。

' OR '1' = '1

結果として、条件が常に真になり、全ユーザーが取得される可能性があります。

プレースホルダを使う

SQLインジェクション対策の基本は、プレースホルダやパラメータバインドを使うことです。

// 例: パラメータ化クエリ
const sql = 'SELECT id, name FROM users WHERE email = ?';
const params = [email];
js

SQL本文と値を分けて渡すことで、入力値はSQL構文ではなく「値」として扱われます。

ユーザー入力をSQL文字列に直接連結せず、必ずプレースホルダで渡します。

ORMやクエリビルダを使っていても、文字列連結でSQLを組み立てれば危険です。

// 危険: ORMを使っていても生SQLを連結している
const users = await db.query(
  `SELECT * FROM users WHERE name LIKE '%${keyword}%'`
);
js

プレースホルダを使います。

const users = await db.query(
  'SELECT * FROM users WHERE name LIKE ?',
  [`%${keyword}%`]
);
js

よくあるハマりどころ

インデックスを貼ったのに速くならない

インデックスを貼っても、クエリ条件やデータ分布によっては使われないことがあります。

  • 条件に関数をかけている
  • 前方一致ではない LIKE '%keyword%' を使っている
  • 複合インデックスの左側の列を使っていない
  • そもそも対象行が多すぎる

EXPLAIN で確認し、実際に使われているかを見る必要があります。

トランザクション内で外部APIを呼ぶ

トランザクション中に外部APIを呼ぶと、API応答待ちの間ロックを持ち続けることがあります。 DB更新に必要な処理だけをトランザクション内に入れ、外部通信は前後に分ける設計を検討します。

エスケープすれば十分だと思う

SQLインジェクション対策は、手作業のエスケープに頼るべきではありません。 DBドライバやORMが提供するパラメータ化の仕組みを使うのが基本です。

ちゃんと使うためのポイント

  • インデックスは検索やJOINを速くするが、更新コストもある
  • 遅いSQLは EXPLAIN で実行計画を見る
  • 複数更新を一体として扱う処理にはトランザクションを使う
  • COMMIT は確定、ROLLBACK は取り消し
  • ロックは必要だが、長く持たない
  • SQLインジェクション対策では、文字列連結ではなくプレースホルダを使う

ここまでで、SQLの基本構文、JOIN、集計、サブクエリ、設計、実務上の安全性までを一通り見ました。 クイズに戻るときは、まず SELECTWHEREJOINGROUP BYNULL、インデックス、トランザクションの違いを意識して解いてみましょう。

SQLクイズに挑戦するこの章で学んだSQLの知識を、4択クイズでアウトプットして定着させよう