ThinkPHP 面试精讲:多数据库连接与读写分离配置

在 PHP 面试中,ThinkPHP 框架的数据库操作几乎是必问环节,而“多数据库连接”与“读写分离”更是中高级岗位的高频考点。很多开发者平时只用过单库单表,一旦被问到“项目大了怎么分库”“主从延迟怎么处理”就答不上来。本文从面试实战角度出发,系统梳理 ThinkPHP 中多数据库连接与读写分离的配置方式、底层原理和常见追问,帮你从容应对。

一、为什么面试官爱问多数据库与读写分离

当系统数据量增长,单台数据库服务器会面临两个瓶颈:读压力和写压力。读多写少的业务(如内容站、电商商品页)通常采用读写分离,主库负责写,从库负责读;而业务模块之间耦合较重时,则可能按业务拆分多个独立数据库。

ThinkPHP 从 5.x 到 6.x/8.x 都原生支持这两项能力,面试官问这个问题,本质是想确认三件事:

  1. 你是否理解读写分离的配置结构和生效时机;
  2. 你是否知道主从延迟带来的数据不一致问题及应对方案;
  3. 你是否能区分多数据库连接与读写分离是两套不同的机制。

二、多数据库连接配置

多数据库连接的核心是配置多个 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 面试精讲:多数据库连接与读写分离配置

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏