添加自增主键导致主从节点查询数据不一致

更新时间:
复制 MD 格式

问题现象

分别在主从节点上使用同样的自增主键值(自增ID)进行查询,查询结果中的数据不一致。

可能原因

当为无主键表添加自增主键时,自增主键的值是按照数据在表中的排列顺序赋值的。在没有主键的情况下,数据在表中的顺序是由存储引擎内部的RowID决定的,同样的数据在主从节点上的RowID可能不同,因此无主键表中的数据在主从节点中的排列顺序不同,从而导致同样的数据对应的自增主键值不同,即用相同的自增主键值分别在主从节点上查询出的数据不同。详情请参见BUG#92949MySQL官方文档

解决方案

您可以执行如下步骤,解决主从节点查询数据不一致的问题:

  1. 在主节点创建一个与原无主键表相同的新表,并添加自增主键。

  2. 将数据按全部字段排序后插入到新表中。

  3. 删除原无主键表,将新表重命名为原无主键表名。

代码示例如下:

CREATE TABLE t2 LIKE t1;
ALTER TABLE t2 ADD id INT AUTO_INCREMENT PRIMARY KEY;
INSERT INTO t2 (col1, col2, data) SELECT * FROM t1 ORDER BY col1, col2;
DROP TABLE t1;
RENAME TABLE t2 TO t1;

自增值变化的其他常见场景

除了无主键表添加自增主键导致主从查询结果不一致外,自增列在以下场景中也可能出现值异常,请注意区分。

REPLACE INTO 导致自增值忽大忽小

REPLACE INTO 语句的执行原理是先判断待插入的行是否已存在(依据主键或唯一索引),如果存在则先 DELETE 该行再 INSERT 新行,而不是直接更新。由于每次 INSERT 都会消耗一个新的自增值,即使新行的业务数据与原行相同,自增列的值也会递增。因此,在频繁使用 REPLACE INTO 的表中,自增值会出现跳跃式增长,表现为同一批业务数据对应的自增 ID 忽大忽小,且不再连续。

若同一份数据被反复 REPLACE INTO,还可能因为自增值被两次消耗导致后续插入命中已分配过的自增值区间,从而在业务侧观察到看似重复或跳变的自增 ID。

主备切换场景下自增值的参数影响

RDS MySQL 实例发生主备切换后,新主库的 auto_increment_incrementauto_increment_offset 参数会按照新主库的配置重新生效:

  • auto_increment_increment:自增列每次递增的步长。

  • auto_increment_offset:自增列的起始偏移量。

如果主备节点上述两个参数配置不一致,主备切换后自增列的取值步长或起点会发生变化,导致业务侧观察到自增值不连续或跳变。建议在主备节点上保持 auto_increment_incrementauto_increment_offset 配置一致,避免主备切换后自增行为发生变化。

自增值通过 ALTER TABLE 修改后 24 小时丢失

通过 ALTER TABLE ... AUTO_INCREMENT = N 手动修改自增列的起始值后,如果实例在此后发生主备切换,新主库会按照自身记录的自增值状态重新生效,之前手动设置的自增值可能因此丢失,自增列从切换前的实际计数状态继续递增,而不是从手动设置的值继续。

如果需要持久生效地调整自增列的起始值,建议使用 pt-online-schema-change(pt-osc)等在线 DDL 工具执行变更,避免因主备切换导致设置丢失。

import table 时自增 ID 可能重复

使用 import table(物理导入)方式导入包含自增列的表数据时,如果目标表在导入前已存在数据且自增计数器状态与源表不一致,导入后自增列的计数器可能未同步更新到源表数据中的最大值,后续插入时可能分配到与已导入数据重复的自增 ID。

为避免该问题,建议在导入完成后,通过 ALTER TABLE ... AUTO_INCREMENT = N 将自增列的起始值显式设置为大于当前表中自增列最大值的数值,确保后续插入不会与已导入的数据发生自增 ID 冲突。