tp5.1内置partition不支持ym分表,必须手动实现getmonthtable()按月生成表名并分别查询。

单表过亿后直接上 TP5.1 的 partition 按日期分表?不行,它根本不认日期字段,只认 year、mod、id、md5 四种硬编码类型,且 year 类型只从时间戳里取年份,不支持 202607 这种年月格式。
TP5.1 的 partition 不支持按 Ym 分表,别被文档误导
官方 getPartitionTableName 方法里对 year 类型的处理是:date('Y', $value) - $rule['expr'] + 1,也就是说它只提取年份(如 2026),然后减去基准年再加 1。你传 '2026-07-01' 或 strtotime('2026-07-01'),结果都是 t_chat_2026,不是你想要的 t_chat_202607。
- 想实现
t_chat_202607这种命名,必须绕过partition(),自己重写getTableName()方法 -
partition(['create_time' => '2026-07-01'], 'create_time', ['type'=>'year'])会生成t_chat_2026,但你库里根本没有这张表,只有t_chat_202607 - 如果强行用
year类型 + 表名后缀为202607,查询时where('create_time', '>=', '2026-07-01')会触发全表 UNION,因为框架无法把条件映射到具体子表
手动封装 getMonthTable() 才能真正按月路由
必须在模型里自己算表名,不能依赖框架内置逻辑。核心是:时间字段必须是 DATETIME 或 TIMESTAMP 类型,且查询前显式限定范围,否则 MySQL 会扫描所有分表。
- 表名生成函数示例:
return $this->name . '_' . date('Ym', strtotime($time));,注意校验$time是否合法,避免date('Ym', false)返回197001 - 插入时调用:
$table = $this->getMonthTable($data['create_time']); Db::name($table)->insert($data); - 查询时不能只写
where('create_time', 'between', [$start, $end]),得先算出涉及哪些月份:$months = $this->getMonthsInRange($start, $end);,再循环查每张表 - 别用
UNION让 TP 拼,手写Db::raw("SELECT * FROM t_chat_202606 UNION ALL SELECT * FROM t_chat_202607"),否则LIMIT和ORDER BY全失效
Union 查询必须手写 raw,TP 的 union() 不跨物理表
TP5.1 的 union() 方法底层基于同一个 PDO 连接实例,只拼 SQL 字符串,不识别表是否真实存在或是否属于同一库。一旦你查 t_chat_202606 和 t_chat_202607,它就当普通字符串拼进去,MySQL 报错 Table 'db.t_chat_202606' doesn't exist 是大概率事件——因为你没建那张表,或表名大小写/下划线不一致。
- 查最近三个月,先生成三个表名:
['t_chat_202605', 't_chat_202606', 't_chat_202607'] - 每个表单独
select()->field(...)->where(...)->limit(20),再 PHP 层array_merge+usort排序 - 如果要
LIMIT 20 OFFSET 100,得每张表都查LIMIT 120,合并后截取,否则漏数据 - 千万别用
SELECT * FROM t_chat_*,TP 不解析通配符,*会被当字面量报语法错误
最易被忽略的是索引——每张分表都得单独建 INDEX idx_create_time (create_time),否则 EXPLAIN 显示 partitions: t_chat_202605,t_chat_202606,t_chat_202607,但实际扫描行数还是全部。表名、字段类型、索引、查询条件,四者缺一不可,少一个,分表就退化成负优化。











