在 PHP 面试中,ThinkPHP 框架的数据库操作几乎是必问环节,而“多数据库连接”与“读写分离”更是中高级岗位的高频考点。很多开发者平时只用过单库单表,一旦被问到“项目大了怎么分库”“主从延迟怎么处理”就答不上来。本文从面试实战角度出发,系统梳理 ThinkPHP 中多数据库连接与读写分离的配置方式、底层原理和常见追问,帮你从容应对。
一、为什么面试官爱问多数据库与读写分离
当系统数据量增长,单台数据库服务器会面临两个瓶颈:读压力和写压力。读多写少的业务(如内容站、电商商品页)通常采用读写分离,主库负责写,从库负责读;而业务模块之间耦合较重时,则可能按业务拆分多个独立数据库。
ThinkPHP 从 5.x 到 6.x/8.x 都原生支持这两项能力,面试官问这个问题,本质是想确认三件事:
- 你是否理解读写分离的配置结构和生效时机;
- 你是否知道主从延迟带来的数据不一致问题及应对方案;
- 你是否能区分多数据库连接与读写分离是两套不同的机制。
二、多数据库连接配置
多数据库连接的核心是配置多个 connections,每个连接有独立的数据库信息,然后在操作时动态切换。
以 ThinkPHP 6/8 的 config/database.php 为例:
return [
// 默认连接
'default' => 'mysql',
'connections' => [
'mysql' => [
'type' => 'mysql',
'hostname' => '127.0.0.1',
'database' => 'main_db',
'username' => 'root',
'password' => '',
'prefix' => 'tp_',
],
// 第二个业务库
'order' => [
'type' => 'mysql',
'hostname' => '127.0.0.1',
'database' => 'order_db',
'username' => 'root',
'password' => '',
'prefix' => 'od_',
],
],
];
使用时通过 Db::connect() 切换:
// 使用默认连接
Db::name('user')->select();
// 切换到 order 库
Db::connect('order')->name('order')->select();
// 模型内指定连接
class OrderModel extends Model
{
protected $connection = 'order';
}
面试要点:Db::connect() 返回的是一个连接对象,多次调用同一名称会复用连接。如果模型固定使用某个库,推荐在模型里用 $connection 属性声明,而不是每次手动切换,这样代码更清晰。
三、读写分离配置
读写分离在 ThinkPHP 中并不是单独一套 API,而是同一个连接下的 master/slave 配置。框架会自动判断当前 SQL 是读还是写,然后选择合适的服务器。
'mysql' => [
'type' => 'mysql',
'prefix' => 'tp_',
// 主库(写)
'hostname' => '192.168.1.10',
'database' => 'main_db',
'username' => 'write_user',
'password' => 'write_pwd',
// 从库(读),可配置多个
'slave' => [
[
'hostname' => '192.168.1.11',
'database' => 'main_db',
'username' => 'read_user',
'password' => 'read_pwd',
],
[
'hostname' => '192.168.1.12',
'database' => 'main_db',
'username' => 'read_user',
'password' => 'read_pwd',
],
],
// 读服务器负载均衡:1 随机 2 轮询
'deploy' => 1,
// 是否严格区分读写
'rw_separate' => true,
],
几个关键配置项的含义:
slave:从库列表,可配置多台,框架会按deploy策略选择。deploy:从库选择策略,1为随机,2为轮询。rw_separate:是否开启读写分离,设为true时框架才会自动路由。
四、底层如何判断“读”与“写”
这是面试中最容易拉开差距的地方。ThinkPHP 的读写分离由 PDOConnection 中的逻辑控制,核心判断依据是 SQL 语句类型:
SELECT、SHOW、DESC等归为读操作,走从库;INSERT、UPDATE、DELETE、REPLACE等归为写操作,走主库;- 事务中的所有操作强制走主库,因为事务必须保证一致性。
对应的源码逻辑大致是:
protected function getRealConnection()
{
// 事务中或强制主库,直接返回 master
if ($this->transTimes > 0 || $this->isMaster) {
return $this->linkID;
}
// 根据 SQL 类型选择
if ($this->queryType === 'read') {
return $this->getSlaveConnection();
}
return $this->linkID;
}
结论:读写分离的切换对业务代码是透明的,你写 Db::name('user')->select() 时框架自动走从库,写 ->insert() 时自动走主库。
五、面试高频追问与应对
1. 主从延迟导致刚写入就读不到,怎么办?
这是读写分离最经典的问题。常见方案:
- 强制走主库:写入后需要立即读取的场景,用
Db::name('user')->master(true)->find()强制读主库; - 事务包裹:把写和紧随其后的读放在同一事务中,事务内强制走主库;
- 延迟感知:记录写入时间戳,短时间内该用户或该数据的读请求走主库;
- 业务容忍:对一致性要求不高的场景(如列表页),接受短暂延迟。
2. 多数据库连接和读写分离能同时用吗?
可以,二者是正交的。每个 connection 内部都可以单独配置 slave,实现“多库 + 每库读写分离”的组合。
3. 从库挂了怎么办?
ThinkPHP 本身对从库故障的容错有限,生产环境通常配合中间件(如 MyCat、ProxySQL)或数据库中间层做故障转移,框架层面则可通过自定义连接类重写 getSlaveConnection 来增加降级逻辑。
4. 如何验证读写分离真的生效了?
开启调试模式后,通过 Db::getQueryLog() 或日志查看实际执行的连接,也可以分别在主从库上开启 general log 观察 SQL 落点。
六、总结
面试回答这类问题,建议按“配置结构 → 生效原理 → 问题应对”三层递进:先说清 connections 与 slave 的配置差异,再讲框架依据 SQL 类型和事务状态自动路由,最后补充主从延迟的解决方案。能把第三层讲透,基本就能拿到这道题的满分。
多数据库连接解决的是“数据放哪里”,读写分离解决的是“读写怎么分流”,理解这两句话,ThinkPHP 的数据库进阶考点就抓住了主干。
未经允许不得转载:任鹏个人博客 » ThinkPHP 面试精讲:多数据库连接与读写分离配置

