ThinkPHP本身不提供双机热备自动故障转移能力,仅支持应用层读写分离与手动连接切换;真正的HA需数据库集群或系统级工具实现,TP仅负责连接失败捕获、降级读取等兜底逻辑。

ThinkPHP 本身不提供双机热备(HA)级别的自动故障转移能力,它只做应用层的读写分离与连接兜底。所谓“自动切换”,必须由数据库集群(如 MHA、Orchestrator、PXC、KingbaseES HA)或系统级高可用工具(corosync + pacemaker)完成;ThinkPHP 能做的,是感知失败、降级读取、避免雪崩。
主从配置生效但 SELECT 仍走主库?检查 read_master 和 master(false)
TP6.0+ 默认关闭自动读路由,即使配了从库,Db::table('user')->select() 仍强制走主库——这是为事务一致性做的保守设计,不是 bug。
- 全局启用自动分流:在
config/database.php的连接配置中加'read_master' => true(注意是布尔值,不是字符串) - 单次强制走从库:用
Db::table('user')->master(false)->select(),master(false)是关键开关 - 事务内该调用无效:
Db::transaction(function () { Db::table('user')->master(false)->select(); });会静默退回到主库,别依赖它做演练验证 - 确认配置结构:TP6.1+ 要求从库必须嵌套在
'connections' => ['mysql' => [..., 'slave' => [...]]]下,平铺式'slave' => [...]会报Database config "slave" not found
主库宕机后写操作失败,怎么捕获并兜底?
TP 不会自动把 insert() 切到从库——从库不可写,强行切等于制造错误。真正要测的是:写失败是否可捕获、读是否能手动降级。
- 停掉主库(
systemctl stop mysqld),执行Db::table('log')->insert(['msg' => 'test']) - 预期抛出
PDOException,错误信息含Connection refused或Can't connect to MySQL server - 读操作兜底示例:
try { $data = Db::table('user')->select(); } catch (\PDOException $e) { if (strpos($e->getMessage(), 'Connection refused') !== false) { $data = db('slave')->table('user')->select(); } } - 注意:这个兜底逻辑需你主动编写,TP 不内置“故障时自动读从库”机制
多库切换时连接数暴涨?别在循环里反复调用 Db::connect()
Db::connect('slave') 每次都新建 PDO 实例,不复用、不回收。高频调用直接触发 Too many connections。
立即学习“PHP免费学习笔记(深入)”;
- 正确做法:提前赋值复用,如
$slaveDb = Db::connect('slave');,后续全部用$slaveDb->table()->select() - 模型里设
protected $connection = 'slave'仅在类加载时读取一次,运行中改配置无效 - 跨库关联(如
User::with('profile'))不会自动跳转:Profile 模型仍按自己声明的$connection执行,无法靠一个配置统一切换 - 迁移命令
php think migrate:run完全无视模型的$connection,只认database.default
双机热备 ≠ ThinkPHP 配置对了就能自动切换
真正的双机热备是基础设施层的事。MySQL 主从复制延迟、VIP 漂移、WAL 同步状态、脑裂防护……这些 TP 一概不参与。它只负责在连接建立失败时抛异常,或在你指定下读从库。
如果你正在用 corosync + pacemaker 管理 MySQL 集群,那 VIP 切换后,ThinkPHP 只需连那个固定虚拟 IP 就行;如果你靠 rsync + 自写脚本做文件同步,TP 连的仍是单点地址,根本没机会“切换”。最容易被忽略的一点:所有兜底逻辑(比如读从库、降级返回缓存、返回默认数据)都得你写在业务代码里,TP 的数据库组件不会替你决定“这时候该返回什么”。



















