thinkphp分表分库需重写gettablename和gettableprefix、用scope延迟计算、自定义query类拦截写操作,并确保事务与连接一致性。

分表路由必须重写 think\Model 的 getTableName 和 getTablePrefix
ThinkPHP 默认不支持运行时动态分表,所有表名在模型初始化时就固化了。想按用户ID或时间分表,得在查询前把真实表名算出来,再让框架用它——关键就在覆盖这两个方法。
常见错误是只改 getTableName 却忽略 getTablePrefix,导致生成的完整表名多拼一次前缀(比如变成 tp_user_001_tp_user);或者在 initialize() 里硬编码表名,结果连 save() 都写错表。
-
getTableName返回不含前缀的物理表名(如user_001),别带数据库名 -
getTablePrefix必须返回空字符串或原始配置值,不能动态拼接,否则和getTableName冲突 - 如果用了读写分离,分表逻辑要同时作用于主从连接,否则查不到刚写入的数据
分库路由只能靠 Db::connect() 动态切换连接配置
ThinkPHP 没有内置“按条件选库”的路由层,Model 本身不感知库名。真正生效的是底层 Connection 实例——所以分库动作必须发生在模型执行 SQL 前一刻,且要确保事务、查询缓存、日志等上下文都绑定到新连接。
典型翻车场景:在控制器里调 Db::connect('db2'),但模型还是用默认连接;或者用 $model->db('db2') 但没重置 Query 实例,导致后续 where 条件仍走旧库。
- 推荐在模型的
base方法或scope中调用Db::connect($config)并返回新 Query 对象 - 连接配置不能直接复用全局配置数组,需深拷贝后修改
database字段,否则污染其他模型 - 跨库关联查询不可行,ThinkPHP 的
join不支持多库,得手动拆成多次查询
scope 是最轻量的分表分库入口,但别在里面做耗时计算
很多人把分表逻辑塞进 initialize(),结果每次 new Model 都执行一遍哈希或日期解析,哪怕这个实例最后根本没查库。正确姿势是用 scope——它只在真正发起查询前触发,且可被链式调用控制时机。
比如按月份分表,scopeMonth('202405') 可以缓存该月表名,下次同参数调用直接复用;但如果 scope 里写了 file_get_contents 或远程 HTTP 请求,就会卡住整个查询流程。
- scope 函数必须返回
$query,不能 return null 或 $this - 分表字段(如 user_id)若来自请求参数,务必在 scope 里校验合法性,避免 SQL 注入或无效表名
- 使用
with关联时,关联模型不会自动继承主模型的 scope,得单独指定
自定义 think\db\Query 子类才能拦截 insert/update 的表名
上面说的 getTableName 只影响 select,insert 和 update 会绕过模型直接走 Query 的 table() 方法。想让写操作也分表,必须继承 think\db\Query,重写 table() 并注入分表逻辑。
最容易漏的是批量插入:insertAll() 默认用模型类名当表名,根本不会调你的 getTableName;还有软删除的 delete(),它走的是 UPDATE 语句,表名来源和 select 不同路径。
- 在模型中通过
query属性指定自定义 Query 类,如protected $query = MyQuery::class -
table()方法里判断是否已设物理表名,避免重复计算;未设置时才按规则生成 - 注意
replace、duplicate等扩展语法,它们可能跳过常规 table 解析流程
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










