postgres_lsp 的 changingColumnType 规则:用 ALTER COLUMN TYPE 改列类型前的表重写与锁表风险检查
2026/9/18 21:51:45 网站建设 项目流程

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),该组包含banDropColumnbanDropTableavoidWideLockWindowrequireConcurrentIndexCreation等 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.

哪些类型变更是安全的

并非所有类型变更都需要重写表。规则将以下三类“放宽型”变更视为安全,不会触发诊断:

  1. 改为texttextvarchar/char系列类型二进制兼容;
  2. 改为不带长度限制的varchar:即去掉长度约束(例如varchar(50)varchar);
  3. 去掉numeric精度约束:例如numeric(10,2)numericdecimal同理)。

规则给出的合法示例:

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";

规则如何工作:源码级实现

规则的执行入口是LinterRulerun方法,见 crates/pgls_analyser/src/lint/safety/changing_column_type.rs,其判定链路如下:

  1. 将语句解析为pgls_queryAST,仅当根节点是AlterTableStmt时继续;
  2. 遍历stmt.cmds中的每条AlterTableCmd,只关注subtype() == AtAlterColumnType(即ALTER COLUMN ... TYPE)的命令;
  3. 从命令的def中取出ColumnDef,调用is_safe_type_widening判断是否为白名单内的安全放宽;
  4. 若不属于安全放宽,则生成一条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_clicheck命令,相关实现见 crates/pgls_cli/src/commands/check.rs),在合并前拦截ALTER COLUMN TYPE类高风险 DDL;
  • 编辑器内实时提示:在 VSCode 等编辑器中使用 postgres_lsp 时,开启该规则可在编写 SQL 时即时获得警告;
  • 替代方案的落地:对确实需要改类型的场景,优先采用“新建列 → 分批迁移数据 → 删除旧列”,并配合lock_timeout、批量化更新等手段缩短锁窗口——仓库中avoidWideLockWindowrequireStatementTimeoutsafety组规则可进一步约束锁的持有范围;
  • 注意:规则的“安全白名单”只覆盖了text、无长度varchar、无精度numeric/decimal三类放宽场景,其余类型变更一律视为不安全,即使某些变更在特定 PostgreSQL 版本中实际可原地完成。因此在严格场景下,还应结合目标库版本与具体类型做人工复核。

【免费下载链接】postgres_lspA Language Server for Postgres项目地址: https://gitcode.com/GitHub_Trending/po/postgres_lsp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询