阅读需 8 分钟

面向 AI 编程代理的安全数据库迁移流程

安全的数据库迁移流程让 AI 编程代理继续发挥作用,同时将架构规划、备份证据、评审和获批的生产执行分开。

面向 AI 编程代理的安全数据库迁移流程

生产数据库变更需要与普通代码修改不同的流程。AI 编程代理可以检查架构、追踪应用查询,并以比大多数团队安排评审更快的速度起草迁移文件。但不能因为某个提示词碰巧包含“部署”一词,就把这种速度变成修改生产环境的权限。

安全的数据库迁移流程会把规划和授权分开。代理可以准备证据和精确的变更集。人工则可以评估操作影响,证明恢复流程有效,并批准一次边界明确的执行。这样的分离能避免常见故障:一个看似无害的 ALTER TABLE 最终阻塞结账流程、触发失控的数据重写,或者造成无人能够解释的半成品部署。

本文使用 PostgreSQL 命令,因为它的锁和事务行为在文档中说明得很清楚。这套流程也适用于其他关系型数据库,但在确认相应数据库的手册之前,不要把 PostgreSQL 的语法或假设直接套用到其他数据库引擎上。

生产变更需要将授权与代码生成分开

迁移文件和生产迁移是两种不同的行为。前者描述意图,后者则是在拥有活跃用户、复制任务、备份以及可能彼此不兼容的应用版本的在线系统上,动用稀缺的操作权限。

团队经常犯的错误是,为了让本地开发更方便,给代理一个范围很大的数据库连接。这个连接随后跨越多个环境。如果暂存环境和生产集群接受相同的凭据,代理就无法分辨暂存主机名和生产主机名。更糟的是,能够执行任意 SQL 的代理没有自然理由在表重写之前停下来。它看到的只是任务和工具。

为工作划分出不同阶段,并为每个阶段设置不同的输入和权限:

  1. 发现阶段读取架构元数据、迁移历史、查询代码和操作约束。
  2. 规划阶段生成 SQL、预期的锁行为、数据影响、预检、验证查询以及恢复决策。
  3. 评审阶段确认计划符合真实的生产状态,并确认组织接受其中的风险。
  4. 执行阶段针对一个指定目标运行一份获批的制品。
  5. 验证阶段证明应用和数据库已经达到预期状态,然后才能宣布部署完成。

有用的区分是“可逆性”和“可恢复性”。添加一个允许为空的列通常是可逆的:如果没有代码依赖它,后续语句可以将其删除。根据错误表达式更新数百万行,即使有人写出看似相反的更新语句,也可能无法逆转,因为你可能已经不知道原来的值是什么。可恢复性意味着你有经过测试的方法来恢复或控制损害。每份迁移计划都应将两者作为不同字段记录。

代理不应根据代码仓库分支、工单标签或聊天提示词中的环境变量,推断自己拥有生产权限。这些信号只描述意图,而意图经常会出错。执行必须要求在代理文本上下文之外选择目标,记录已准备制品的摘要,并让某个人清楚看到即将运行的确切内容。

将迁移计划做成可评审的制品

一份可评审的计划,应让操作人员有足够信息在迁移到达数据库之前拒绝它。“添加索引”不是计划。表的大小、查询目的、索引形式、事务限制、锁暴露以及验证查询,合在一起才构成计划。

要求代理生成一个包含固定文件的目录,而不是在合并请求中写一段说明。例如:

migrations/2025-04-add-orders-status-index/
  up.sql
  verify.sql
  preflight.sql
  recovery.md
  manifest.json

清单应将这些文件绑定到目标,并说明代理已经了解到的情况。下面的例子不会暴露凭据,也不会因为有人提出了备份要求,就声称备份已经存在。

{
  "migration_id": "2025-04-add-orders-status-index",
  "engine": "postgresql",
  "target": "production-orders",
  "change": "create index for filtered order status query",
  "transaction_mode": "outside_transaction",
  "requires_backup_restore_check": true,
  "expected_write_blocking": "none during index build",
  "stop_conditions": [
    "target schema differs from preflight result",
    "backup restore check fails",
    "index is invalid after execution"
  ]
}

人工评审者应能从这份制品中回答五个问题:

  • 哪个数据库和架构会接收这次变更?
  • 将执行哪些确切 SQL,执行器会不会把它们包在事务中?
  • 哪个操作会造成阻塞、重写、扫描或额外存储消耗?
  • 有什么证据能证明变更出错时数据库可以恢复?
  • 哪些查询或应用信号可以证明成功?

要求代理写出自己的不确定性。如果它无法确定 PostgreSQL 版本、表大小、当前迁移版本,或调用方应用的兼容范围,就应将这些内容列为停止条件。仅凭不完整的代码仓库访问权限制造确定感,比保留一个未解决项目更糟糕。

评审后应保持生成的 SQL 不可变。如果评审者修改了 up.sql,就要重新生成校验和,并再次让制品进入评审流程。常见的失败模式是:迁移已经评审通过,随后有人在部署脚本中匆忙加入一个“小修复”。此时线上命令已经不是评审过的命令,而审计记录却讲述了一个令人安心但不真实的故事。

在决定如何运行前先给操作分类

SQL 动词本身不足以说明操作风险。ALTER TABLE 既可能表示很快完成的变更,也可能表示需要持有锁或重写大量数据、最终耗尽存储空间的变更。在确定执行路径之前,安全流程会先结合实际操作、数据库版本、表大小和并发负载进行分类。

PostgreSQL 的 ALTER TABLE 文档明确指出了令人不安的一点:除非手册另有说明,许多形式都会获取 ACCESS EXCLUSIVE 锁。这种锁会与读写操作冲突。如果暂存环境没有长事务、没有报表流量,数据量也只有生产环境的一小部分,那么“在暂存环境运行得很快”几乎不能说明问题。

可以采用四种实用分类:

类别典型示例执行预期
仅修改元数据添加没有默认值的可空列操作时间短,但仍要针对当前版本验证锁行为
并发构建创建新索引使用特殊命令形式,并单独处理事务
分批数据变更回填新列小批量提交,控制速率,支持断点续跑
重写或破坏性变更修改大列类型或删除数据需要明确恢复计划的维护决策

不要把所有在线操作都称为安全操作。PostgreSQL 的 CREATE INDEX CONCURRENTLY 按照 CREATE INDEX 文档中的说明,可以在构建索引时避免阻塞写入。但它耗时更长,不能在事务块内运行,失败后还可能留下无效索引。“始终使用 concurrently”这种流行建议忽略了这些条件。只有在写入可用性很重要,并且迁移执行器能够处理这些规则时,才使用它。

规划查询可以为评审者提供表和索引大小的初步估算:

SELECT
  pg_size_pretty(pg_total_relation_size('public.orders')) AS total_size,
  pg_size_pretty(pg_relation_size('public.orders')) AS table_size,
  pg_size_pretty(pg_indexes_size('public.orders')) AS indexes_size;

预期输出是一行包含三个易读的大小值。将其视为容量信号,而不是持续时间承诺。行宽、缓存状态、并发写入、复制、磁盘吞吐量以及活跃事务都会改变结果。

对于锁暴露明显的操作,代理还应在执行前检查阻塞者。PostgreSQL 会在 pg_stat_activity 中公开活跃会话,但这并不意味着你有权终止它们。计划应指出任何长时间运行任务的负责人,并说明迁移是等待、重新安排,还是进入维护窗口。

经过恢复测试的备份是入场券

备份命令成功,只能证明程序写出了一个文件。它不能证明文件能够恢复,不能证明里面包含所需对象,也不能证明恢复流程在压力下有效。对于破坏性或难以逆转的工作,应在执行前进行恢复检查。

对于 PostgreSQL 数据库,自定义格式的逻辑转储可以这样执行:

pg_dump -Fc -d "$SOURCE_DATABASE" -f "orders-preflight.dump"
pg_restore -l "orders-preflight.dump" | sed -n '1,12p'
createdb migration_restore_check
pg_restore -d migration_restore_check "orders-preflight.dump"
psql -d migration_restore_check -c "SELECT count(*) FROM public.orders;"

列表命令应打印出归档条目,例如表定义、表数据条目和索引定义。最后的查询应打印一个单列结果,其中包含恢复后的行数。将命令结果、转储标识符、恢复目标和检查结果记录到迁移记录中。不要把数据库 URL 或密码写入记录。

逻辑恢复检查不能替代物理恢复测试、时间点恢复、只读副本提升演练或云服务商的备份保障。它回答的是一个更具体的问题:这个转储能否恢复到数据库中,并产生我们预期的对象和数据形态?这个范围有限的答案仍然可以发现损坏的归档、缺失的扩展、角色问题,以及只在编写者自己电脑上正常运行的流程。

在执行前就确定恢复方法,不要等出错后再决定。计划应说明以下哪种情况适用:

  • 反向迁移是安全的,因为没有丢弃数据,而且应用可以承受逆转。
  • 恢复到替代数据库是恢复路径,并明确由谁决定切换。
  • 使用副本或云服务商的恢复流程,并记录恢复点目标。
  • 变更无法干净地恢复,因此需要维护窗口和明确的风险接受。

最后一种情况完全合理。因为部署模板要求填写某个字段,就假装存在回滚方案,这种做法并不合理。我见过团队把 DROP COLUMN 写成回填操作的回滚,而回填已经改变了客户数据。后来他们发现,删除新列只是抹掉了证据,原来的值仍然是错的。

先扩展,后移除

让 API 凭据远离提示词
代理通过 sp mcp 连接,Sallyport 注入 HTTP 凭据,并且只返回操作结果。

大多数面向应用的架构变更,都应在不同应用版本混合运行期间保持兼容。部署过程中,旧进程和新进程可能同时存在,因为工作线程退出缓慢、用户保持请求打开,或者回滚重新启动了旧版本。假设应用版本会瞬间统一的迁移,会把正常部署行为变成中断。

假设你需要用受约束的 orders.status_code 替换 orders.status_text。不安全的做法是在一次发布中添加新列、重写所有行、切换应用代码,再删除旧列。它包含多个故障点,也没有适合中途停止的安静时机。

改用扩展和收缩的顺序:

  1. status_code 添加为可空列,并添加不会破坏当前应用的辅助结构。
  2. 部署应用代码:存在新字段时读取新字段,并同时写入两个字段,或者安全地从一个字段推导另一个字段。
  3. 以有界批次回填现有行,并将进度记录在代理临时聊天上下文之外。
  4. 验证所有行都符合新约束,同时确认读取方使用新字段。
  5. 在后续发布中停止写入旧字段,经过约定的保留期后再移除它。

批处理循环很重要。一个巨大的 UPDATE 会让资源长时间被占用,产生大量预写日志,给副本带来压力,也让恢复更难判断。每批有明确边界的查询,会在提交之间为操作人员提供检查复制延迟、错误率和数据库负载的机会。

下面的 PostgreSQL 模式按主键更新选定批次,并返回发生变化的行:

WITH batch AS (
  SELECT id
  FROM public.orders
  WHERE status_code IS NULL
  ORDER BY id
  FOR UPDATE SKIP LOCKED
  LIMIT 500
)
UPDATE public.orders AS o
SET status_code = CASE o.status_text
  WHEN 'new' THEN 10
  WHEN 'paid' THEN 20
  WHEN 'shipped' THEN 30
  ELSE NULL
END
FROM batch
WHERE o.id = batch.id
RETURNING o.id;

只有在仍有返回行,并且映射结果符合要求时,执行器才应重复运行这段语句。对于受控的工作线程,SKIP LOCKED 可以避免等待其他事务持有的行,因此很有用。但它不能证明所有行都已处理。验证查询必须检查剩余的空值和意外的源值:

SELECT status_text, count(*)
FROM public.orders
WHERE status_code IS NULL
GROUP BY status_text
ORDER BY count(*) DESC;

如果查询返回了陌生值,就停止。不要让代理因为想完成任务,就决定把未知的生产状态转换成默认枚举值。

执行需要硬性停止条件和有界命令

获批的迁移仍可能遇到与规划者检查时不同的生产状态。执行器必须在执行前立即进行预检,并在结果不一致时停止。这正是流程比依靠记忆执行的操作手册更安全的地方。

使用专用执行器,只接受制品标识符和目标选择器。它应拒绝从终端粘贴的内联 SQL,拒绝校验和已改变的制品,并在开始前打印目标身份。执行器可以先运行只读预检 SQL,然后要求对修改部分进行单独批准。

明确设置会话时间限制。PostgreSQL 将 lock_timeoutstatement_timeout 作为两个独立控制项进行说明。前者在等待锁时中止,后者在语句运行过久时中止。对于短时间的元数据操作,可以使用如下前置语句:

SET lock_timeout = '5s';
SET statement_timeout = '60s';
SELECT current_database(), current_user, now();

不要把这些数字直接套用到索引构建或回填上。超时是一个需要由计划解释的预算。如果预期负载下命令需要运行三十分钟,那么六十秒的语句超时只会制造一次可预测的失败。创建并发索引时,应使用不会将命令包在事务中的执行路径:

CREATE INDEX CONCURRENTLY IF NOT EXISTS orders_status_code_idx
ON public.orders (status_code);

命令结束后要验证索引,而不是根据进程正常退出就假定成功。PostgreSQL 会在目录元数据中记录索引有效性,并发构建失败后可能留下无效索引。重试前需要检查并删除它。将这项检查放入 verify.sql,并在适当情况下加入面向应用的查询计划。

真正的中止决策需要明确的信号。如果预检发现意外的迁移版本、存储余量不足、允许窗口之外的活跃阻塞者、备份恢复失败、校验和失败,或结果与验证条件不符,就停止执行。操作人员不应在锁等待不断增长时,还要与代理协商。

批准应将一个人绑定到一次线上运行

要求每次调用都获得批准
将敏感密钥设置为每次使用都需批准,包括每一次生产操作。

当系统要求人们批准每一次 SQL 调用时,就会出现批准疲劳。人们随后会凭习惯批准,或者关闭提示。一次性批准一个定义清晰的代理流程是有用的,前提是批准中明确该流程,流程结束时批准失效,并且不会悄悄覆盖之后的另一个会话。

批准界面必须展示足够上下文,让人能够拒绝这次运行:签名的进程身份、选定目标、制品标识符、计划中的操作类别,以及操作通道是否具备写入能力。它不应显示原始密码,也不应要求代理处理密码。人批准的是操作,而不是泄露秘密。

Sallyport 适合这个边界:它将 API 和 SSH 凭据保存在加密保管库中,让支持 MCP 的代理通过 sp mcp 请求操作,并返回操作结果而不是明文密钥。独立的会话批准和可选的逐密钥批准,可以让生产数据库操作必须经过人工决定,同时不把可重复使用的凭据放入代理上下文。

让批准只覆盖执行阶段。发现代理可以使用只读路径。执行代理可以获得一个随进程结束而失效的会话,并且只能访问获批的操作路径。即时撤销很重要,因为当预检结果令人意外时,操作人员需要能够停止运行,而不是只能等部署脚本结束。

如果团队无法在压力下解释策略,就不要用难以说明的策略语言替代清晰的操作设计。可靠的边界很简单:锁定访问会拒绝所有操作,新执行进程需要授权,特别敏感的凭据可以要求每次使用都重新决定。相比一堆没人记得是谁写下的推断规则,这种边界更容易审计。

审计记录必须在不包含秘密的情况下重建变更

迁移失败后,人们会问一些简单的问题:谁运行了它,针对哪个目标,执行的确切制品是什么,什么时候停止,以及是否修改了数据?普通的终端记录很少能完整回答这些问题。它可能遗漏目标,在清理 shell 历史后丢失命令,或者包含根本不应记录的秘密。

为规划、批准、预检、执行、验证、撤销和失败记录结构化事件。每个事件都应包含迁移标识符和制品摘要,以便调查者将评审记录与线上运行连接起来。记录预检返回的数据库身份,而不只是调用者请求的名称。

最小事件格式可以这样写:

{
  "event": "migration.verify",
  "migration_id": "2025-04-add-orders-status-index",
  "artifact_sha256": "recorded-digest",
  "target_identity": "production-orders",
  "result": "passed",
  "observed": "index valid; query returned expected columns"
}

如果 SQL 参数可能包含个人数据、令牌或客户内容,就不要记录它们。改为记录制品摘要和安全的语句标识符。执行系统可以按照明确的保留策略保存受保护的诊断信息,但审计日志本身应在不变成另一个秘密存储的情况下保持有用。

防篡改证据会改变调查质量。任何人在糟糕的部署之后都能编辑的记录,只能证明某人拥有记录访问权。如果使用哈希链审计日志,就应在事故复盘中独立验证它。Sallyport 的 sp audit verify 可以在离线状态下检查加密审计链,而不需要访问保管库。这正适合不应获得生产凭据的评审者。

验证必须测试工作负载,而不只是 DDL

只批准一个执行进程
新的代理进程首次调用时,会先显示其代码签名权限,然后才能执行本次运行。

迁移成功的标准是预期工作负载运行正常,而不是数据库接受了一条语句。新列可能存在,但应用写入时没有填充它。索引可能有效,但由于谓词或数据类型不同,目标查询无法使用它。约束可能已经验证通过,但旧工作线程仍在发送违反新应用契约的数据。

在执行前写好验证查询,这样评审者才有机会质疑其中的假设。对于索引迁移,检查目录状态并检查促成本次变更的查询。对于回填,统计剩余行,按组查看意外的源值,并确认新的应用写入路径产生预期表示。对于约束,在要求生产环境启用它之前,先在一次性数据库中测试有效和无效写入。

PostgreSQL 的 EXPLAIN 很有用,但也很容易误用。规划器的决定取决于统计信息、参数值、数据分布和配置。EXPLAIN (ANALYZE, BUFFERS) 会实际运行查询并测量它,因此不要随意对高成本的生产查询使用。应在有代表性的安全查询上使用它,并在看到输出前定义可接受的结果。否则每个查询计划都会变成疲惫的操作人员可以勉强解释的东西。

在变更期间和之后观察实时应用指标:请求错误率、受影响路径的查询延迟、连接压力、复制健康状况以及工作线程失败。代理可以收集并展示这些读数,但是否满足既定退出条件,应由发布负责人决定。

不要因为扩展部署已经通过,就安排破坏性的收缩操作。要等到可观测性和发布历史都表明旧应用进程不再依赖旧架构后,再将移除操作作为一份独立的、经过评审的迁移运行。多做这一步,比在回滚期间发现旧版本依赖一小时前刚删除的列,代价低得多。

第一次生产运行应当刻意做到平淡无奇

第一次由代理协助执行的生产迁移,不应在高负载下重构表,而应添加低风险、可观察的变更。可以选择可空的新增列、注释,或其他你已经充分了解其行为的操作。利用这次运行测试各个边界:制品生成、目标选择、批准、备份证据、审计事件、预检失败、执行和验证。

让这次演练证明系统能够拒绝工作。将执行器指向架构指纹错误的目标,确认它会停止。修改已经评审过的文件,确认摘要检查会拒绝它。在一次无害的暂存运行期间撤销授权,确认后续操作会失败。这些测试能揭示控制措施在真正需要时是否有效,而不仅仅是在设计文档中看起来合理。

之后逐一扩大允许的迁移类别。团队能够安全添加列,并不代表它已经证明自己可以回填大表、创建并发索引,或从错误的数据转换中恢复。每个类别都需要独立的实际运行和独立的失败标准。

把生产权限当成代理为一个狭窄任务暂时借用的东西,任务结束后就收回。这种操作习惯能防止的损害,会比任何巧妙的提示词指令都多。

常见问题

应该允许 AI 编程代理执行生产迁移吗?

不应该。规划迁移主要是读取信息、可检查且通常可逆的工作,而执行迁移可能锁定表、消耗磁盘空间或修改线上数据。可以允许代理检查元数据并生成计划,但真正修改生产环境的命令必须单独运行,并经过人工批准。

数据库迁移应该设置多长的超时时间?

当查询目的明确、停止点安全且时间有界时,可以设置超时。PostgreSQL 的 lock_timeout 会在等待锁的时间过长时中止语句,statement_timeout 会在语句运行超过预算时停止它。应有意识地设置这两个值,因为它们都不能替代监控或经过测试的恢复路径。

生产迁移前如何验证备份?

逻辑备份只有在能够恢复它、连接恢复后的数据库,并执行与本次变更相关的检查时,才算真正验证过。至少要检查转储文件,将其恢复到一次性目标数据库,并核对预期的表和行数。没有人恢复过的备份,只是一种假设。

什么时候应该使用 CREATE INDEX CONCURRENTLY?

如果表足够大,普通索引创建会造成不可接受的阻塞,通常可以使用它。PostgreSQL 文档说明,CREATE INDEX CONCURRENTLY 可以避免阻塞写入,但耗时更长,而且不能在事务块中运行。失败后仍需检查是否留下了无效索引。

如何迁移列而不破坏旧版本应用?

把列变更拆成扩展、应用兼容、数据迁移、验证以及后续收缩几个阶段。先添加新的表示形式,让应用能够同时兼容两种形式,再用小批量提交数据,最后在有证据表明旧路径已不再使用后移除它。一次破坏性的语句看起来简洁,却会让你几乎没有回旋余地。

每次数据库迁移都能回滚吗?

破坏性数据重写之后,回滚往往不可能。好的计划会明确写出这一点,并改用遏制措施,例如停止工作线程、禁用新的应用路径、恢复经过测试的备份,或者在运行模型允许的情况下进行故障转移。不要把未经测试的反向 SQL 语句称为回滚计划。

AI 执行迁移时,审计日志应该记录什么?

记录准确的迁移标识符和校验和、操作人员、连接目标、开始和结束时间、SQL 或工具版本、受影响的对象名称、批准记录、备份证据以及验证结果。错误也要记录。如果缺少这些信息,事故复盘就会变成凭记忆拼凑经过。

AI 代理应该如何向生产数据库进行身份验证?

代理应该获得能够检查架构并执行获批迁移命令的连接,而不是可重复使用的管理员密码。将密钥保留在代理上下文之外,把批准绑定到具体进程和会话,并为每次操作保留审计记录。这样既能限制提示词驱动的误操作,也能减少凭据暴露。

什么时候应该中止迁移?

如果目标身份不明确、备份恢复失败、预期架构状态不匹配、等待锁的时间超过设定上限、磁盘可用空间不足,或者线上结果与计划结果不同,就应停止。仅仅因为部署窗口已经打开就继续,是把本可恢复的意外变成长时间中断的常见原因。

AI 代理可以安全地自动化数据库迁移的哪些部分?

架构规划可以大幅自动化,因为它可检查且可重复。生产执行则应使用更窄的自动化边界:固定的制品、明确的目标、规定的时间窗口、绑定到本次运行的批准,以及实时检查。关键边界在于,准备变更和行使修改生产环境的权限是两件事。

Sallyport

Sallyport 替你的 AI 智能体执行 API 调用和 SSH 命令。密钥留在你 Mac 上的本地密钥库里;每次运行由你批准,每个操作都落入一份密封的审计日志。

© 2026 Sallyport · 依据 Apache-2.0 开源 · Oleg Sotnikov