本文目录导读:

在 Laravel 中实现读写分离有多种方式,我来详细介绍几种常用方法:
配置文件方式(最简单)
第一步:配置数据库连接
在 .env 文件中配置:
# 主库(写) DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=main_db DB_USERNAME=root DB_PASSWORD=password # 从库(读)- 可配置多个 DB_READ_HOST1=127.0.0.1 DB_READ_HOST2=192.168.1.100 DB_READ_DATABASE=replica_db
第二步:修改 database.php 配置
在 config/database.php 中配置读写分离:
'mysql' => [
'driver' => 'mysql',
// 主库配置(写)
'read' => [
// 从库配置(读)- 可配置多个
[
'host' => env('DB_READ_HOST1', '127.0.0.1'),
'database' => env('DB_READ_DATABASE', 'main_db'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
],
[
'host' => env('DB_READ_HOST2', '127.0.0.1'),
'database' => env('DB_READ_DATABASE', 'main_db'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
],
],
// 写库配置
'write' => [
[
'host' => env('DB_HOST', '127.0.0.1'),
'database' => env('DB_DATABASE', 'main_db'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
],
],
'sticky' => true, // 是否启用粘性连接
'driver' => 'mysql',
'port' => env('DB_PORT', 3306),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
],
使用 DB Facade 手动指定
use Illuminate\Support\Facades\DB;
// 使用默认连接(会根据配置自动选择读写)
$users = DB::table('users')->get(); // 自动使用读库
// 强制使用主库(写)
$users = DB::connection('mysql')->table('users')->get();
// 手动指定使用读库
$users = DB::connection('mysql::read')->table('users')->get();
使用 Eloquent 模型方式
// User.php 模型
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
// 默认使用配置好的读写分离连接
protected $connection = 'mysql';
// 或者为模型指定特定的读写连接
protected $readConnection = 'mysql';
protected $writeConnection = 'mysql';
// 自定义查询方法,强制使用主库
public function scopeFromMaster($query)
{
$query->useWritePdo();
return $query;
}
}
// 使用方式
$users = User::fromMaster()->find(1); // 强制使用主库
$users = User::find(1); // 自动使用读库
高级配置:动态切换
// 创建自定义连接类
namespace App\Support;
use Illuminate\Database\Connection;
use PDO;
class ReadWriteConnection extends Connection
{
public function select($query, $bindings = [], $useReadPdo = true)
{
// 判断是否为写操作
if ($this->isWriteQuery($query)) {
$useReadPdo = false;
}
return parent::select($query, $bindings, $useReadPdo);
}
protected function isWriteQuery($query)
{
$query = trim(preg_replace('/\s+/', ' ', strtolower($query)));
return in_array(substr($query, 0, 6), ['insert', 'update', 'delete', 'create', 'alter', 'drop']);
}
}
中间件方式控制
// 创建读写分离中间件
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\DB;
class ReadWriteMiddleware
{
public function handle($request, Closure $next)
{
// 判断请求类型
if ($request->isMethod('POST') || $request->isMethod('PUT') || $request->isMethod('DELETE')) {
// 写操作强制使用主库
DB::connection('mysql')->statement('USE WRITE');
}
return $next($request);
}
}
事务处理注意事项
use Illuminate\Support\Facades\DB;
// 事务操作必须使用主库
DB::transaction(function () {
// 事务内的所有操作都在主库执行
$user = User::find(1);
$user->name = 'New Name';
$user->save();
// 即使读操作也会在主库执行
$otherUser = User::find(2);
});
// 或者手动指定使用主库
DB::beginTransaction();
try {
$user = User::useWritePdo()->find(1); // 强制使用主库
$user->save();
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
}
负载均衡策略
// 在 config/database.php 中可以配置负载均衡策略
'options' => [
'fetch' => PDO::FETCH_OBJ,
'emulate_prepares' => false,
'driver' => 'mysql',
// 自定义负载均衡算法
],
监控和日志
// 添加事件监听器
namespace App\Providers;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
class DatabaseServiceProvider extends ServiceProvider
{
public function boot()
{
// 监听每次查询
DB::listen(function ($query) {
if (app()->environment('local')) {
\Log::info($query->sql, $query->bindings);
}
});
// 可以统计读写分离的使用情况
DB::connection('mysql')->enableQueryLog();
}
}
最佳实践建议:
- 粘性连接(sticky):建议开启,避免同一请求内读写不一致
- 事务处理:事务操作必须在主库执行
- 查询优化:缓存频繁读取但不常变化的数据
- 监控告警:监控主从延迟,设置阈值告警
- 故障转移:配置从库故障时自动使用主库
- 测试环境:确保开发环境有完整的测试用例
// .env 配置示例 DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=main_db DB_USERNAME=root DB_PASSWORD=password DB_STICKY=true DB_READ_HOST1=192.168.1.101 DB_READ_HOST2=192.168.1.102 DB_READ_DATABASE=replica_db DB_READ_USERNAME=reader DB_READ_PASSWORD=readpassword
通过以上配置,Laravel 会自动将所有读操作路由到从库,写操作到主库,从而实现读写分离。