关于mysql:技术分享-关于-MySQL-自增-ID-的事儿

34次阅读

共计 3214 个字符,预计需要花费 9 分钟才能阅读完成。

作者:贲绍华

爱可生研发核心工程师,负责我的项目的需要与保护工作。其余身份:柯基铲屎官。

本文起源:原创投稿

* 爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。


当咱们应用 MySQL 进行数据存储时,个别会为一张表设置一个自增主键,当有数据行插入时,该主键字段则会依据步长与偏移量增长(默认每次 +1)。

下文以 Innodb 引擎为主进行介绍,应用自增主键的益处有很多,如:索引空间占比小、范畴查问与排序都敌对、防止像 UUID 这样随机字符串带来的页决裂问题等 …

一、自增 ID 是如何调配的?

1.1 计数器的初始化

当咱们对该表设置了自增主键之后,则会在该表上产生一个计数器,用于为自增列调配 ID。

自增的值并不是保留在表构造信息内的,对于不同的版本它们有如下的区别:

1.1.1 MySQL 8.0 版本之前(重启后可能会产生变动):

计数器的值存储在内存中的,重启后抛弃,下一次将读取最大的一个自增 ID 往后持续发号。

https://dev.mysql.com/doc/ref…

1.1.2 MySQL 8.0 版本(重启后放弃不变):

计数器的值将会长久化到磁盘。在每次发号时都将写入 Redolog,并在每个 Checkpoint 都进行保留,重启时候应用 Redolog 复原重启之前的值。

https://dev.mysql.com/doc/ref…

1.2 数据的插入模式

1.2.1 Simple Inserts(简略插入):

能够预先确定插入行数的语句(像简略 insert 的语句蕴含多个 value 这种状况也是属于简略插入,因为在进行插入时就曾经能够确定行数了)

1.2.2 Bulk Inserts(大量插入):

事后不晓得要插入的行数的语句(包含 INSERT … SELECT, REPLACE … SELECT 和 LOAD DATA 语句,但不包含 plain INSERT)

1.3 AUTO-INC 表级锁

如果一个事务正在向表中插入值,则会产生表级的共享锁,以便以后事务插入的行接管间断的主键值。

1.3.1 加锁策略:

当处于 [传统模式] 与[间断模式]时,每次拜访计数器时都会加上一个名为 AUTO-INC 的表级锁

1.3.2 开释策略:

传统模式:锁只持有到该语句执行完结,留神是语句完结,不是事务完结

间断模式:批量插入时锁持有到该语句执行完结,简略插入时锁持有到申请完自增 ID 后即开释,不直到语句实现

1.4 计数器的三种模式(innodb_autoinc_lock_mode)

通过调整 innodb_autoinc_lock_mode 配置项,能够定义 AUTO-INC 锁的模式,不同的模式对应的策略与锁的粒度也将不同。

当应用基于 Binlog 的复制场景时,对于 statement(SBR)同步模式下只有 [传统模式] 与[间断模式]能保障语句的正确性。

基于 row(RBR)行复制的状况下任何配置模式都能够。

1.4.1 传统模式 [innodb_autoinc_lock_mode = 0]

执行语句时加 AUTO-INC 表级锁,执行结束后开释

1.4.2 间断模式 [innodb_autoinc_lock_mode = 1] [8.0 版本之前为默认]

针对 Bulk Inserts 时才会采纳 AUTO-INC 锁,而针对 Simple Inserts 时,则采纳了一种新的轻量级的互斥锁来调配 auto_increment 列的值。

该模式下能够保障同一条 insert 语句中新插入的自增 ID 都是间断的,但如果前一个事务 rollback 抛弃了一部分 ID 的话也会存在后续 ID 呈现距离的状况。

1.4.3 混合模式 [innodb_autoinc_lock_mode = 2] [8.0 版本为默认]

来一个调配一个,不会产生 AUTO-INC 表级锁,仅仅会锁住调配 ID 的过程。

因为锁的粒度缩小,多条语句在插入时进行锁竞争,自增长的值可能不是间断的。

且当 Binlog 模式为 statement(SBR)时自增 ID 不能保证数据的正确性

1.5 自增 ID 肯定就是间断吗?

不肯定,业务也不应该过分依赖 MySQL 自增 ID 的连续性,在以下三种状况下,并不能保障自增 ID 的连续性:

1.5.1 插入时的其余惟一索引抵触

假如已存在数据{1, 张三},且张三所属的字段设置了惟一主键

此时再次插入 {null, 张三} 时候,主键抵触插入失败,但表的计数器已由 2 变成了 3

当下次插入 {null, 李四} 的时候最终入库的会变成{3, 李四}

1.5.2 事务回滚

在一个事务里进行数据的插入,但最初并没提交,而是执行了 Rollback。那么计数器已递增的 ID 是不会返还的,而是被间接抛弃。

1.5.3 产生 Bulk Inserts(大量插入)时

产生大量插入时可能会呈现自增 ID 并不是间断的状况

二、自增 ID 用完了该怎么办?

当咱们为表设置了自增主键后,自增 ID 的范畴则与主键的数据类型长度相干。

如果没有一张表里没有设置任何主键,则会主动生成一个隐性的 6 字节的 row_id 作为主键,它的取值范畴为 0 到 2^48-1。

row_id 是由一个全局的 dict_sys.row_id 参数进行保护的,所有没有主键的表都会用上它(并不是每一个表独自占一份 row_id list)

那么针对这两种主键,则会有以下两种状况产生:

2.1 当自增主键用完了:

当自增 ID 达到下限后,受到主键数据类型的影响,计数器发放的下一个 ID 也是以后这个 Max ID,当执行语句时则会提醒主键抵触。

1062 – Duplicate entry ‘4294967295’ for key ‘PRIMARY’

倡议依据业务正当布局,在进行表设计时就抉择适宜的数据类型。

当然也能够间接抉择 Bigint 类型,它的取值范畴是无符号状况下:0 到 2^64–1(18446744073709551615)

这里并不是指 bigint 类型肯定不会用完,毕竟一个有范畴的持续增长的值肯定会有溢出的时候,只是说个别场景下它都是足够应用的。

一秒减少的记录数大概多少年后才会用完
1/ 秒584942417355 年
1 万 / 秒58494241 年
100 万 / 秒584942 年
1 亿 / 秒5849 年

2.2 当 row_id 用完了

当 row_id 应用完后则又会从 0 开始发放,此时新插入的数据将笼罩回 row_id=0 的数据行。

因为它并不产生谬误,还会造成数据的笼罩写。所以咱们平时还是尽量给表都设置一个正当的主键才是。

三、自增 ID 的裸露导致被爬虫歹意遍历该怎么办?

在理论业务场景中,ID 经常须要返回给客户端用来进行相干业务操作。

如果咱们有个 userinfo?uid=? 的 API 接口,而用户 ID 是自增的,这时会产生什么?

该接口通过简略的尝试就能够暴露出实在的业务用户总数,能够很不便的应用爬虫从 1 开始递增获取数据信息。

那么有的同学说,我既想应用自增 ID 带来的益处,也不想接受这种比拟常见的问题,那该怎么办呢?

3.1 自增 ID 输入输出前进行本义

在输入或者获取前对指定字段进行可逆的本义操作

长处:实现起来比较简单,无论单体业务或者分布式应用都无需思考对数据源的解析,只需在客户端实现本人的本义与解析办法即可;

毛病:业务入侵较大,且须要前后端各个合作方确认对立的规范;如果本义办法有调整,变更影响面也会很大;字符串长度会随 ID 长度而变动,应用空位填充也会特地显著;

3.2 Snowflake 花算法:

长处:因为采纳了工夫戳进行 ID 生成,该 ID 是有序的,对范畴查问与排序都比拟敌对;

毛病:须要保障发号节点的高可用性;另外因为生成时依赖工夫戳,须要思考时钟回拨与时钟同步的问题;

3.3 应用 HashMap 进行映射:

保护一份 ID 与 hash 的映射字典,它能够存在于客户端自身,也能够依赖其余如 Redis、ETCD 之类的组件

长处:hash 长度不会随着 ID 长度或值的变动而变动;能够依据已有的 hash code 来造布隆过滤器;

毛病:业务入侵较大,查问时同样须要先依据 hash key 找到对应的 ID 值;须要思考抉择适合的 hash 算法以及解决 hash 抵触或扩容的问题。

正文完
 0