经过上一小节的数据插入测试,我们可以来认真分析一下“2000W 行分表”的问题了。
住范儿生产数据库中的电商业务常用大表“订单商品表”,实际单行数据长度为0.9KB
,与测试结果的 1KB 相差不大,因此测试结果还是能比较符合现实世界的真实情况的。
我们取以下值来计算三层和四层的理论极限:
14/0.9=15.5555
,取整为 15 行数据。int
,页指针长度为 12 字节,因此每页最多可以存储 14*1024/12=1194.6666
,取整为 1194 个页指针。则三层 B+ 树的理论极限为:1194^2 * 15 = 21384540
,大约 2100 万行,与传说中的 2000W 行分表相符。
如果需要五层,则行数需要达到 1194^3 * 15 = 25533140760
,即 255 亿行。这个数字已经超过了 unsigned int
的上限 42 亿多,需要使用 bigint
作为主键。感兴趣的读者可以自行计算 bigint
下五层索引甚至六层、七层、八层的行数极限,笔者在此不再赘述。
并不会:三层索引和四层索引的性能差异微乎其微,2000W 行分表已经过时了!
实际上,每次 B+ 树增高只会增加两个索引页,修改一个索引页,总共只修改了三个 16KB 的数据页。无论是磁盘 I/O 还是 Buffer Pool 缓存失效,对性能的影响都非常微小:
索引从三层转换到四层,只增加了一次 I/O,绝对性能降低幅度的理论极限仅为 1/3
。而且在有 Buffer Pool 存在的情况下,性能差异几乎可以忽略不计,只增加了 1~2
次比大小的计算成本。
虽然三层索引和四层索引看起来性能差异不大,但是如果你的单行数据比较大,例如达到了 5KB,仍然建议进行横向分表,这是减少磁盘 I/O 次数的最直接有效的优化方法:
2017 年的阿里巴巴 Java 开发手册上说,当单表行数超过 500 万行或单表容量超过 2GB 时,推荐进行分库分表。然而,很多技术博文错误地将其解读为:阿里巴巴建议超过 500 万行的表进行分表。
尽管经过实测,每行数据定长 1024 字节,Buffer Pool 配置为 22GB,在单表体积 24GB 的情况下,四层索引和三层索引之间没有性能差异。但现实世界中的数据表并非如此完美:
那么,如何回答 “何时进行分表” 这个问题呢?很遗憾并没有一个通用的答案,这取决于每个表的读取、新增、更新情况。
虽然在数据库技术层面无法给出具体答案,但从软件工程层面可以得出一个结论:
能不分就不分,不到万不得已不搞分表,如果能通过加索引或加内存解决就不考虑分表,分表会对业务代码产生根本性的影响,并带来长期的技术债务。
B+ 树和分表的问题就讨论到这里,下面我们简单了解一下 Buffer Pool 的设计思想和运行规律。
📙 高并发的哲学原理 《Philosophical Principles of High Concurrency》
Copyright © 2023 吕文翰