タイトル: DB接続方法
SEOタイトル: PHP データベース接続完全ガイド (PDO/mysqli)
| この記事の要点 |
|
PHP で DB に接続する方法の全体像
PHP には大きく分けて 3 つの DB 接続 API があります。
| API | 対応 DB | 状態 | 推奨度 |
|---|---|---|---|
| PDO | MySQL/PostgreSQL/SQLite/SQL Server/Oracle 他 | 標準・継続開発中 | ★★★ 新規開発の第一選択 |
| mysqli | MySQL / MariaDB のみ | 標準・継続 | ★★ MySQL 固有機能を使う場合 |
| MySQL のみ | PHP 7.0 で削除済 | 使用禁止 |
PDO で MySQL に接続
PDO::ERRMODE_EXCEPTION, // 例外で受ける
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // 連想配列で返す
PDO::ATTR_EMULATE_PREPARES => false, // ネイティブ prepare
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
error_log('DB connect failed: ' . $e->getMessage());
http_response_code(500);
exit('DB error');
}
各オプションの意味
- ATTR_ERRMODE = ERRMODE_EXCEPTION: エラー時に
PDOExceptionをスロー。これを設定しないとサイレントに失敗する - ATTR_DEFAULT_FETCH_MODE = FETCH_ASSOC:
fetch()がカラム名キーの連想配列を返す - ATTR_EMULATE_PREPARES = false: PHP 側エミュレーションを切ってサーバ側 prepare を使う。プレースホルダが整数として扱われる等、型情報が正確になる
- MYSQL_ATTR_INIT_COMMAND: 接続直後に実行する SQL。文字コード固定に使う
プリペアドステートメント (SQL インジェクション対策)
// ❌ NG: 直接埋め込み (SQL インジェクション脆弱)
$email = $_POST['email'];
$pdo->query("SELECT * FROM users WHERE email = '$email'");
// ✅ 名前付きプレースホルダ
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email AND status = :status');
$stmt->execute([
':email' => $_POST['email'],
':status' => 'active',
]);
$user = $stmt->fetch();
// ✅ ? プレースホルダ (順序依存)
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ? AND status = ?');
$stmt->execute([$_POST['email'], 'active']);
$user = $stmt->fetch();
// 複数件取得
$stmt = $pdo->prepare('SELECT id, name FROM users WHERE created_at > ?');
$stmt->execute(['2025-01-01']);
foreach ($stmt as $row) {
echo $row['id'] . ': ' . $row['name'] . PHP_EOL;
}
// INSERT で採番された ID を取得
$stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
$stmt->execute(['Tanaka', 'tanaka@example.com']);
$newId = $pdo->lastInsertId();
トランザクション
try {
$pdo->beginTransaction();
$pdo->prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?')
->execute([1000, 1]);
$pdo->prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?')
->execute([1000, 2]);
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}
// ネストしたトランザクション (SAVEPOINT)
$pdo->beginTransaction();
try {
$pdo->exec('UPDATE foo SET x = 1');
$pdo->exec('SAVEPOINT sp1');
try {
$pdo->exec('UPDATE bar SET y = 2');
} catch (PDOException $e) {
$pdo->exec('ROLLBACK TO SAVEPOINT sp1');
}
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
}
mysqli で接続する場合
set_charset('utf8mb4');
} catch (mysqli_sql_exception $e) {
error_log($e->getMessage());
exit('DB error');
}
// プリペアドステートメント
$stmt = $mysqli->prepare('SELECT id, name FROM users WHERE email = ?');
$stmt->bind_param('s', $_POST['email']); // s=string, i=int, d=double, b=blob
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
echo $row['id'] . ': ' . $row['name'] . PHP_EOL;
}
// オブジェクト指向 と 手続き型の使い分け
$mysqli->close();
他 DB への接続 (PDO で統一)
// PostgreSQL
$pdo = new PDO('pgsql:host=127.0.0.1;port=5432;dbname=app', 'postgres', 'secret');
// SQLite
$pdo = new PDO('sqlite:/var/db/app.sqlite');
$pdo = new PDO('sqlite::memory:'); // メモリ DB
// SQL Server
$pdo = new PDO('sqlsrv:Server=localhost;Database=app', 'sa', 'secret');
// または ODBC 経由
$pdo = new PDO('odbc:Driver={ODBC Driver 18 for SQL Server};Server=localhost;Database=app', 'sa', 'secret');
// Oracle
$pdo = new PDO('oci:dbname=//localhost:1521/XEPDB1', 'app', 'secret');
Laravel での DB 接続 (内部は PDO)
// .env
// DB_CONNECTION=mysql
// DB_HOST=127.0.0.1
// DB_PORT=3306
// DB_DATABASE=app
// DB_USERNAME=app_user
// DB_PASSWORD=secret
use Illuminate\Support\Facades\DB;
// DB Facade (クエリビルダ)
$users = DB::table('users')->where('status', 'active')->get();
// 生 SQL
$users = DB::select('SELECT * FROM users WHERE email = ?', [$email]);
// Eloquent モデル
$user = User::where('email', $email)->first();
// 内部の PDO インスタンス取得 (低レベル操作)
$pdo = DB::connection()->getPdo();
接続プールについて
PHP はリクエストごとにプロセスが終了するモデルなので、Java や Node.js のような長寿命接続プールが直接は使えません。
| 方式 | 解説 | 注意 |
|---|---|---|
| PDO::ATTR_PERSISTENT | PHP-FPM ワーカ単位で接続を再利用 | セッション変数が残る・MySQL 側の接続数が増えがち |
| ProxySQL | PHP と MySQL の間に挟む TCP プロキシ | 本番運用での定番。クエリルーティングも可 |
| RDS Proxy | AWS のマネージドプール | Lambda / ECS Fargate で必須に近い |
| PgBouncer | PostgreSQL 用のプール | セッションプール / トランザクションプール / ステートメントプール |
セキュリティのベストプラクティス
- 接続情報は環境変数 (.env) で管理、リポジトリに含めない
- アプリケーション用ユーザは最小権限に絞る (DROP / GRANT は不要)
- 本番は TLS 接続を強制 (
PDO::MYSQL_ATTR_SSL_CA) - SQL は必ずプリペアドステートメント、文字列結合は禁止
- エラーメッセージをそのまま画面に出さない (情報漏洩)
FAQ
Q: PDO と mysqli、どちらを使うべき?
A: 新規開発は PDO。複数 DB 対応・抽象度が高い・標準的な記法が確立されている。MySQL 限定で get_result() 等の機能が必要なら mysqli。
Q: 接続が遅い
A: localhost 指定が UNIX ソケット経由になる。明示的に 127.0.0.1 を指定すると TCP 経由、その逆も。DNS 解決遅延の場合は IP 直書きで改善。
Q: 「too many connections」エラー
A: MySQL の max_connections 不足。一時的にはサーバ側で増やせるが、根本的にはコネクションプーラ (ProxySQL) の導入を検討。