数据库分表分表处理海量跨境商品数据:数据库分表实战
什么时候必须考虑分表
如果你正在维护一个跨境商品表,随着SKU数量突破500万甚至千万,你会明显感觉到:查询变慢、写入锁竞争加剧、备份时间过长。
单表即使加了索引,也扛不住突发流量和复杂筛选。
这时就需要通过数据库分表将数据分散到多个物理表中,减轻单表压力。
两种主流分表策略怎么选
- 水平分表:按商品ID的哈希值取模(如
id % 8),将数据均匀分布到8张表中。适合按主键查询、写入密集的场景。跨境商品通常每个SKU独立,哈希法可以避免热点。 - 按区域分表:如果商品有明显的区域归属(如国内、北美、欧洲),直接按region字段分表。缺点是可能造成某些区域数据量大、其他区域闲置。
我的建议是:优先使用商品ID哈希水平分表,后期如果需要按区域查询,配合ES或Redis辅助。
动手实现:MySQL水平分表示例
假设原表结构如下:
CREATE TABLE goods (
id BIGINT PRIMARY KEY,
name VARCHAR(255),
category_id INT,
price DECIMAL(10,2),
region VARCHAR(10),
created_at DATETIME
);
我们根据id哈希建8张子表(goods_0 ~ goods_7):
CREATE TABLE goods_0 LIKE goods;
CREATE TABLE goods_1 LIKE goods;
-- ... 重复6次
插入数据时,应用层计算分表编号:
$tableIndex = $goodsId % 8;
$sql = "INSERT INTO goods_{$tableIndex} (id, name, ...) VALUES (?, ?, ...)";
查询单条商品同理。
如果业务需要批量查询,必须遍历所有分表(除非能缩小范围)。特别注意: 分表后不能直接使用SELECT * FROM goods,必须由应用层路由。
数据迁移:从单表到分表
如果原表已有大量数据,建议利用低峰期分批迁移:
- 确认原表自增ID范围,分批次(每次10万条)读取记录。
- 遍历每一条记录,计算目标分表编号,执行INSERT。
- 迁移过程中先写分表,保留原表作为备份。
- 迁移完成后,用计数对比和抽样校验数据一致性。
示例脚本(伪代码):
batch_size = 100000
offset = 0
while True:
rows = db.query(f"SELECT * FROM goods LIMIT {batch_size} OFFSET {offset}")
if not rows:
break
for row in rows:
tbl = f"goods_{row['id'] % 8}"
db.execute(f"INSERT INTO {tbl} VALUES (...) ", row)
offset += batch_size
注意:迁移期间原表不能有写入,否则数据遗漏。
可以通过停服或双写过渡。
避坑指南与验证方法
常见坑点
- 分表键选择不当:不要用
created_at这类连续值,会导致热点。 - 跨分表join:尽量避免,如果业务必须,考虑在应用层汇总或引入ES。
- 后期扩容难:哈希分表后如果需增加分表数,必须rehash所有数据。建议初始分表数设大一些(如32或64),避免频繁改动。
- 事务跨表:如果一次操作涉及多个分表,要配合分布式事务(XA或TCC),但大多数跨境场景单商品操作不跨表。
效果验证
- 查询性能:对比单表SELECT * from goods WHERE id=1000 和分表后的响应时间,通常降低到毫秒级。
- 写入吞吐:用工具压测并发写入,观察单表锁等待与分表后的TPS差异。
- 数据一致性:随机抽取若干分表记录,与原表备份对比。
总结
处理海量跨境商品数据时,数据库分表是成本较低、效果明显的方案。
核心在于选对分片键、设计合理的路由逻辑,并做好迁移验证。
如果你正面临单表瓶颈,不妨按本文思路从一个小规模分表方案开始尝试,逐步优化。
遇到异常时,优先检查分表路由代码和索引是否适配。