如何通过ThinkPHP进行数据库主从切换演练及故障转移测试详解?
- 内容介绍
- 文章标签
- 相关推荐
本文共计947个文字,预计阅读时间需要4分钟。
ThinkPHP 5.1 的主从读写分离并非配置了就自动切换,必须显式开启部署模式和读写分离开关。常见现象包括明确写了多个数据库配置,代码示例如下:
关键配置项只有两个:
-
deploy => 1:启用分布式部署(主从/集群的基础开关) -
rw_separate => true:开启读写分离(注意不是rw_separate => 1,布尔值写错会导致静默失效)
其他如 master_num、slave_no 属于进阶控制,非必需。漏掉 deploy 或类型写错,整个主从逻辑直接跳过,TP 会退化为单库操作。
如何强制走从库?用 db('slave') 或 ->master(false)
默认情况下,SELECT 语句由 TP 自动路由到从库,但这个行为不可见、难验证。演练时需要确定性触发从库访问,避免“以为走了从库,其实没走”。
立即学习“PHP免费学习笔记(深入)”;
-
db('slave'):直连指定的从库配置(需在database.php中定义名为slave的独立配置) -
Db::table('user')->master(false)->select():强制禁用主库,走从库池(配合slave_count轮询) -
Db::connect(['dsn' => 'mysql:host=192.168.10.22;dbname=test']):临时绕过配置,直连特定 IP 验证网络与权限
注意:->master(false) 在事务中会被忽略——TP 认为事务内所有操作必须在同一节点,这是合理限制,但容易被忽略导致演练误判。
故障转移怎么测?关主库后看 Connection refused 是否被拦截
真正的故障转移不是“主挂了自动切”,而是“主不可达时,读操作降级走从库,写操作抛出可捕获异常”。演练重点不是等它自动恢复,而是确认失败路径是否可控。
- 停掉主库 MySQL 服务(
systemctl stop mysqld或杀进程),再执行Db::table('user')->insert([...]) - 预期结果:抛出
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 本身不提供全自动故障转移(即写操作 fallback 到从库),那是违反数据一致性的,别被“高可用”宣传误导。
从库延迟导致数据不一致?lastInsertId() 和 find() 必须走主库
演练中常犯的错:在插入后立刻用 select 查刚写入的数据,却没意识到从库可能延迟几秒,查不到或查到旧值。
-
Db::table('user')->insertGetId()返回的是主库 ID,但后续Db::table('user')->find($id)默认走从库 → 可能查不到 - 解决方案:显式指定主库查询
Db::table('user')->master(true)->find($id) - 同理,
lastInsertId()、update、delete后的校验查询,都应加->master(true)
这不是 TP 的 bug,是主从架构固有约束。演练时不模拟延迟、不验证跨库一致性,等于白测。
本文共计947个文字,预计阅读时间需要4分钟。
ThinkPHP 5.1 的主从读写分离并非配置了就自动切换,必须显式开启部署模式和读写分离开关。常见现象包括明确写了多个数据库配置,代码示例如下:
关键配置项只有两个:
-
deploy => 1:启用分布式部署(主从/集群的基础开关) -
rw_separate => true:开启读写分离(注意不是rw_separate => 1,布尔值写错会导致静默失效)
其他如 master_num、slave_no 属于进阶控制,非必需。漏掉 deploy 或类型写错,整个主从逻辑直接跳过,TP 会退化为单库操作。
如何强制走从库?用 db('slave') 或 ->master(false)
默认情况下,SELECT 语句由 TP 自动路由到从库,但这个行为不可见、难验证。演练时需要确定性触发从库访问,避免“以为走了从库,其实没走”。
立即学习“PHP免费学习笔记(深入)”;
-
db('slave'):直连指定的从库配置(需在database.php中定义名为slave的独立配置) -
Db::table('user')->master(false)->select():强制禁用主库,走从库池(配合slave_count轮询) -
Db::connect(['dsn' => 'mysql:host=192.168.10.22;dbname=test']):临时绕过配置,直连特定 IP 验证网络与权限
注意:->master(false) 在事务中会被忽略——TP 认为事务内所有操作必须在同一节点,这是合理限制,但容易被忽略导致演练误判。
故障转移怎么测?关主库后看 Connection refused 是否被拦截
真正的故障转移不是“主挂了自动切”,而是“主不可达时,读操作降级走从库,写操作抛出可捕获异常”。演练重点不是等它自动恢复,而是确认失败路径是否可控。
- 停掉主库 MySQL 服务(
systemctl stop mysqld或杀进程),再执行Db::table('user')->insert([...]) - 预期结果:抛出
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 本身不提供全自动故障转移(即写操作 fallback 到从库),那是违反数据一致性的,别被“高可用”宣传误导。
从库延迟导致数据不一致?lastInsertId() 和 find() 必须走主库
演练中常犯的错:在插入后立刻用 select 查刚写入的数据,却没意识到从库可能延迟几秒,查不到或查到旧值。
-
Db::table('user')->insertGetId()返回的是主库 ID,但后续Db::table('user')->find($id)默认走从库 → 可能查不到 - 解决方案:显式指定主库查询
Db::table('user')->master(true)->find($id) - 同理,
lastInsertId()、update、delete后的校验查询,都应加->master(true)
这不是 TP 的 bug,是主从架构固有约束。演练时不模拟延迟、不验证跨库一致性,等于白测。

