この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:5
ページ更新者:atom
更新日時:2026-06-11 07:07:02

タイトル: DB接続方法
SEOタイトル: 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 に接続

 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_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) の導入を検討。