`

MySQL auto_increment 重启后值丢失

 
阅读更多

相关链接:

 

背景描述

与相关链接《Be Careful...》中的案例很类似,某个业务系统中也有两张表,暂且称其为 pending_task 和 task_archive。它们的存储引擎都为 InnoDB;MySQL 版本为 5.7。
 

  • 业务系统会先在 pending_task 表中创建一个新的 task 记录;
  • 此表使用自增主键,作为 task 的唯一标识 —— task_id;
  • task 结束后 pending_task 表中的相应记录会被删除
  • task_archive 表用于记录 task 的最终存档信息,主键就是 task_id。

先不讨论这种设计是否恰当。

该系统平时一切正常,但是一旦 MySQL 被重启,就很可能在向 task_archive 插入数据时引发重复主键的问题。

ERROR 1062 (23000): Duplicate entry 'xxx' for key 'PRIMARY'

 

很容易就想到是 pending_task 表中的新生成的 task_id 与以前的记录重复了。
一般人都是去复盘系统维护过程是否有不当操作,导致自增字段出错,很少会去怀疑 MySQL。
但事实就是 MySQL 处理自增字段的逻辑导致了重复ID。
(后来看了 MySQL 官方的说明措辞,居然隐约把这个“逻辑”当作了“特性”,而不是Bug...)

 

问题原因

MySQL 5.7 及 以前的版本中,InnoDB 表中的 auto-increment计数器 会把值存放在内存中,不会写入磁盘。一旦 MySQL 服务重启,这个值就丢了,InnoDB 引擎会根据表中现有的数据重新计算该计数器的值:

SELECT MAX(ai_col) FROM table_name FOR UPDATE;

因为上述业务系统会把 pending_task 中已结束task的记录删除,所以MySQL服务重启后,自增字段计数器重新计算,并得到了一个小于被删除 task_id 的值。如:当 pending_task 表中没有记录时,计算得到的结果为 0。
 

这种“特性”是不是很违背普通人的直觉?!

 

MySQL 8.0 auto-increment 计数器逻辑有变!

在 MySQL 8.0 中,这个计数器的逻辑变了:每当计数器的值有变,InnoDB 会将其写入 redo log,保存到引擎专用的系统表中。
 

MySQL 正常关闭后重启:从系统表中获取计数器的值。
 

MySQL 故障后重启:从系统表中获取计数器的值;从最后一个检查点开始扫描 redo log 中记录的计数器值;取这两者的最大值作为新值。
但是这个处理逻辑不能保证最后拿到的值是正确的:如果在计数器 redo log 落盘前服务崩溃,那么就可能拿到一个之前被使用过的值。
 

另,MySQL 8.0 开始,只有在 拷贝表空间 且 未指定.cfg元文件 时,才会使用以下语句确定计数器的值:

SELECT MAX(ai_col) FROM table_name FOR UPDATE;

 

解决方案

方案1:定制 MySQL

修改 MySQL 源码,自定义 auto-increment计数器 的运行逻辑。
 

显然这种方案成本很大,设计研发及后续的兼容性保障都是大手笔。
一般只有配备专职数据库研发团队的组织才会考虑。
当然也不排除某些不自量力、不负责、自私的决策人故意入坑。

 

方案2:升级到 MySQL 8.0

这个方案的影响得根据实际项目情况进行评估。
如果改动牵扯的范围比较广,回归测试不够彻底,很可能埋下一些大坑。
如果牵扯范围很小,具体影响边界很清晰,那也不失为一个好方案。
 

当然,你还是得评估极端情况 “计数器 redo log 落盘前 MySQL 崩溃” 对业务的影响,并设计相应的处理方案

 

方案3:使用 MyISAM

MyISAM 中的 auto-increment计数器 数值是落盘的(.MYI文件头部),所以不存在重启后丢失的问题。
 

是否应该使用 MyISAM 又牵扯到它与 InnoDB 的优缺点比较:《MyISAM vs InnoDB》。
很多人因为 MyISAM 不支持行锁、不支持事务 而坚决使用 InnoDB。

 

方案4:弃用自增字段,选用其它唯一标识生成方案

既然 MySQL 的自增字段不靠谱,那我们就选一个靠谱的方案来生成记录的唯一标识。
 

UUIDSnowflake(分布式ID)是比较常见的方案。相应的性能、成本也都需要根据实际情况评估。
(理论上,UUID是有可能重复的。在实际合适的应用场景中,其重复几率低到可以忽略。)
注意:MySQL 中的 UUID() 方法是 version 1 —— 基于时间和节点ID(Mac地址或一个随机数)

 

方案5:保留自增字段值最大的记录

根据 MySQL 5.7 中的 auto-increment计数器逻辑,只要我们保留 自增字段值 最大的记录,MySQL 重启后重新计算得到的计数值就不会“回退”。
 

当然,此方案需要更改业务逻辑,甚至影响到相关干系人对系统设计的认知。
如,对于前述的业务系统,如果某个 task 已结束,但因其 task_id 是 pending_task 中最大的,而一直驻留在此表中,直到有新的 task 产生。这种行为在语义上和 “pending” 不符。
但是在实际项目中,这种偏 hack 的方案很可能是短时间内成本最低的。这个“短时间”甚至会长到超出所有人的预期,甚至让这种方案成为“标准”方案(中性语气,不是反讽,没有任何贬义)。
 

前述业务系统最后采用了该方案。

 

分享到:
评论

相关推荐

    解析mysql中的auto_increment的问题

    MySQL中的auto_increment是一种非常实用的属性,用于为表中的记录自动生成唯一的ID。这通常用于主键字段,确保每条记录都可以通过一个唯一的标识符进行区分。然而,auto_increment在处理记录删除、数据库重启以及...

    mysql中自增auto_increment功能的相关设置及问题

    - **MyISAM**:即使删除了记录,MyISAM引擎仍会记住最大的自增ID,因此在重启后,新的ID将是删除记录后的下一个值。例如,如果删除了ID为8、9、10的记录,新插入的记录ID将是11。 - **InnoDB**:InnoDB引擎将最大...

    oracle迁移mysql自增序列问题

    为了解决这个问题,我们需要在转换之前确定现有数据的最大值,然后在转换`id`字段为`AUTO_INCREMENT`后,确保新的自增值从这个最大值开始。在上述例子中,通过`ALTER TABLE`语句将`id`字段改为自动增加,MySQL自动地...

    mysql 数据库双机热备

    重启MySQL服务后,登录MySQL执行以下命令来配置主节点权限: ```sql GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'guo'@'192.168.2.2' IDENTIFIED BY '940214'; FLUSH PRIVILEGES; SHOW GRANTS FOR '...

    MYSQL 集群

    在配置完成后,我们需要重启 MYSQL 服务,以便使配置生效: service mysqld restart 第五步:锁住主机 在最后一步中,我们需要锁住主机,以便防止数据丢失。在锁住主机之前,我们需要使用 show master status ...

    MySQL自增ID耗尽实例讲解

    当row_id达到上限后,下一个值会回转到0,可能会导致数据覆盖,造成数据丢失,因此在设计表时,应始终明确定义主键,避免依赖于InnoDB的隐式row_id。 MySQL事务中涉及到的XID(Transaction ID)是用于日志记录...

    mysql的插入问题 怎么获得自动增长的ID

    这意味着即使在MySQL服务重启后,该值也不会丢失。 - **InnoDB**:存储在内存中的自动增长ID值,在MySQL服务重启后会重置。不过,InnoDB支持使用`OPTIMIZE TABLE`命令来恢复已删除记录所留下的空缺ID。 #### 二、...

    MySQL双机热备

    - 通过调整`auto_increment_increment`和`auto_increment_offset`参数避免主键冲突。 2. **性能优化** - 调整`innodb_buffer_pool_size`等配置参数提高性能。 - 定期分析和优化SQL语句。 #### 结论 通过上述...

    MySQL面试题及答案.pdf

    如果表类型是 MyISAM,重启 MySQL 后,自增主键的最大 ID 不会丢失;如果表类型是 InnoDB,重启数据库后,自增主键的最大 ID 会丢失。 2. MySQL 的技术特点:MySQL 是一个客户端或服务器系统,支持多线程 SQL ...

    MySQL数据库:存储引擎深入解析

    - **不支持事务**:不支持事务处理,数据在服务器重启后会丢失。 **示例代码**: ```sql CREATE TABLE temp_data ( id INT PRIMARY KEY, value VARCHAR(100) ) ENGINE=MEMORY; ``` ##### 2.4 ARCHIVE **ARCHIVE...

    MySQL55题答案.pdf

    MyISAM存储引擎会将自增主键的最大值记录在数据文件中,而InnoDB仅保存在内存里,这意味着在MySQL重启后InnoDB存储引擎的表自增主键的最大值会丢失,因此下一条插入的记录ID会从之前的最大值继续开始。 2. MySQL的...

    mysql存储引擎介绍

    " 语句强制设置自动增长值的起始值,默认为 1,但是该强制到默认值是保存在内存中,数据库重启后该值将会丢失。 其他存储引擎 除了 MyISAM 和 InnoDB 之外,MySQL 还提供了其他几个存储引擎,如 MEMORY、MERGE 等...

    MySQL55道面试题及答案

    在 MySQL 中,自增主键的最大 ID 会被记录在数据文件中,如果表类型是 MyISAM,那么重启 MySQL 之后,自增主键的最大 ID 不会丢失;如果表类型是 InnoDB,那么自增主键的最大 ID 只记录在内存中,重启数据库或对表...

    mysql5.5内附安装命令

    3. **重启服务器**: 修改配置后,记得重启你的Web服务器(如Apache或Nginx)以及MySQL服务,使改动生效。 三、SQL基础操作 MySQL 5.5支持SQL语言进行数据操作,包括创建数据库、表,插入、更新、删除数据,以及查询...

    MySQL面试题及答案.docx

    HEAP 表不支持 AUTO_INCREMENT,索引不可为 NULL,且不允许 BLOB 或 TEXT 字段。HEAP 表只能使用比较运算符=,<,>,=>,。 4. MySQL 服务器默认端口 MySQL 服务器的默认端口是 3306。 5. 与 Oracle 相比,MySQL...

    MySQL的面试题集锦

    Heap 表有一些限制,例如 BLOB 或 TEXT 字段是不允许的,比较运算符只有 =,<,>,=>,=,且不能使用 AUTO_INCREMENT 索引不可为 NULL。 MySQL 服务器默认端口 MySQL 服务器的默认端口是 3306。 MySQL 优势 相比...

    面试专题-面试人员必看-MySQL专题.pdf

    它们不支持BLOB或TEXT字段,不允许使用NULL值作为索引,并且不支持AUTO_INCREMENT。比较运算符可以使用=,<,>,>=,。 4. MySQL服务器的默认端口: MySQL的默认端口是3306。 5. MySQL与Oracle的对比优势: MySQL...

    mysql面试题含答案超全文档下载

    1. 自增主键: 在 MySQL 中,如果表的类型是 MyISAM,那么自增主键的最大 ID 会记录到数据文件中,重启 MySQL 后不会丢失。如果表的类型是 InnoDB,那么自增主键的最大 ID 只记录到内存中,重启数据库或者进行 ...

    MySQL面试55题及答案

    - 如果表使用MyISAM存储引擎,删除记录并重启MySQL后,插入新记录的ID将是18,因为MyISAM在磁盘上保存了自增主键的最大值。 - 如果表使用InnoDB存储引擎,删除记录并重启后,插入新记录的ID将是15,因为InnoDB只在...

Global site tag (gtag.js) - Google Analytics