暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PostgreSQL ADD COLUMN NOT NULL DEFAULT now():这条 ALTER 为什么不该直接过审

原创 Fan() 2026-08-25
70

PostgreSQL ADD COLUMN NOT NULL DEFAULT now():这条 ALTER 为什么不该直接过审

上周有一条变更单,SQL 只有一行:

ALTER TABLE users ADD COLUMN created_at timestamptz NOT NULL DEFAULT now();

开发说:加一列,有默认值,旧行不会变成 NULL,语法也干净。工单系统过了,风格检查过了,有人已经准备在业务低峰执行。

我拦下来了。不是因为这条 SQL 写错,而是因为它在 PostgreSQL 上可能 rewrite 整张表。大表上这意味着长时间锁、WAL 暴涨、磁盘接近翻倍,看起来像「加列」,做起来像「重建表」。

下面按我平时审变更的顺序写:这条 SQL 危险在哪、常见审核为什么放行、用 DeltaScope 离线能看到什么、同类还有哪些漏网,以及它拦不住什么。

1. 这条 ALTER 为什么危险

PostgreSQL 加列并不都便宜。

  • 加可空列、没有 volatile 默认值,很多版本只改目录,几乎不碰堆表。
  • 加 NOT NULL,并且 DEFAULT 是 now()、clock_timestamp() 这类每行求值不同的表达式,优化器没法写成「目录里记一个常量」。引擎可能不得不扫全表、物化每一行的默认值,过程中拿住很重的锁。

社区里把这类命令叫 table rewrite:relfilenode 会变,并发读写被堵住,磁盘可能再吃一份表大小。墨天轮上也有人专门整理过各版本哪些 DDL 会 rewrite,结论很朴素——这类语句不该被当成普通加列。

所以问题不是「SQL 能不能 parse」,而是「它会不会在生产上变成一次表重建」。人审如果只看语法和命名,很容易放行。

2. 常见审核路径会漏什么

我见过的上线前检查,大致就三类。都不「错」,但切口不一样。

工单 / 人审。 人会盯权限、备份、执行窗口,不一定记得 DEFAULT now() 和 DEFAULT 0 在 PostgreSQL 上不是一类操作。单子干净、评论区没人反对,就过了。

风格和语法检查。 缩进、关键字大小写、标识符是否合法,过了也不等于评估了锁和 rewrite。now() 对 linter 来说只是一个函数调用。

在线 DDL 执行器。 gh-ost、pt-osc、各家 Online DDL 解决的是「怎么改」,不是「该不该这样改」。DeltaScope 自己的文档也写了:它不执行任何 SQL,只在执行前看文本和策略,跟在线 DDL 是互补,不是替代。

还有一种更常见的漏:变更被拆成好几条「看起来很小」的语句。MySQL 上同一张表连续两条 ALTER TABLE,每条都像小改动,合在一起却是两次表重建。工单按条过,文件级跨语句关系没人看。

3. 离线跑一遍,DeltaScope 会标哪条规则

DeltaScope 是离线优先的 SQL 审核引擎,支持 MySQL / TiDB / PostgreSQL。默认不连库,只解析 SQL、套策略,给出 blocker / warning / notice,再聚合成 reject / review / pass。当前发布版本是 v0.490.0(2026-08-20),发行说明里登记了 371 条规则:blocker 72、warning 142、notice 157。它不执行提交的 SQL,也不跑 EXPLAIN ANALYZE。

仓库发行说明里有这条现成的命令:

deltascope audit \ --dialect postgresql \ --sql "ALTER TABLE users ADD COLUMN created_at timestamptz NOT NULL DEFAULT now()" \ --format json

预期命中:

项目 值
规则 ID ddl.pg.alter.add_column.non_null_default.rewrite.warn
默认级别 warning
附加事实 not_null、has_default、default_kind(例如 function_call)
默认结论 review,不是 reject

这里必须写清楚,免得读的人以为工具会替你一刀切:

默认 --fail-on 是 blocker。warning 只把 verdict 打成 review,进程退出码仍是 0。CI 里要拦住这类加列,得显式加上 --fail-on warning。策略可以改级别,但改配置时必须带齐 enabled 和 params——只写 level 会整条替换默认策略,规则可能被关掉。用 deltascope config status <rule-id> 看生效状态,不要猜。

我自己的判断:这条不该默认 blocker。有的表很小,低峰 rewrite 一次能接受;有的表几个亿,同样一行 SQL 就是事故。工具把风险标出来,窗口和回滚还是人定。

想看规则正文:

deltascope rules explain ddl.pg.alter.add_column.non_null_default.rewrite.warn

4. 同一类漏网,我还会看这几条

DEFAULT now() 不是孤例。PostgreSQL 迁移里还有几条「语法合法、生产很贵」的写法,能力矩阵和 CLI 文档都登记了,默认也都是 warning。

CHECK 不加 NOT VALID。 一次校验加 ACCESS EXCLUSIVE,大表等于锁表扫描。

deltascope audit \ --dialect postgresql \ --sql "ALTER TABLE orders ADD CONSTRAINT amount_positive CHECK (amount >= 0);"

文档里的 JSON 摘录是:

{ "rule_id": "ddl.pg.alter.add_check.not_valid.require", "level": "warning", "message": "ADD CHECK constraint should use NOT VALID to avoid full table scan with ACCESS EXCLUSIVE lock" }

更稳的拆法:先 ADD CONSTRAINT … NOT VALID,再在同一批 SQL 里 VALIDATE CONSTRAINT。从 v0.42.0 起,只有 NOT VALID、后面没有对应 VALIDATE,还会再打一条 ddl.pg.alter.not_valid_constraint.validate.require。

CREATE INDEX 不带 CONCURRENTLY。 默认建索引会挡住读写。规则是 ddl.pg.create_index.concurrently.require,默认 warning。

MySQL 同一张表拆多条 ALTER。 官网规则表和示例配置里的 ID 是 ddl.alter.merge.mysql.require,默认 warning。能力矩阵里同一条写成 ddl.alter.merge.mysql。行为是:多条 ALTER TABLE 打同一张表,建议合成一条,少做几次重建。跨文件检测不到,必须放进同一次 deltascope audit。

这些都不是 DROP TABLE,也不是「SQL 写错了」。它们是合法但昂贵。

5. 对照:没有 WHERE 的 DELETE,默认就是 reject

ALTER 之外,DML 有一条几乎所有人都会认的红线。README 里的例子:

deltascope audit --sql "delete from users"

文档里的输出摘录:

Verdict: reject Statements: 1 Blockers: 1 Warnings: 0 Notices: 0 Statement 1: DELETE - [blocker] dml.where.require: UPDATE and DELETE statements must include a WHERE clause

UPDATE / DELETE 带 LIMIT、带子查询,也有对应规则。带 WHERE 的删除,离线模式只按 SQL 形状估影响行数;连上库、用只读统计(PostgreSQL 上还会用 planner 的 EXPLAIN,不是 EXPLAIN ANALYZE)可以把估算收得更紧。阈值规则是 dml.impact.rows.max_count 和 dml.impact.ratio.max_percent。

我把 ALTER 放前面、DELETE 放后面,是因为没有 WHERE 的 DELETE 太好认,工单里本来就难混过去。DEFAULT now() 这种才是人审容易点过、事后难收场的。

6. 离线够用,什么时候该连库

默认离线。笔记本、CI、agent 会话都不必先塞数据库账号。

连库是可选项。实例相关的检查——列是否已经存在、索引是否已经在、InnoDB 索引键长是否超限——离线会被跳过,不会假装「过了」。README 里的连库例子:

deltascope audit \ --sql "alter table orders add index idx_status (status)" \ --host 127.0.0.1 --port 3306 --user root --ask-password --schema app

PostgreSQL 连库要显式 --dialect postgresql,不会自动侦测。命令行不再接受 --password,改用 --ask-password、--password-env 或 --password-file。

离线回答「这句话本身犯不犯规」;连库回答「对这台实例、这张表现在合不合适」。两层不要混成一句「必须连生产才能审」。

方言默认是 mysql。SQL 长得像 PostgreSQL、却没加 --dialect postgresql 时,它会打一条 dialect.postgresql.syntax.detected.notice,不会自动切方言。审 PG 迁移却看到一堆 MySQL 规则,先检查方言,别先改 SQL。

从 v0.43.0 起,默认策略按方言隔离:--dialect postgresql 不再报缺 UNSIGNED / ENGINE;MySQL / TiDB 也不会冒出 ddl.pg.*。

7. 安装和放进流水线

当前发布是 v0.490.0,产物覆盖 macOS / Linux 的 amd64 和 arm64,archive 里带 deltascope、deltascope-server、deltascope-mcp。README 上没有 Windows 安装入口。

macOS(README 推荐):

brew tap Fanduzi/deltascope brew install --cask deltascope

通用安装(中英文 README 都有):

curl -fsSL https://raw.githubusercontent.com/Fanduzi/DeltaScope/main/install.sh | sh

钉死版本:

curl -fsSL https://raw.githubusercontent.com/Fanduzi/DeltaScope/v0.490.0/install.sh | \ DELTASCOPE_VERSION=v0.490.0 sh

审文件:

deltascope audit --file ./migrations/20260328_add_column.sql

CI 里我通常这样:策略文件进仓库;审核步放在 migrate / flyway 之前;默认 --fail-on blocker,要对 warning 失败再收紧;机器读 JSON / SARIF / GitLab Code Quality,人看 markdown。文档还写了 --format github-actions、--format github-summary、--format gitlab-codequality。

deltascope audit --file ./migrations.sql --format json --fail-on warning

HTTP 是同一套引擎的另一张脸:POST /v1/audit。HTTP 不能在请求体里塞账号密码,只能引用服务端 runtime config 里的 connection_id。CLI 仍可用 --host 这些旗标。中英文 README 的服务启动写法不一致,这里不抄启动命令,以免抄错。

它不是工单平台,没有审批流;不是 schema diff,审的是你提交的 SQL,不是两套 schema 的差;也不是执行后的审计日志。

8. 给 agent 用的 MCP(可选)

人审和 CI 先跑起来,再考虑 MCP。它不是某一个编辑器的专用接口。

官方 MCP 服务暴露四个工具,用来审核 SQL、解释规则、列出规则和查询能力。没有 Query Access 工具。启动器按平台下载官方发布包。需要 Node.js 24 或更高,且只支持 macOS 与 Linux(amd64 / arm64)。

四个工具名是:audit_sql、describe_rule、list_rules、get_capabilities。

推荐启动器(文档写法):npx -y @fanduzi/deltascope-mcp

通用 stdio 配置示例:

[mcp_servers.deltascope] command = "npx" args = ["-y", "@fanduzi/deltascope-mcp"] startup_timeout_sec = 20

已经装过 installer 或 Homebrew 的,可以直接跑本地二进制 deltascope-mcp。

审核契约和 CLI 相同:同一套规则 ID、同一套 verdict。agent 改 SQL 时对着结构化结果改,不必从一段散文里猜哪条没过。

9. 我不会用它做的事

写清楚边界,比多列几个功能有用。

  • 不执行 SQL,不替代 gh-ost / pt-osc / 原生 Online DDL。
  • 不替代人审窗口、备份、回滚。warning 默认不失败,别以为装上就进了门禁。
  • 不是完整 PostgreSQL DDL 覆盖。生成列、部分 identity 形态、部分 parser-error 会明确说没审,不要把「没报错」当成「没问题」。
  • Query Access 是另一条能力(只读分类、是否可接纳),不是这次说的变更审核,也不在 MCP 工具集里。

项目在 GitHub:https://github.com/Fanduzi/DeltaScope
站点:https://deltascope.pages.dev/
许可证:Apache 2.0

10. 小结

回头看那一行 SQL:

ALTER TABLE users ADD COLUMN created_at timestamptz NOT NULL DEFAULT now();

语法没问题,工单也干净。它危险,是因为在 PostgreSQL 上可能 rewrite 整张表。风格检查看不到锁,执行器假设你已经决定要跑。

DeltaScope 离线就能标出 ddl.pg.alter.add_column.non_null_default.rewrite.warn。默认是 warning,结论是 review。要把它变成门禁,把这条规则升到 blocker,或者 CI 用 --fail-on warning。

我自己的用法很窄:本地先过一遍变更文件,CI 用同一份 deltascope.yaml 再过一遍,连库只留给「列在不在、索引在不在」这种必须看实例的问题。MCP 有需要再挂。工具负责把规则 ID 和级别摆到桌面上,过不过,还是 DBA 签字。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论