postgres_lsp 的 changingColumnType 规则:用 ALTER COLUMN TYPE 改列类型前的表重写与锁表风险检查
【免费下载链接】postgres_lspA Language Server for Postgres项目地址: https://gitcode.com/GitHub_Trending/po/postgres_lsp
本文以 postgres_lsp(Postgres Language Server)的lint/safety/changingColumnType规则为线索,讲解 ALTER TABLE 修改列类型在 PostgreSQL 中可能引发的整表重写(table rewrite)、ACCESS EXCLUSIVE锁阻塞读写等问题,并给出该规则在postgres-language-server.jsonc中的启用配置、安全类型变更白名单,以及规则源码级判定逻辑与测试用例,帮助你写出可安全上线的迁移脚本。
规则速览
| 属性 | 值 |
|---|---|
| 诊断类别(Diagnostic Category) | lint/safety/changingColumnType |
| 版本(Since) | vnext |
| 默认严重级别 | Warning(警告) |
| 默认是否推荐(recommended) | false(需手动启用) |
| 灵感来源(Sources) | squawk/changing-column-type |
| 所属分组 | safety(安全) |
该规则的元信息定义在 crates/pgls_analyser/src/lint/safety/changing_column_type.rs 的declare_lint_rule!宏中:
pub ChangingColumnType { version: "next", name: "changingColumnType", severity: Severity::Warning, recommended: false, sources: &[RuleSource::Squawk("changing-column-type")], }规则注册于safetylint 组(见生成文件 crates/pgls_analyser/src/lint/safety.rs),该组包含banDropColumn、banDropTable、avoidWideLockWindow、requireConcurrentIndexCreation等 45 条围绕 DDL 安全性的规则,changingColumnType与它们共同构成迁移脚本的“安全护栏”。
为什么修改列类型是危险的
核心结论:修改列类型通常要求整表重写,并在此期间持有排他锁,阻塞读写。
- 大多数列类型变更需要先获取表上的排他锁(exclusive lock),随后将整张表重写为新格式;
- 对于大表而言,重写可能耗时很长,期间所有读操作与写操作都会被阻塞;
- 即使单条语句执行成功,重写也会打破线上客户端的既有假设(例如返回结果类型、驱动层面的类型映射),从而“破坏现有客户端”。
因此changingColumnType规则的触发消息为:
Changing a column type requires a table rewrite and blocks reads and writes.
并附带修复建议:
Consider creating a new column with the desired type, migrating data, and then dropping the old column.
哪些类型变更是安全的
并非所有类型变更都需要重写表。规则将以下三类“放宽型”变更视为安全,不会触发诊断:
- 改为
text:text与varchar/char系列类型二进制兼容; - 改为不带长度限制的
varchar:即去掉长度约束(例如varchar(50)→varchar); - 去掉
numeric精度约束:例如numeric(10,2)→numeric(decimal同理)。
规则给出的合法示例:
ALTER TABLE "core_recipe" ALTER COLUMN "edits" TYPE text;以上三类白名单在规则源码的is_safe_type_widening函数中逐一判定,见 crates/pgls_analyser/src/lint/safety/changing_column_type.rs:
fn is_safe_type_widening(col_def: &pgls_query::protobuf::ColumnDef) -> bool { // ... 提取目标类型名与 typmods(类型修饰符)... let has_type_modifier = !type_name.typmods.is_empty(); match target_type.to_lowercase().as_str() { // text is always safe — binary compatible with varchar/char "text" => true, // varchar without length is safe (dropping a length constraint) "varchar" if !has_type_modifier => true, // numeric without precision is safe (dropping precision constraint) "numeric" | "decimal" if !has_type_modifier => true, _ => false, } }从源码可以推断出两个判定细节:
- 判定依据是“目标类型 + 是否携带类型修饰符(typmods)”:
varchar只有在其后没有长度参数时才安全;numeric/decimal只有在其后没有精度参数时才安全。因此ALTER COLUMN ... TYPE varchar(100)依然会被判定为不安全; - 类型名比较不区分大小写:目标类型会先经
to_lowercase()归一化再参与匹配。
触发示例与诊断输出
不安全示例(触发诊断)
ALTER TABLE "core_recipe" ALTER COLUMN "count" TYPE bigint;运行 lint 后的 CLI 输出如下:
code-block.sql:1:1 lint/safety/changingColumnType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! Changing a column type requires a table rewrite and blocks reads and writes. > 1 │ ALTER TABLE "core_recipe" ALTER COLUMN "count" TYPE bigint; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 │ i Consider creating a new column with the desired type, migrating data, and then dropping the old column.安全的替代方案
对于不安全的类型变更,推荐的迁移流程是“新建列 → 迁移数据 → 删除旧列”三步走,避免直接ALTER COLUMN TYPE引发长时间锁表:
-- 1. 新建目标类型列(ADD COLUMN 默认不重写已有行,所需锁更轻) ALTER TABLE "core_recipe" ADD COLUMN "count_new" bigint; -- 2. 分批迁移数据(可结合批量 UPDATE + 索引,控制单次事务时长) UPDATE "core_recipe" SET "count_new" = "count"::bigint WHERE "count_new" IS NULL; -- 3. 删除旧列并重命名新列 ALTER TABLE "core_recipe" DROP COLUMN "count"; ALTER TABLE "core_recipe" RENAME COLUMN "count_new" TO "count";规则如何工作:源码级实现
规则的执行入口是LinterRule的run方法,见 crates/pgls_analyser/src/lint/safety/changing_column_type.rs,其判定链路如下:
- 将语句解析为
pgls_queryAST,仅当根节点是AlterTableStmt时继续; - 遍历
stmt.cmds中的每条AlterTableCmd,只关注subtype() == AtAlterColumnType(即ALTER COLUMN ... TYPE)的命令; - 从命令的
def中取出ColumnDef,调用is_safe_type_widening判断是否为白名单内的安全放宽; - 若不属于安全放宽,则生成一条
LinterDiagnostic,消息为“Changing a column type requires a table rewrite and blocks reads and writes.”,并附带“新建列、迁移数据、删除旧列”的 detail 建议。
该规则类型为type Options = (),即不接收额外配置参数;其行为完全由源码中的白名单逻辑决定。整体分析流程由 crates/pgls_analyser/src/lib.rs 的Analyser::run驱动:每个语句依次经过已启用规则的flat_map过滤,诊断会附带语句的文本区间(span)。
测试用例验证
仓库中提供了对应的快照测试,文件位于:
- 输入 SQL:crates/pgls_analyser/tests/specs/safety/changingColumnType/basic.sql
- 期望输出:crates/pgls_analyser/tests/specs/safety/changingColumnType/basic.sql.snap
basic.sql通过-- expect_lint/safety/changingColumnType注释声明期望触发的规则,内容正是文档中的“不安全”示例:
-- expect_lint/safety/changingColumnType ALTER TABLE "core_recipe" ALTER COLUMN "count" TYPE bigint;对应的快照断言诊断输出包含× Changing a column type requires a table rewrite and blocks reads and writes.与i Consider creating a new column with the desired type, migrating data, and then dropping the old column.。该测试由 crates/pgls_analyser/tests/rules_tests.rs 驱动,是“文档示例即测试用例”的典型体现,可直接作为本地验证规则行为的入口。
如何配置
该规则默认不随推荐集启用(recommended: false),需要在配置文件中显式开启。postgres_lsp 使用postgres-language-server.jsonc作为配置文件(仓库根目录即有一份示例:postgres-language-server.jsonc,其中顶层linter.enabled控制 linter 总开关,linter.rules控制各规则)。
将changingColumnType设为"error"的完整配置:
{ "linter": { "rules": { "safety": { "changingColumnType": "error" } } } }配置值可为"error"(作为错误阻断)或"warn"(作为警告提示),也可设为"off"关闭该规则。由于规则位于safety分组,也可以先通过"safety": { "recommended": true }开启该组推荐规则,再单独补充此条。
配置文件的解析与规则选项传递链路为:配置 →LinterOptions(见 crates/pgls_analyser/src/linter_options.rs 中的LinterRules结构)→ 经Analyser::new构建规则注册表 → 每次run时按规则过滤执行。完整的 linter 配置结构可参考 crates/pgls_configuration/src/linter/rules.rs。
适用场景与使用建议
- 迁移脚本审查(CI 集成):将 linter 接入 CI(仓库提供
pgls_cli的check命令,相关实现见 crates/pgls_cli/src/commands/check.rs),在合并前拦截ALTER COLUMN TYPE类高风险 DDL; - 编辑器内实时提示:在 VSCode 等编辑器中使用 postgres_lsp 时,开启该规则可在编写 SQL 时即时获得警告;
- 替代方案的落地:对确实需要改类型的场景,优先采用“新建列 → 分批迁移数据 → 删除旧列”,并配合
lock_timeout、批量化更新等手段缩短锁窗口——仓库中avoidWideLockWindow、requireStatementTimeout等safety组规则可进一步约束锁的持有范围; - 注意:规则的“安全白名单”只覆盖了
text、无长度varchar、无精度numeric/decimal三类放宽场景,其余类型变更一律视为不安全,即使某些变更在特定 PostgreSQL 版本中实际可原地完成。因此在严格场景下,还应结合目标库版本与具体类型做人工复核。
【免费下载链接】postgres_lspA Language Server for Postgres项目地址: https://gitcode.com/GitHub_Trending/po/postgres_lsp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考