22.

PHP データベース接続完全ガイド (PDO/mysqli)

編集
この記事の要点
  • PHP で MySQL に繋ぐ主要な API は PDOmysqli の 2 種類
  • 新規開発は PDO 推奨: MySQL / PostgreSQL / SQLite / SQL Server を同じ API で扱える
  • 接続時は必ず PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION を設定して例外で受け取る
  • プリペアドステートメント (prepare + execute) で SQL インジェクション対策
  • PHP-FPM 環境では 永続接続 (PDO::ATTR_PERSISTENT) より接続プーラ (ProxySQL等) が安全

PHP で DB に接続する方法の全体像

PHP には大きく分けて 3 つの DB 接続 API があります。

API対応 DB状態推奨度
PDOMySQL/PostgreSQL/SQLite/SQL Server/Oracle 他標準・継続開発中★★★ 新規開発の第一選択
mysqliMySQL / MariaDB のみ標準・継続★★ MySQL 固有機能を使う場合
mysql_*MySQL のみPHP 7.0 で削除済使用禁止

PDO で MySQL に接続

<?php
$dsn = 'mysql:host=127.0.0.1;port=3306;dbname=app;charset=utf8mb4';
$user = 'app_user';
$pass = 'secret';

$options = [
    PDO::ATTR_ERRMODE            => 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 で接続する場合

<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

try {
    $mysqli = new mysqli('127.0.0.1', 'app_user', 'secret', 'app', 3306);
    $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_PERSISTENTPHP-FPM ワーカ単位で接続を再利用セッション変数が残る・MySQL 側の接続数が増えがち
ProxySQLPHP と MySQL の間に挟む TCP プロキシ本番運用での定番。クエリルーティングも可
RDS ProxyAWS のマネージドプールLambda / ECS Fargate で必須に近い
PgBouncerPostgreSQL 用のプールセッションプール / トランザクションプール / ステートメントプール

セキュリティのベストプラクティス

  • 接続情報は環境変数 (.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) の導入を検討。

編集
Post Share
子ページ
  1. PDO(PHP Data Objects)
同階層のページ
  1. 基本事項
  2. HTMLへの埋め込み
  3. 変数
  4. 可変変数
  5. 定数
  6. データ型
  7. キャスト
  8. エスケープ文字
  9. 配列
  10. 演算子
  11. 代入の際の注意点
  12. 条件分岐
  13. 繰り返し処理
  14. クラスとインスタンス
  15. コンストラクタ
  16. 関数
  17. スーパーグローバル変数
  18. スコープ
  19. staticについて
  20. yieldについて
  21. ファイルのアップロード方法
  22. DB接続方法
  23. SQL実行方法
  24. カプセル化の具体例
  25. 継承の構文
  26. オーバーライド
  27. ポリモーフィズム(多様性)の具体例
  28. 抽象クラス・メソッドの構文と具体例
  29. GET通信
  30. try catchで全てのエラーを拾う方法

最近更新/作成されたページ