【方案设计】Mysql相关场景
- 1、为什么建议MYSQL数据记录超过1000万后需要分库分表?
-
- [1.1 B+树的高度限制](#1.1 B+树的高度限制)
- [1.2 数据页](#1.2 数据页)
- [1.3 估算三层B+树能存储的数据容量](#1.3 估算三层B+树能存储的数据容量)
- 2、MySQL热点数据更新会带来哪些问题?
- 3、MySQL自增主键用完了会怎么样?
1、为什么建议MYSQL数据记录超过1000万后需要分库分表?
1.1 B+树的高度限制
B+树是InnoDB存储引擎使用的索引结构,随着表中数据量的增加,B+树的高度会逐渐增加。如果B+树的高度过高,每次查询需要经过较多的层级,会导致查询性能下降。因此,B+树的高度限制是单表存储量的一个瓶颈。对于B+树的高度限制,一般建议将B+树的高度控制在3到4层以内,以获得更快的查询性能。
1.2 数据页
Innodb中的数据页默认大小是16KB,并且B+树的每个节点都对应着一个数据页,包括根节点、非叶子节点和叶子节点。B+树的非叶子节点对应着数据页,其中存储着主键+指向子节点(即其他数据页)的指针。B+树的叶子节点包含实际的数据行,每个数据行存储在一个数据页中。
1.3 估算三层B+树能存储的数据容量
1、非叶子节点的数量
一个根节点中,可以扩展出多少个子节点?
已知一个根节点的存储量是16KB,并且他作为非叶子节点,他只需要存储一个主键+一个指针就行了。假设是一个bigint类型的主键(8字节),和默认6字节的指针。那么可以存储:
16 * 1024 / (8+6) ≈ 1170
根节点可以扩展出1170个二层高度的子节点,而三层的B+树则会有两层非叶子节点。那么最终就能关联出 1170 * 1170 = 1,368,900个叶子节点。
2、叶子节点的存储行数
已知一个叶子节点有16KB,那么它能存储多少数据量就取决于单行数据的大小了。假设单行数据量1KB,那么他就能存储16条记录。
3、估算结果
基于以上计算方式,假设单条数据的存储空间是1KB,那么3层高度的B+树最终的可存储数据量为:
1170 * 1170 * 16 = 21,902,400,即2000万!
因此当单表记录数超过2000万之后,查询性能会有下降。
2、MySQL热点数据更新会带来哪些问题?
热点数据更新是指频繁更新某个或某些特定行的数据,比如大促期间,有一个大商家,如小米旗舰店、Apple旗舰店等他们的某个商品,会因为有很多人要买,所以他就是热点商品,而大家在短时间内同时买这个商品的时候,就会热点更新这个商品的库存,这就是所谓的热点数据更新,或者叫做热点行更新,如:
sql
UPDATE products
SET stock_quantity = stock_quantity - 10
WHERE product_id = 10086;
热点数据的更新是我们需要避免的,因为他存在以下问题:
1、锁竞争,热点数据的更新是通过update语句进行的,而update是需要给记录增加排他锁的,这就会导致大量的请求被阻塞。降低整个系统的吞吐量。
2、占用数据库连接,当有大量的update语句,因为要修改同一条记录而被阻塞的时候,他们持有的数据库连接是不会释放的,而数据库连接又是有限的,所以会导致连接数不够,进而影响整个系统的吞吐量及可用性。
3、耗尽数据库CPU,大量锁等待,就会导致大量的自旋,多个线程就会不断的尝试获取锁,CPU就需要不断的执行自旋操作,并且需要做死锁检测,消耗大量CPU时间。并且在这个过程中,操作系统也需要频繁的进行线程上下文的切换,这个过程会导致CPU时间片的浪费。
4、死锁风险,在高并发的情况下。由于数据库需要频繁定位和更新这些特定行,可能会增加锁竞争和死锁的风险,影响并发性能。
5、索引维护开销大,频繁的更新热点数据,不仅会导致数据的变化,还可能导致相关索引的频繁维护,这可能会增加数据库的开销,导致性能下降。
6、主从不一致,热点数据的频繁更新,如果在主从复制出现延迟的情况下,就会放大数据不一致的概率。
3、MySQL自增主键用完了会怎么样?
在MySQL中,自增主键有两种,一种是显式的、一种是隐式的。如果我们在一张表中没有定义主键,那么,MySQL会创建一个隐藏的主键(row_id)作为主键。
那么,不管是我们自己定义的自增主键,还是row_id的这个主键,都是一个固定类型的,一般都是bigint unsigned,那么既然有固定类型,就有取值范围。那么随着数据量的增长,主键的值会不断增长,那么万一超过了这个范围限制,会怎么样呢?
如果是显式定义的一个自增ID,如果已经达到了上限,那么下一次申请ID的时候,得到的值就是那个最大值,后续也不会再增加。这时候我们会拿到一个已经用过的主键,如果继续插入的话,会报主键冲突。
如果没有自定义自增ID,那么就会默认使用row_id,如果已经达到了上限,那么下一次申请ID的时候,得到的值会从0开始,然后继续重新自增。但是,这种情况如果我们因为没有设置主键,所以他不会报主键冲突,他会直接把这个row_id = 0的数据插入到数据库中,并且会把之前的row_id=0的数据给直接覆盖了。
所以,结论是:
● 显示自定义的自增ID,用完以后下次插入会报主键冲突 。
● 未定义自增ID主键,会用row_id,用完以后下一次插入会覆盖历史数据。
那么,从这个方面来看的话,我们为了避免数据被覆盖,还是需要自己设置一个自增的主键ID的,毕竟异常我们是可以感知到的,但是数据覆盖我们可能过了很久才能发现。
一旦用完了,可以有以下几个解决方式:
- 重用未使用的主键值(不推荐):
○ 如果你的表中有删除操作,可能会有未使用的主键值。你可以通过编写脚本或程序来找到这些空缺,并在插入新行时显式地指定这些主键值。但这种方法可能会破坏数据的完整性和连续性。 - 归档旧数据(推荐):
○ 如果表中的一些数据是历史数据,不再经常访问,可以将其归档到另一个表中,然后从原表中删除这些数据。这可以为新数据释放主键空间。 - 使用UUID作为主键(不推荐):
○ 考虑使用 UUID(通用唯一标识符)作为主键。UUID 是128位长,几乎不可能用完。但这会增加存储需求,并可能影响性能。