单表过亿行怎么办?数据库分片与分区表选型指南

去年一个客户,订单表涨到了1.2亿行。查询越来越慢,写入也开始卡。他们尝试了加索引、读写分离、升级实例规格,能试的都试了。但查询还是慢,尤其跨月统计报表,跑一次要半小时。
技术负责人问我:“是不是要分库分表了?”
我问他:“你们查订单,主要按什么条件?”
“按用户ID,或者订单ID。”
“按时间查的多吗?”
“多,运营常看某段时间的订单。”
我说:“你的场景,分区表也许就够了,不一定直接上分库分表。”
这是很多DBA的思维定势:表大了就分库分表。但分库分表是手术,能不做尽量不做。
今天聊聊单表过亿时,分区表、分片、分库到底怎么选。
01 分区表:最简单的第一步
分区表是把一张逻辑大表,按某个规则拆成多个物理小表。对应用透明,查询时自动裁剪到相关分区。
分区方式:
范围分区:按时间范围,比如按月分。适合日志、订单、流水类数据。
列表分区:按枚举值,比如按省份分。
哈希分区:按哈希值均匀分布,适合无明显范围特征的数据。
优点:
对应用透明,不用改代码
查询带分区键时,只扫描相关分区,性能提升明显
管理方便,可单独删除旧分区
限制:
查询条件必须包含分区键,否则全表扫描所有分区
分区数有限(比如MySQL最多8192个)
跨分区查询(比如跨月统计)仍然慢
那家客户的订单表,按订单创建时间按月分区。查询“某用户的订单”带用户ID但不带时间,还是会扫所有分区。后来在联合索引里加了时间字段,查询时先过滤用户再限定时间范围,才用上分区裁剪。
02 分库分表:终极手段,不到万不得已不用
分区表解决不了,才考虑分库分表。
垂直分片:按业务模块拆库。订单库、用户库、商品库分开。不同库在不同实例。
水平分片:同一张表拆成多张结构相同的表,按分片键分布。
什么时候该上水平分片:
单表数据超过500万行,查询开始变慢
单表超过1000万行,索引效果明显下降
写入TPS达到上限,加从库没用
那家客户的订单表1.2亿行,已经远超阈值。但分区表还能撑,暂时没动。
03 分片键:选错等于没分
水平分片最核心、最容易选错的就是分片键。
好分片键的标准:
查询中高频出现
分布均匀,避免数据倾斜
值相对稳定,很少变化
常见选法:
按用户ID:用户维度的查询只扫一个分片
按订单ID:订单维度的查询只扫一个分片
按时间:适合按时间查询,但可能热点集中
那家客户如果做分片,可以考虑按用户ID哈希分16片。用户查自己的订单,只扫一个分片。运营按时间统计的查询,需要扫所有分片再聚合,可以单独用离线库解决。
04 分区表 vs 分片:怎么选
| 维度 | 分区表 | 水平分片 |
|---|---|---|
| 应用透明 | 是 | 否,需改代码 |
| 查询条件要求 | 需带分区键 | 需带分片键 |
| 跨分区/跨分片查询 | 慢 | 更慢 |
| 单表上限 | 千万级 | 百亿级 |
| 运维复杂度 | 低 | 高 |
| 回表复杂度 | 低 | 高 |
| 适用场景 | 数据有明显时间特征 | 数据无时间特征,按用户等维度查 |
选型建议:
数据有明显时间特征,查询常带时间范围 → 先上分区表
分区表扛不住了,或者查询条件不带时间 → 考虑水平分片
能分区表解决的,不要上分片
05 一个真实案例:3亿订单表的演进之路
某电商订单表三年涨到3亿行。他们经历了三个阶段:
第一阶段:分区表
按月分区,订单创建时间为分区键。查询单用户订单,带用户ID但不带时间,还是会扫所有分区。加了联合索引(user_id, create_time),查询时先定位用户,再限定时间范围,用上分区裁剪。撑了1.5亿行。
第二阶段:历史归档
把超过一年的订单迁到历史库,当前库只保留一年数据。查询一年前订单走历史库。当前库数据降到5000万行,性能恢复。
第三阶段:水平分片
业务继续增长,当前库也扛不住了。按用户ID哈希分16片,用户维度的查单只扫一个分片。运营统计报表不查分片库,改走离线数仓。
现在订单表已经5亿行,分片后单分片约3000万行,查询毫秒级。
技术负责人说:“分区表给我们扛了两年,分片是最后一步。能晚分就晚分,能不分就不分。”
写在最后
单表过亿,别急着上分库分表。先看能不能用分区表。分区表对应用透明,实施成本低,能解决大部分问题。
那家电商的运维负责人后来总结了一个决策顺序:
“先优化索引,再上读写分离,再加缓存,再试分区表,最后才分库分表。千万不要一步跳到分片。”
你的表过亿了,分区表试过吗?