laravel主库超时不会自动降级到从库,必须应用层显式探测健康状态并手动切换连接;db::timeout()仅控制语句执行超时,不触发连接路由切换,真正降级需封装查询入口先测主库再fallback至mysql.read。

主库超时不会自动降级到只读模式,Laravel 默认不提供该能力;必须在应用层显式拦截、判断、切换连接角色。
DB::select('SELECT 1') 超时后怎么切到从库执行查询
这不是 Laravel 自动行为,而是你得在每次写操作前主动探测主库健康状态。常见错误是直接用 DB::connection('mysql')->getPdo() 判断连接对象是否存在——它不反映真实连通性,PDO 实例可能还活着但网络已断。
- 真正有效的探测方式是执行轻量 SQL:
DB::select('SELECT 1')或DB::statement('DO 1'),捕获PDOException错误码2002(Connection refused)、2006(MySQL server has gone away)、HY000(超时类) - 不要在事务中做这个探测:一旦
DB::transaction()开启,连接已绑定主库,中途切库会报错 - 探测失败后,不能继续用
DB::table()->get(),必须显式指定从库连接:DB::connection('mysql.read')->table(...)->get() - 注意:如果你的配置里没定义
'read'子键(只写了'host'),那mysql.read连接根本不存在,会抛出InvalidArgumentException
为什么 DB::timeout(2)->table()->get() 不等于“主库超时就走从库”
DB::timeout() 是语句级超时,只控制单条 SQL 执行等待时间,不是连接路由开关。它不会让 Laravel 放弃主库、转而用从库重试,更不会影响后续查询的连接选择逻辑。
- 它底层调用的是 PDOStatement::execute() 的超时控制,仅对当前 query 生效
- 如果主库响应慢但未超时,
timeout(2)会等满 2 秒再失败;如果主库已宕机,通常connect_timeout(如PDO::MYSQL_ATTR_CONNECT_TIMEOUT)会先触发,约 3–5 秒,远早于timeout() - 想实现“超时即降级”,必须自己封装一层查询入口,例如:
SafeReader::query()->from('users')->get(),内部先测主库,失败则自动切mysql.read - 别把
timeout()和连接池的wait_timeout混淆:后者是 MySQL 服务端配置,控制空闲连接存活时间,PHP 层无法通过timeout()修改它
config/database.php 里怎么配才能支持运行时切读连接
关键不是加多少参数,而是结构必须满足 Laravel 读写分离的硬性约定。很多人配了多个 host 却依然全走主库,就是因为格式错了。
- 必须把从库地址放在
'read' => ['host' => ['192.168.1.10', '192.168.1.11']],不能写成'host' => ['192.168.1.10', '192.168.1.11'](这会被当作文本数组,Laravel 解析失败) -
'write'只能是单个 host 字符串,不支持数组;Laravel 不会从多个“主库”里选一个,挂了就是挂了 - 所有读操作(
select、count、exists)才走read配置;update、insert、delete强制走write,无例外 - 确保
.env中没覆盖掉DB_HOST等变量——否则config/database.php里的read/write结构再正确也无效
真正的降级难点不在“怎么切”,而在“切完怎么保持一致性”:比如用户刚提交订单失败,你切到只读模式返回列表页,但页面显示的订单数还是旧的。这种场景下,单纯切连接解决不了业务语义问题,得配合缓存失效、前端兜底提示或异步补偿。











