Laravelで複数データベース接続を運用する:接続定義・読み書き分離・テナント分割まで
- 作成日 2026.03.04
- その他
Laravelは標準で複数DB接続を扱える。単に「接続を増やす」だけでなく、用途(参照専用DB、分析DB、外部システムDB、テナント別DB)ごとに責務を分離し、マイグレーション・トランザクション・キュー・テスト・監視まで含めた運用設計に落とし込むと事故が減る。ここでは config/database.php と Eloquent / Query Builder を軸に、複数接続を“壊れない形”で管理する手順をまとめる。
- 1. 複数DBが必要になる代表パターンを整理する
- 2. 接続定義の基本:config/database.php に connections を追加する
- 3. .envの分離:接続ごとに環境変数プレフィックスを揃える
- 4. クエリ単位で接続を指定する:DB::connection()
- 5. Eloquentで接続を固定する:Modelの $connection を使う
- 6. 動的に接続を切り替える:on() / setConnection() を使う
- 7. 読み書き分離(Read/Write Splitting):read / write を設定して自動振り分けする
- 8. トランザクション:必ず同一接続で完結させる
- 9. マイグレーションの分離:接続別・パス別に管理する
- 10. キューとジョブ:実行環境で接続がズレないように固定する
- 11. テナント分割:テナントごとにDBを切り替える実務パターン
- 12. 接続のヘルスチェック:起動時ではなく“必要時に検知”する
- 13. ログと監視:どの接続で遅いのかを切り分ける
- 14. まとめ:Laravelで複数DBを安全に管理する要点
複数DBが必要になる代表パターンを整理する
・業務DB(書き込み)+参照専用レプリカ(読み取り)
・アプリDB(トランザクション重視)+分析DB(集計/BI)
・自社DB+外部プロダクトのDB(別スキーマ/別認証)
・マルチテナント(テナントごとにDB/スキーマ分割)
・段階移行(旧DBと新DBを並行稼働して切り替え)
このどれなのかで、接続の切り方と運用コストが変わる。
接続定義の基本:config/database.php に connections を追加する
Laravelの接続は connections 配下に増やせる。キー名が接続名になる(例:mysql、analytics、legacy)。
// config/database.php(抜粋イメージ)
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
// ...
],
'analytics' => [
'driver' => 'pgsql',
'host' => env('ANALYTICS_DB_HOST'),
'port' => env('ANALYTICS_DB_PORT', '5432'),
'database' => env('ANALYTICS_DB_DATABASE'),
'username' => env('ANALYTICS_DB_USERNAME'),
'password' => env('ANALYTICS_DB_PASSWORD'),
// ...
],
'legacy' => [
'driver' => 'mysql',
'host' => env('LEGACY_DB_HOST'),
'port' => env('LEGACY_DB_PORT', '3306'),
'database' => env('LEGACY_DB_DATABASE'),
'username' => env('LEGACY_DB_USERNAME'),
'password' => env('LEGACY_DB_PASSWORD'),
// ...
],
],
.envの分離:接続ごとに環境変数プレフィックスを揃える
接続が増えるほど、変数名の統一が効いてくる。例として analytics/legacy を追加する。
# .env(例)
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=app
DB_USERNAME=app
DB_PASSWORD=secret
ANALYTICS_DB_HOST=analytics-db
ANALYTICS_DB_PORT=5432
ANALYTICS_DB_DATABASE=analytics
ANALYTICS_DB_USERNAME=analytics
ANALYTICS_DB_PASSWORD=secret
LEGACY_DB_HOST=legacy-db
LEGACY_DB_PORT=3306
LEGACY_DB_DATABASE=legacy
LEGACY_DB_USERNAME=legacy
LEGACY_DB_PASSWORD=secret発生しやすい問題:
・config:cache 後に .env を更新しても反映されない(デプロイ手順に組み込む)
・接続名と環境変数がズレて、意図しないDBに接続する
クエリ単位で接続を指定する:DB::connection()
Query Builderを使う場合、接続を明示すればそのDBに対してSQLが発行される。
use Illuminate\Support\Facades\DB;
$rows = DB::connection('analytics')
->table('daily_metrics')
->whereDate('date', now()->toDateString())
->get();複数接続運用では「どの接続で実行しているか」を必ず見える化する(メソッドチェーンの先頭で固定)。
Eloquentで接続を固定する:Modelの $connection を使う
モデル単位で接続を固定すると、コードが散らかりにくい。
namespace App\Models\Analytics;
use Illuminate\Database\Eloquent\Model;
class DailyMetric extends Model
{
protected $connection = 'analytics';
protected $table = 'daily_metrics';
public $timestamps = false;
}
use App\Models\Analytics\DailyMetric;
$metrics = DailyMetric::whereDate('date', today())->get();発生しやすい問題:
・リレーション先のモデルが別接続のままで、意図しないDBへ飛ぶ
・同名テーブルが複数DBにあり、間違って更新してしまう(命名/接続固定を徹底)
動的に接続を切り替える:on() / setConnection() を使う
同じモデルクラスを、処理の文脈でDB切替したい場合に使う。
$user = (new \App\Models\User)->setConnection('legacy')
->newQuery()
->where('email', $email)
->first();
$users = \App\Models\User::on('legacy')->where('status', 'active')->get();
ただし、乱用すると「どこでどのDBか」が追えなくなる。基本はモデルで固定、例外として動的切替。
読み書き分離(Read/Write Splitting):read / write を設定して自動振り分けする
MySQL/PGのレプリカ構成では、読み取りはreadへ、書き込みはwriteへ振る設計がある。Laravelは接続定義でread/writeを持てる。
// config/database.php(概念例:mysql)
'mysql' => [
'driver' => 'mysql',
'read' => [
'host' => [env('DB_READ_HOST', '127.0.0.1')],
],
'write' => [
'host' => [env('DB_WRITE_HOST', '127.0.0.1')],
],
'sticky' => true,
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
// ...
],・sticky=true:書き込み直後の読み取りを同一接続(write側)に寄せて整合性問題を減らす
発生しやすい問題:
・レプリカ遅延で「更新したのに見えない」が起きる(sticky/設計で吸収)
・トランザクション中にreadへ飛んで不整合(トランザクション境界を厳密に)
トランザクション:必ず同一接続で完結させる
異なる接続を跨いで DB::transaction() しても、分散トランザクションにはならない。基本は「接続ごとにトランザクション」を徹底する。
use Illuminate\Support\Facades\DB;
DB::connection('mysql')->transaction(function () {
// mysql側の更新はここで完結
});
DB::connection('analytics')->transaction(function () {
// analytics側の更新はここで完結
});複数DBで整合が必要なら、アプリ側の冪等設計・補償トランザクション(後追い整合)を前提にする。
マイグレーションの分離:接続別・パス別に管理する
分析DBや外部DBにマイグレーションを当てる場合は、migrationファイルの置き場を分けると運用が楽。
# analytics用マイグレーションを別パスで実行する例
php artisan migrate --database=analytics --path=database/migrations/analytics
発生しやすい問題:
・デフォルト接続に当たってしまい、別DBにテーブルが作られる
・同名マイグレーションや依存順序が崩れる(パス分離で回避)
キューとジョブ:実行環境で接続がズレないように固定する
ジョブは非同期で別プロセス/別コンテナで走る。ジョブ内で接続を明示しないと、環境変数やデフォルト接続の差分で事故が起きる。
public function handle(): void
{
$row = \DB::connection('legacy')->table('users')->where('id', $this->legacyUserId)->first();
// 取り込んだ結果を mysql に保存
\DB::connection('mysql')->table('users')->updateOrInsert(
['external_id' => $row->id],
['name' => $row->name]
);
}テナント分割:テナントごとにDBを切り替える実務パターン
代表的な方式:
・DB分割(tenant_001, tenant_002 のようにDBが別)
・スキーマ分割(PostgreSQLのschema)
・テーブルに tenant_id を持たせる(単一DB)
DB分割を採る場合は、リクエスト単位で接続を切り替える仕組みが必要になる。
use Illuminate\Support\Facades\DB;
public function handle($request, \Closure $next)
{
$tenant = $request->header('X-Tenant');
config([
'database.connections.tenant.database' => "tenant_{$tenant}",
'database.default' => 'tenant',
]);
DB::purge('tenant'); // 既存接続を破棄
DB::reconnect('tenant'); // 新設定で再接続
return $next($request);
}注意点:
・接続プール/再接続のコストが増えるため、乱用しない
・テナント識別子をログに必ず残し、事故時の追跡性を確保
・接続名(tenant)を固定し、databaseだけ差し替える運用が管理しやすい
接続のヘルスチェック:起動時ではなく“必要時に検知”する
外部DB/分析DBは落ちる前提で、例外処理とリトライ方針を決める。接続確認を毎リクエストでやると遅くなる。
try {
DB::connection('analytics')->select('select 1');
} catch (\Throwable $e) {
// フォールバック(キャッシュ値、別経路、機能停止)
}ログと監視:どの接続で遅いのかを切り分ける
複数DBで遅いとき、「どの接続」「どのクエリ」かが分からないと詰む。
・DB接続名をログに出す(テナントIDも同様)
・遅いクエリ監視(スロークエリログ / APM)
・レプリカ遅延の可視化(read/write構成の場合)
・キュー実行時の接続先ログ(非同期は特に)
まとめ:Laravelで複数DBを安全に管理する要点
・複数DBの目的(レプリカ/分析/外部/テナント/移行)を先に固定
・connectionsを増やし、.envはプレフィックス統一で管理
・クエリ/モデルで接続を明示し、意図しない接続を排除
・read/write分離はstickyと整合性設計までセットで考える
・トランザクションは接続を跨がない(跨ぐなら補償設計)
・マイグレーション/ジョブ/テストは“接続指定漏れ”が事故要因なので仕組みで防ぐ
-
前の記事
Laravelのサービスコンテナを理解して設計を強くする:依存解決・バインド・スコープ運用まで 2026.03.02
-
次の記事
Laravel『TokenMismatchException』の原因と対処法 2026.03.05
コメントを書く