分库分表需先验证真实瓶颈:innodb_row_lock_waits>100/小时、slow_queries日增超50条且未走索引、threads_connected>200或单表>2gb,满足两条才考虑垂直拆分,全满足且优化无效才水平分片。

数据量大了不是立刻分库分表,而是先确认它真成了瓶颈——很多项目在单表 500 万行、QPS 几百时就急着拆,结果把简单问题搞复杂,还引入路由错乱、跨表查不动、ID 冲突一堆新坑。
要不要分?先看这三组指标
别凭感觉,盯住 MySQL 的真实反馈:
-
Innodb_row_lock_waits持续 > 100/小时,说明写锁争抢严重 -
Slow_queries每天增长超 50 条,且EXPLAIN显示没走索引 -
Threads_connected长期 > 200,或单表Data_length> 2GB(InnoDB)
满足其中两条,再考虑垂直拆分;全满足且缓存+读写分离压不住,才进水平分片。TP 框架本身不带自动分片能力,所有“智能路由”都是你手动写的逻辑,别被文档里过时的 partition 配置误导。
按时间分表:表名、字段、索引一个都不能错
日志、订单这类强时间维度数据,最常用按月分表(如 order_202404),但错一个点就白干:
- 表名后缀必须严格校验:
preg_match('/^\d{6}$/', $suffix),拒绝2024_04或2024041 -
create_time字段类型必须是DATETIME或TIMESTAMP,不能是VARCHAR存 "2024-04-19" - 每张分表都得单独建索引:
ALTER TABLE order_202404 ADD INDEX idx_create_time (create_time); - 查询时必须显式传时间范围:
where('create_time', '>=', '2024-04-01 00:00:00'),不能只靠表名
否则 EXPLAIN 会显示扫描全部分表,partitions 列一堆名字,性能比单表还差。
按 ID 分片:取模只是起点,扩容才是真难点
用户、订单主表用 user_id 或 order_no 做分片键最稳,但别只写 $id % 8:
- 初始分片数建议设为 1024 这类 2 的幂,方便后续扩容(比如从 8 库扩到 16 库只需迁移一半数据)
- 避免用
date('Ym')当分片依据——时间字段天然热点,月底最后几小时写入全挤在一张表 - 全局 ID 必须脱离
AUTO_INCREMENT:Snowflake或号段模式(如 Leaf-segment),否则跨库插入主键冲突 -
Db::name('user_' . ($id % 8))是可行的,但别在控制器里裸写——封装进模型的getTableName(),否则关联查询、软删除全失效
最易忽略的是跨分片聚合:想查“最近 7 天总订单数”,PHP 得并发查 7 张表再 array_sum(),别指望 TP 的 union() 能跨物理表优化,它连语法都不支持。
分库配置和模型切换必须配对生效
TP6 支持多数据库连接,但“分库”和“分表”是两件事,混用会出问题:
- 分库配置写在
config/database.php的connections里,用Db::connect('mysql2')或模型里protected $connection = 'mysql2'; - 分表是模型层行为,靠重写
getTableName()或调用$this->table('user_123')实现,跟连接名无关 - 错误示范:
Db::connect('mysql2')->name('user')->partition(123)——partition()在 TP6 已废弃,且不识别连接上下文 - 安全红线:任何用户输入(如 URL 参数
?month=202404)拼表名前,必须过白名单校验,缓存合法后缀列表比每次正则更快
真正麻烦的从来不是怎么切,而是怎么让 join、count、事务、监控这些原本透明的事,在分片后依然可控——这些细节藏在每次 where() 和 table() 的调用顺序里,漏一个,线上就报 Table 'db.user' doesn't exist。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











