laravel主从读写分离需手动配置mysql(主)和mysql_slave(从)连接,通过模型重写newquery或显式调用on()指定连接,事务内强制走主库,多从库轮询需自定义逻辑,主从延迟时需用主库查询或缓存兜底。

怎么在 database.php 里配主从连接
读写分离不是 Laravel 自动开启的功能,得手动定义多个连接,并在模型或查询时显式指定。核心是配置一个 mysql(主库)和一个 mysql_slave(从库),它们共用同一套驱动但指向不同服务器。
在 config/database.php 的 'connections' 数组里加:
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '192.168.1.10'), // 主库 IP
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'app'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
PDO::MYSQL_ATTR_SSL_CERT => env('MYSQL_ATTR_SSL_CERT'),
PDO::MYSQL_ATTR_SSL_KEY => env('MYSQL_ATTR_SSL_KEY'),
]) : [],
],
'mysql_slave' => [
'driver' => 'mysql',
'host' => env('DB_SLAVE_HOST', '192.168.1.11'), // 从库 IP
'port' => env('DB_SLAVE_PORT', '3306'),
'database' => env('DB_DATABASE', 'app'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => false, // 从库建议关 strict,避免某些兼容问题
'engine' => null,
],
- 两个连接必须使用相同
database、username、password,否则权限或数据不一致会出错 -
strict在从库设为false可避免因 MySQL 版本差异导致的 SQL mode 不兼容报错 - 别漏掉
charset和collation,否则中文乱码或索引失效
怎么让 select 自动走从库、insert/update/delete 走主库
Laravel 本身不自动识别语句类型做路由,所谓“自动读写分离”其实是靠封装好的查询构造器 + 连接切换逻辑实现的。最常用且可控的方式是重写模型的 newQuery 方法,或统一用 DB::connection('mysql_slave')->table(...) 显式调用。
推荐做法:在基类模型里加判断逻辑:
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model
{
public function newQuery($excludeDeleted = true)
{
$builder = parent::newQuery($excludeDeleted);
if ($this->isWriteOperation(request()->method())) {
return $builder->on('mysql');
}
return $builder->on('mysql_slave');
}
protected function isWriteOperation($method)
{
return in_array(strtoupper($method), ['POST', 'PUT', 'PATCH', 'DELETE']);
}
}
- 这个方案只适用于「请求级」读写分流,不适合命令行任务或队列中执行的查询
- 更稳妥的做法是业务层明确指定:
User::on('mysql_slave')->get()或DB::connection('mysql_slave')->table('users')->get() - 不要依赖
DB::transaction()自动切回主库——事务内所有操作都强制走主连接,哪怕你写了on('mysql_slave')也会被忽略
负载均衡怎么加:一主多从怎么轮询选从库
Laravel 原生不支持从库间负载均衡,需要自己扩展。常见做法是把多个从库定义成独立连接(如 mysql_slave_1、mysql_slave_2),再通过一个自定义连接管理器随机或轮询选取。
简单轮询示例(放在服务提供者或辅助函数里):
$slaves = ['mysql_slave_1', 'mysql_slave_2', 'mysql_slave_3'];
$index = (int) cache('slave_index', 0);
$selected = $slaves[$index % count($slaves)];
cache(['slave_index' => $index + 1]);
return DB::connection($selected)->table('users')->get();
- 用
cache存轮询序号,比每次rand()更可控,也避免单次请求内多次查询打到不同从库造成数据不一致 - 如果从库有延迟,别盲目轮询——先加
SHOW SLAVE STATUS检查Seconds_Behind_Master,超阈值就跳过该节点 - 生产环境建议用中间件或数据库代理(如 ProxySQL、MaxScale)做真正的连接层负载,PHP 层只负责发请求
为什么 DB::transaction 里查不到刚插入的数据
这是主从延迟 + 连接未对齐导致的典型问题。你在事务里 insert 到主库,紧接着用 on('mysql_slave') 去查,但此时从库还没同步完,自然查不到。
- 事务内所有查询默认走主连接,哪怕你写了
on('mysql_slave')也会被强制覆盖——这是 Laravel 的硬编码行为 - 如果确实需要“写后立刻读”,必须用主库连接:
DB::connection('mysql')->table(...)->get() - 不要试图在事务里切换连接来绕过这个问题,Laravel 会抛出
LogicException: Database transactions aren't supported with multiple connections. - 高并发下这种强一致性需求,往往得结合缓存(如 Redis)兜底,而不是依赖数据库实时同步
read_only=ON 的从库可能被误写,而没做 Seconds_Behind_Master 检查的应用会在延迟高峰时大量返回旧数据。










