参考:
🔥我说MySQL每张表最好不超过2000万数据,面试官让我回去等通知? - 掘金
考虑到磁盘IO是非常高昂的操作,计算机操作系统做了预读的优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。
每一次IO读取的数据我们称之为一页(page),具体一页有多大数据跟操作系统有关,一般为4k或8k,也就是我们读取一页内的数据时候,实际上才发生了一次IO。MySQL每个节点大小默认为16KB,也就是每个节点最多存16KB的数据,可以修改,最大64KB,最小4KB。
如果某一行数据太大了超过16KB怎么办?
如果行超过最大行长度, 则将可变长度列用外部页存储,直到该行符合最大行长度限制。 就是说把varchar、text这种长度可变的存到外部页中,来减小这一行的数据长度。只在该列上保留一个 20 字节的指针指向溢出页。
索引页就是存索引的节点,也就是非叶子节点。
每一条索引记录当中都包含了当前索引的值 、 一个 6字节 的指针信息 、一个 5 字节的行标头,用来指向下一层数据页的指针。
假设我们的主键id为 bigint 型,也就是8个字节,那索引页中每行数据占用的空间就等于 8+6+5=198+6+5=19 字节。每页可以存 15232÷19≈80115232÷19≈801 条索引数据。
那算上页目录的话,按每个槽平均6条数据计算的话,至少有 801÷6≈134801÷6≈134 个槽,需要占用 268 字节的空间。
把存数据的空间分一点给槽的话,我算出来大约可以存787 条索引数据。
如果是主键是 int 型的话,那可以存更多,大约有 993条索引数据。
前两层非叶子节点计算
在 B+ 树当中,当一个节点索引记录为 N 条时,它就会有 N 个子节点。由于我们 3 层B+树的前两层都是索引记录,第一层根节点有 N 条索引记录,那第二层就会有 N 个节点,每个节点数据类型与根节点一致,仍然可以再存 N 条记录,第三层的节点个数就会等于 N * N。
则有:
- 主键为 bigint 的表可以存放 787 * 787=619369 个叶子节点(约等于62w)
- 主键为 int 的表可以存放 993 * 993=986049 个叶子节点(约等于99w)
分析一下这张表的行记录:
- 行记录头信息:肯定得有,占用5字节。
- 可变长度字段列表:表中
title
占用1字节,description
占用2字节,共3字节。 - null值列表:表中仅
school_code
、cover_image
、release_time
3个字段可为null,故仅占用1字节。 - 事务ID和指针字段:两个都得有,占用13字节。
- 字段内容信息:
id、author_id、school_code
均为bigint型,各占用8字节,共24字节。create_time、release_time、modified_time
均为datetime类型,各占8字节,共24字节。status、is_delete
为tinyint类型,各占用1字节,共2字节。cover_image
为char(32),字符编码为表默认值utf8,由于该字段实际存的内容仅为英文字母(存url的),结合前面讲的字符编码不同情况下的存储 ,故仅占用32字节。title、description
分别为varchar(50)、varchar(250),这两个应该都不会产生溢出页(不太确定),字符编码均为utf8mb4,实际生产中70%以上都是存的中文(3字节),25%为英文(1字节),还有5%为4字节的表情😁,则存满的情况下将占用 (50+250)×(0.7×3+0.25×1+0.05×4)=765(50+250)×(0.7×3+0.25×1+0.05×4)=765 字节。
统计上面的所有分析,共占用 869 字节,则每个叶子节点可以存放 15232÷869≈1715232÷869≈17 条,算上页目录,仍然能放 17 条。
则三层B+树可以存放的最大数据量就是 17×619369=10,529,273,约一千万条数据,再次没想到吧👴。
以下是粗略估算:
InnoDB存储引擎中页的大小为16KB,一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),指针类型也一般为4或8个字节,也就是说一个页(B+Tree中的一个节点)中大概存储16KB/(8B+8B)=1K个键值(因为是估值,为方便计算,这里的K取值为〖10〗^3)。
也就是说一个深度为3的B+Tree索引可以维护10^3 * 10^3 * 10^3 = 10亿 条记录。(这种计算方式存在误差,而且没有计算叶子节点,如果计算叶子节点其实是深度为4了)