数据库分库分表实践
数据存储734 字预计 2 分钟阅读
探讨关系型数据库在大数据量下的分片设计,包括分库分表的维度、拆分方式及技术瓶颈解决。
引言
当单表数据量达到千万级甚至亿级以上时,即使加了完美索引,B+ 树的高度增加依然会导致查询变慢,数据库的 I/O、CPU 压力也会逼近物理极限。此时,必须通过分库分表(Sharding)从架构层面彻底打破单机性能瓶颈。
拆分方式:水平与垂直
1. 垂直拆分 (Vertical Splitting)
- 垂直分库:按业务模块拆分。例如将一个巨大的数据库拆分为“用户库”、“商品库”和“订单库”,降低单个数据库实例的并发压力。
- 垂直分表:将单表的大字段(如 text 类型的备注)或不常用的字段拆分到另一张关联表(主键关联)中。这可以提高主表的行密度,使得缓存在内存中的热点行数据更多,提升 I/O 效率。
2. 水平拆分 (Horizontal Splitting)
- 将同一张表的数据按某种规则分发到多张表或多个物理库中,每张表的结构完全一致。
- 常用分片键(Sharding Key)算法:
- Range(范围):按时间或 ID 区间(如 1 - 100 万放在表 1,100 万 - 200 万放在表 2)。扩展性好,但容易产生新写入数据的单表热点写问题。
- Hash(哈希取模):根据主键的哈希值取模分发。数据分布极均匀,但后续再次扩容时,需要进行痛苦的数据迁移和重哈希。
分库分表带来的痛点与解决对策
分库分表并非免费的午餐,它会引入一系列复杂的工程问题:
事务处理(分布式事务)
原本在单机下一个事务(ACID)内能完成的操作,由于分库后数据散落在不同数据库,需要通过二阶段提交(2PC)、TCC(Try-Confirm-Cancel)或可靠消息最终一致性等分布式事务方案来保障一致性。
跨库 Join 问题
分库后无法直接在 SQL 中使用 JOIN 连接不同库中的表。
- 解决方案:
- 字段冗余:把一些常用字段直接写入表中,避免 Join(如订单表中直接冗余买家姓名)。
- 全局表:将极少变动的公共配置表在所有物理库中各复制一份。
- 应用层组装:先查出主表数据,再在业务代码中并发查询关联表数据进行内存组装。
跨库分页与排序 (LIMIT)
若查询需要对全表数据进行排序分页,如 ORDER BY create_time LIMIT 100, 10。在没有分片键约束的情况下,中间件必须去所有物理子表中分别拉取前 110 条数据,然后在内存中合并并重新排序,这会导致巨大的网络开销和内存消耗。因此,实际业务中应限制深分页,或将聚合分析查询转到 Elasticsearch 中进行。