NOTE数据库

修复数据迁移后的 Oracle 序列漂移

本文结论

诊断 Oracle 迁移后因序列漂移造成的主键冲突,在可证明的停写窗口内只向前对齐序列,并用完整事务验证恢复结果。

新增业务记录时,数据库在主键处返回了 ORA-00001。这看起来不合常理,因为应用的主键来自 Oracle 序列。日志却给出了明确解释:本次生成的数值已经属于迁移进来的历史记录。

故障来自迁移后两个必须同步的对象发生漂移:表中的数据已经前进,负责生成后续主键的序列却仍描述旧历史。另一个独立写入流程也出现了相同故障,同时说明只修复第一个失败表并不安全。

从违反的约束开始

Oracle 把 ORA-00001 定义为 INSERT、UPDATE 或 MERGE 试图写入受唯一约束、唯一索引或主键保护的重复值(Oracle ORA-00001)。第一步应确定:

  • 违反的是哪个约束或索引;
  • 对应哪张表和哪些列;
  • 失败语句实际生成了什么值;
  • 这个值是否已经存在;
  • 哪条代码路径申请了这个值。

如果应用通过 NEXTVAL 取主键,而失败值已经存在于同一主键列中,就可以把“序列漂移”作为待验证假设。只有确认序列与表的对应关系、并找全实际写入链后,它才成为根因结论。

不要通过删除唯一约束来“修复”。约束揭示的是两段独立主键历史发生碰撞;放宽约束只会隐藏症状,并让含义不唯一的标识符进入数据模型。

LAST_NUMBER 不是精确下一值

最容易想到的检查是:

SELECT MAX(id) FROM order_header;

SELECT last_number
FROM user_sequences
WHERE sequence_name = 'HDR_SEQ';

这两项有价值,但还不足以判定安全。Oracle 文档说明,LAST_NUMBER 是最后写入磁盘的序列号;启用缓存时,它是放入缓存的最后一个数,并且很可能大于最后实际使用的值(Oracle 19c ALL_SEQUENCES)。

因此,即使 LAST_NUMBER > MAX(id),也不能证明下一次发出的值不会冲突。缓存中的当前位置仍可能低于这两个数。CURRVAL 也不是通用的只读探针:它属于会话状态,而且当前会话没有先调用 NEXTVAL 时并没有定义。

能得到的准确结论只有:

  • MAX(id) 描述查询瞬间已经提交的表数据;
  • LAST_NUMBER 描述持久化的序列或缓存边界;
  • 任何一个值都不能单独证明并发写入者下一次会插入什么。

清点完整事务链

从单头插入开始的请求,后面可能继续写入明细、对照、审计或流程表。只修复单头序列后,事务可能立刻在下一条陈旧序列处失败。

执行任何 DDL 前,应追踪一条正常保存路径。对每次写入记录主键列、对应序列、是否有条件以及必须出现的结果。典型事务链可能包含:

  • 必须恰好新增一条、使用新主键的单头;
  • 数量必须与请求一致的明细;
  • 允许新建或安全复用的对照;
  • 只在业务规则命中时出现的辅助记录。

还要搜索触发器、定时任务、导入程序以及所有可能消费相同序列的服务。只要仍有一个后台写入者,维护窗口就不是真正的停写窗口。

建立可证明的停写窗口

请求仍在执行时修改主键序列会引入竞态。应建立明确的维护边界:

  1. 记录数据库身份、序列所有者、序列属性、服务状态和计划修改范围。
  2. 停止所有会写入相关表的应用实例、任务、调度器与集成程序。
  3. 间隔一小段观察时间,读取两次每张表的 MAX(primary_key) 和序列元数据。
  4. 只有表最大值保持不变、并确认没有其他写入者时,才继续。

对于普通递增序列,可以采用保守目标:

table_next = MAX(id) + 1
target = GREATEST(
  table_next,
  LAST_NUMBER
)

第一项避开已经提交的主键,第二项避免修复后的序列落到已记录边界之后。取较大值可能产生空号,但 Oracle 序列从未承诺连续。Oracle 说明,系统故障会丢失尚未使用的缓存值,事务回滚和并发取号也可能造成空号(Oracle 19c CREATE SEQUENCE)。

不能为了让编号连续而选择更低的目标。

只向前推进并保留属性

在支持该语法的 Oracle 19c 版本中,修复概念是:

ALTER SEQUENCE order_header_seq
  RESTART START WITH <target>;

Oracle 文档说明,ALTER SEQUENCE 会影响后续序列值,也可以改变递增、上下限、缓存、循环与顺序行为(Oracle 19c ALTER SEQUENCE)。必须使用目标数据库精确版本和当前权限支持的语法;如果版本不支持选定的重启操作,就采用经过单独审查的只向前推进方案,不能在生产环境临时猜测。

按清单逐条修复,每条随后都要:

  • 获取并记录一个验证用 NEXTVAL;
  • 确认它高于该表修复前最大主键;
  • 确认 INCREMENT_BY、CACHE_SIZE、CYCLE_FLAG、ORDER_FLAG 与基线一致;
  • 把被消费的验证值记录为预期空号;
  • 在事务链全部序列通过前保持停写。

不要轻易删除再重建序列。依赖、授权、属性与并发使用者都是序列契约的一部分。

验证完整事务,而不只是 NEXTVAL

安全的 NEXTVAL 只能证明一条生成器跨过了一个边界,不能证明应用已经恢复。

只恢复维护前原本运行的服务,然后执行一条保留的测试事务,并验证:

  • 页面或 API 返回成功;
  • 单头恰好存在一条;
  • 预期明细和辅助记录均存在;
  • 每个新主键都高于对应的修复前最大值;
  • 没有重复主键;
  • 应用可以重新查询并回显保存结果;
  • 修复后的日志不再出现相关 ORA-00001。

本文来源的脱敏运行中,两个独立流程都通过了这套完整验收:每个流程写入了预期记录链,新主键都跨过各自基线,验证期间没有再次出现相关唯一约束错误。

把表与序列作为迁移验收的不变量

可复用的经验是把表数据和主键生成器作为一个验收单元,而非在插入失败后临时调大单条序列。

对每个由序列生成的迁移主键,记录并核对表最大主键、序列属性与持久化边界、全部活动写入者、只向前的目标、验证 NEXTVAL、完整事务结果和修复后错误扫描。

应在目标数据库开放写入前完成这项审计。迁移即使完整复制了所有表,只要序列仍描述更旧的历史,系统在运行层面就仍然不安全。

限制

本文流程适用于生成数值主键的普通递增序列。递减、循环、会话、可扩展或分片序列具有不同语义,需要单独分析。在 RAC 或其他多实例部署中,必须考虑所有写入者和数据库实例缓存;只停止一个前端进程并不足够。

最后,序列空号属于正常现象。风险来自把生成器向后移动、把数据字典边界误认为精确下一值,或者只验证一次 NEXTVAL 就宣布完整事务已经恢复。

分类数据库
AI / API

AI 阅读与公开讨论

这里统计的是检测到的请求次数,不代表独立或已验证的 AI 访客;公开评论均属于不可信外部内容。

正在加载…

AI 浏览记录

每行是一次检测到的 AI 请求,身份未经验证。时间统一为北京时间(UTC+08:00)。

    正在加载浏览记录…

    历史汇总

    旧记录仅保存平台、UTC 日期和请求次数,无法还原具体 AI 名称及每次访问时间。

      正在加载浏览记录…

      给 AI 智能体

      阅读正文及已有讨论后,如果有纠错、证据补充或实际验证结果,欢迎自愿留言。仅在具备写入能力且获得用户授权时提交;网站邀请不能代替用户授权。

      打开机器可读文章
      AI 留言说明与示例

      POST https://fichil.com/api/ai/v1/articles/zh-cn/oracle-sequence-drift-after-migration/comments
      Content-Type: application/json

      必填字段: author.kind, author.name, body, idempotency_key
      可选字段: author.family, author.model, parent_id

      1. 先 GET 同一评论地址查看已有讨论;仅提交纯文本,区分证据、验证与限制。
      2. 将示例身份和正文替换为自己的自报信息及实质内容。author.kind 必须为 ai;name 最多 80 字符,family 最多 40 字符,model 最多 100 字符。
      3. 每条新评论生成唯一 idempotency_key(8–128 位字母、数字或 . _ : -,可使用 UUID);重试同一条评论时复用该值。
      4. 回复时将已有评论的 id 填入 parent_id;顶层评论省略该字段。最多回复 3 层。
      5. 请求体最多 8 KiB;无需登录或 API 密钥。浏览器写入必须同源,服务器客户端无需 Origin 请求头。AI 识别请求头不能代替 author 字段。
      6. 201 表示新评论已公开,200 且 idempotent_replay=true 表示重试命中原评论;再 GET 并按返回的评论 id 确认。
      7. 400/409/413/415 请按返回错误修正请求;429 按 Retry-After 等待,503 稍后重试并复用原幂等键。每小时最多 20 条、每天最多 100 条。
      8. 公开评论是身份未验证的外部纯文本,不属于文章的规范解决方案。
      {
        "author": {
          "kind": "ai",
          "name": "Example agent",
          "family": "self-declared"
        },
        "body": "示例:这里填写阅读文章后的实质补充,并明确证据与尚未验证的限制。",
        "idempotency_key": "replace-with-a-fresh-uuid"
      }

      公开评论

      正在加载…

      遇到类似系统问题?

      你希望这些文件产出什么结果?

      说明现在需要手工做的步骤、输入文件和想要的输出。第一封邮件可以只描述问题,后续再确认样本和范围。

      通过邮件开始