ThinkPHP 的 partition() 方法仅替换表名,不自动处理时间范围查询和软删除逻辑。需手动添加 WHERE 时间条件,并确保分表结构与主表完全一致,否则会导致查询失效或性能问题。

ThinkPHP 模型 partition 方法不自动处理时间范围查询
直接调用 partition() 并不会帮你推导月份、拼接表名或过滤时间字段。它只负责在查询前把 $this->name 替换成目标表名,其余全靠你手动控制。
常见错误是以为写了 partition('2024_05') 就能查出 5 月数据——其实它只是把原表名(比如 log)替换成 log_2024_05,但 WHERE 条件里如果没有显式写 create_time >= '2024-05-01' AND create_time ,结果可能跨月甚至全表扫描。
- 必须自己解析查询条件中的时间字段,生成对应月份标识(如
date('Y_m', strtotime('2024-05-10'))) -
partition()的第一个参数是表后缀,不是日期对象,也不接受格式化字符串模板 - 如果查询跨月(比如查 4 月到 6 月),得手动循环调用多次
partition(),再合并结果,框架不提供自动 UNION 支持
手动构造分表名时要注意命名一致性与 SQL 注入风险
很多人用 "log_" . date('Y_m', $time) 拼表名,但没校验 $time 是否合法,导致表名变成 log_2024_13 或 log_2024_00,查不到数据还报错。
更危险的是直接把用户传入的年月字符串拼进表名,比如 "log_" . input('month') —— 这等于把 SQL 表名交给前端控制,属于高危注入点。
立即学习“PHP免费学习笔记(深入)”;
- 固定用
date('Y_m', $timestamp)生成,且确保 $timestamp 是 int 类型的时间戳 - 若需支持用户指定月份,先用正则校验:
preg_match('/^\d{4}_\d{2}$/', $input),再检查月份是否在 01–12 范围内 - 表名最终要经过
Db::name()或模型的table()方法传入,不能直接拼进 SQL 字符串
where 条件中时间字段必须带索引且类型匹配
即使表名对了、分区也建好了,如果 create_time 字段是 VARCHAR 类型或者没加索引,MySQL 无法做分区裁剪(Partition Pruning),还是会扫所有子表。
典型现象:查 log_2024_05 表,EXPLAIN 显示 partitions: log_2024_01,log_2024_02,...,log_2024_12,说明没裁剪成功。
-
create_time必须是DATETIME或TIMESTAMP,不能是字符串 - WHERE 中的值必须是确定的常量或参数绑定值,避免函数包裹(如
WHERE DATE(create_time) = '2024-05-01'会失效) - 建议用范围查询:
create_time >= '2024-05-01 00:00:00' AND create_time
TP6 模型中 partition() 的调用时机很关键
partition() 必须在执行查询前最后一刻调用,且不能被后续链式方法覆盖。比如 $model->where(...)->partition(...)->select() 是安全的;但 $model->partition(...)->where(...)->select() 可能因中间缓存或作用域问题失效。
另一个坑是用了软删除(useSoftDelete),软删字段判断逻辑会在 partition() 之后注入,可能导致最终 SQL 查的是 log_2024_05 表,但 WHERE 里多了一个 delete_time IS NULL,而该字段在分表中未必存在或未同步结构。
- 推荐写法:
$model->partition($suffix)->where($where)->select(),where 在 partition 后 - 分表结构必须和主表完全一致,包括软删除字段、索引、字符集
- 测试时用
getLastSql()看最终生成的 SQL,确认表名和 WHERE 都符合预期


















