postgres_lsp 锁安全规则详解:runningStatementWhileHoldingAccessExclusive 与 ACCESS EXCLUSIVE 锁泄漏检测
2026/9/18 18:45:48 网站建设 项目流程

postgres_lsp 锁安全规则详解:runningStatementWhileHoldingAccessExclusive 与 ACCESS EXCLUSIVE 锁泄漏检测

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

本篇文章围绕 postgres_lsp 的lint/safety/runningStatementWhileHoldingAccessExclusive规则展开,系统讲解它如何检测"持有 ACCESS EXCLUSIVE 锁期间继续执行语句"这一高危模式、其底层事务状态跟踪实现,以及如何在项目中配置与规避。读完本文,你将理解该规则的工作原理、适用场景,并能把"锁内执行额外语句"这一隐患纳入日常 SQL 审查与 CI 流程。

规则概述

runningStatementWhileHoldingAccessExclusive是 postgres_lsp 中safety规则组的一员,属于lint/safety诊断类别,自vnext版本起可用。该规则已在官方文档中标记为recommended:一旦启用,lint 时若命中会直接产生诊断错误,提示开发者及时处理。

规则灵感来源于 eugene 的 E4 提示(对应源码中的RuleSource::Eugene("E4"),见 规则声明文件)。

问题背景:ACCESS EXCLUSIVE 锁的代价

在 PostgreSQL 中,ALTER TABLE等 DDL 操作会对目标表施加ACCESS EXCLUSIVE锁。这是最重的一级表级锁,它会阻塞该表上的所有并发操作,包括:

  • SELECT(读取也被阻塞)
  • INSERTUPDATEDELETE(写入同样被阻塞)
  • 其他 DDL 与索引维护操作

更关键的是,锁的持有时间取决于整个事务的持续时间,而非单条语句的时长。如果开发者在同一个事务里先执行ALTER TABLE再执行其他查询,那么这些额外语句会不断延长锁的持有窗口,期间任何对该表的访问都会排队等待,严重时会造成线上服务大面积阻塞。

特别需要警惕的是,即使像SELECT COUNT(*)这样的简单查询,也可能显著拉长锁的持有时间——这正是该规则要拦截的核心场景。

触发条件与检测逻辑

什么时候会报错

规则源码(running_statement_while_holding_access_exclusive.rs)中的判断逻辑非常简洁:

let tx_state = ctx.file_context().transaction_state(); if tx_state.is_holding_access_exclusive() { diagnostics.push(LinterDiagnostic::new(...)); }

即:只要当前语句执行时,文件级事务状态跟踪器显示"正在持有 ACCESS EXCLUSIVE 锁",该语句就会被标记,无论它是一条SELECTINSERTUPDATE还是CREATE INDEX

规则产生的诊断消息包含三层信息:

  • 主消息:"Running statement while holding ACCESS EXCLUSIVE lock."
  • 详情:"This blocks all access to the table for the duration of this statement."
  • 提示:"Run this statement in a separate transaction to minimize lock duration."

锁状态是如何被跟踪的

postgres_lsp 的 linter 在分析一个 SQL 文件时,会按顺序遍历每条语句,并通过AnalysedFileContext维护一个跨语句的TransactionState(见 linter_context.rs)。TransactionState专门记录"当前事务是否持有 ACCESS EXCLUSIVE 锁"(holding_access_exclusive字段),其更新逻辑(update_from_stmt)有以下关键行为:

  1. 锁的获取:当遇到针对已存在表ALTER TABLE语句,且其子命令确实需要 ACCESS EXCLUSIVE 锁时,将holding_access_exclusive置为true
  2. 子命令的精细判断:并非所有ALTER TABLE子命令都会升级到 ACCESS EXCLUSIVE 锁。例如VALIDATE CONSTRAINT实际只取SHARE UPDATE EXCLUSIVE,因此被显式排除(见is_access_exclusive_subcommand,源码中通过!matches!(subtype, AtValidateConstraint)实现)。这种精细度避免了误报。
  3. 新建对象的豁免:若ALTER TABLE针对的是本事务内刚创建的表(通过has_created_object判断),不会计入锁持有——因为新建表上不存在并发读者,风险可忽略。
  4. 事务边界的重置:遇到COMMITROLLBACK(以及ROLLBACK TO SAVEPOINT等)时调用reset_transaction_state()清空累计状态,确保不同事务之间互不污染、不产生跨事务的误报;而BEGINSAVEPOINT会递增嵌套深度。

此外,同一套TransactionState还被avoid_wide_lock_windowrequire_idle_in_transaction_timeoutrequire_statement_timeoutlock_timeout_warning等 safety 规则共享,共同构成 postgres_lsp 的"事务与锁安全"检测家族。

实测用例印证

仓库自带的规则测试充分覆盖了各种触发形态(测试目录:runningStatementWhileHoldingAccessExclusive):

测试文件场景触发语句
basic.sqlALTER TABLE 后执行 SELECTSELECT COUNT(*) FROM authors;
insert_after_alter.sqlALTER TABLE 后执行 INSERTINSERT INTO books (title, isbn) VALUES (...);
create_index_after_alter.sqlALTER TABLE 后创建索引CREATE INDEX orders_total_idx ON orders(total);
multiple_statements.sql同事务内多条后续语句UPDATESELECT均被标记

对应快照basic.sql.snap记录了精确的诊断输出格式,与源码中定义的消息文案完全一致。注意multiple_statements.sql中每条后续语句都通过expect_lint标注,说明事务内 ALTER TABLE 之后的每一条语句都会独立产生一条诊断,而非只报一次。

正确用法与反例

反例(会触发诊断)

同一事务中在ALTER TABLE之后继续执行任何语句,都会触发该规则:

-- 反例 1:ALTER TABLE 后立即 SELECT ALTER TABLE authors ADD COLUMN email TEXT; SELECT COUNT(*) FROM authors; -- 反例 2:ALTER TABLE 后执行 INSERT ALTER TABLE books ADD COLUMN isbn TEXT; INSERT INTO books (title, isbn) VALUES ('Database Systems', '978-0-1234567-8-9'); -- 反例 3:ALTER TABLE 后创建索引 ALTER TABLE orders ADD COLUMN total DECIMAL(10, 2); CREATE INDEX orders_total_idx ON orders(total);

正例(合规写法)

ALTER TABLE独占一个事务,其余操作放入独立事务:

-- 单独执行 ALTER TABLE,尽快释放 ACCESS EXCLUSIVE 锁 ALTER TABLE authors ADD COLUMN email TEXT; -- 锁已释放,在后续独立事务中再执行其他查询 SELECT COUNT(*) FROM authors;

如果确实需要在同一事务中执行多条 DDL,也应当优先使用并发安全写法(例如CREATE INDEX CONCURRENTLY),减少锁窗口对线上读写的影响。

如何配置该规则

配置项说明

规则在linter.rules.safety分组下,键名为runningStatementWhileHoldingAccessExclusive。它是一个标准的规则配置项,源码中对应结构体位于 linter/rules.rs,可取值包括:

  • "error":命中即报错(推荐,规则本身默认 recommended)
  • "warn":命中仅告警
  • "off":关闭规则
  • 或使用更细粒度的{ "level": "warn" }对象形式

JSON 配置示例

在 postgres-language-server.jsonc 配置文件中启用:

{ "linter": { "rules": { "safety": { "runningStatementWhileHoldingAccessExclusive": "error" } } } }

由于该规则默认 recommended,即使不显式配置,在默认推荐的 lint 流程中也会生效,出现诊断错误提示。你可以在 docs/reference/rules.md 中查看全部 safety 规则的索引与说明。

实战建议:把锁安全写进迁移审查

结合本规则与同组的其他锁安全规则(如require_statement_timeoutavoid_wide_lock_windowrequire_separate_constraint_validation),可以在提交前拦截绝大多数危险的锁模式。建议在团队中约定如下检查清单:

  1. 任何ALTER TABLE语句独立成事务,事务内不混入 SELECT/INSERT/UPDATE;
  2. 需要长时间 DDL(建索引、刷新物化视图)时优先使用CONCURRENTLY
  3. runningStatementWhileHoldingAccessExclusive: "error"写入共享配置,接入 CI 检查流程,让数据库迁移脚本的合并请求自动被锁安全扫描把关。

通过静态分析在代码审查阶段就发现"锁内执行语句"的隐患,比在生产环境遇到锁等待风暴再排查要高效得多——这正是该规则存在的意义。

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

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

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

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

立即咨询