Laravel怎么实现数据库读写分离_Laravel配置多数据库连接负载均衡【实战】

2次阅读

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

Laravel怎么实现数据库读写分离_Laravel配置多数据库连接负载均衡【实战】

怎么在 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, ],
  • 两个连接必须使用相同 databaseusernamepassword,否则权限或数据不一致会出错
  • strict 在从库设为 false 可避免因 MySQL 版本差异导致的 SQL mode 不兼容报错
  • 别漏掉 charsetcollation,否则中文乱码或索引失效

怎么让 select 自动走从库、insert/update/delete 走主库

Laravel 本身不自动识别语句类型做路由,所谓“自动读写分离”其实是靠封装好的查询构造器 + 连接切换逻辑实现的。最常用且可控的方式是重写模型的 newQuery 方法,或统一用 DB::connection('mysql_slave')->table(...) 显式调用。

推荐做法:在基类模型里加判断逻辑:

use IlluminateDatabaseEloquentModel;  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_1mysql_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 检查的应用会在延迟高峰时大量返回旧数据。

text=ZqhQzanResources