CodeGym /课程 /SQL SELF /锁和冲突监控

锁和冲突监控

SQL SELF
第 46 级, 课程 2
可用

想象一下你在办公室,结果所有门都被锁了,因为有个人把钥匙忘在房间里了。PostgreSQL 里的锁其实就差不多这样。如果有个查询或者事务锁住了某个资源,其他想访问同一个资源的操作就只能等它结束。这种情况会导致延迟、冲突,严重的话甚至让系统卡死。

什么时候会有锁?

PostgreSQL 里的锁(Locks)是用来管理并发访问数据的。它们会在这些场景下出现:

  1. 执行写操作的时候:UPDATE、DELETE、INSERT。
  2. 有事务占着资源不放,比实际需要的时间还久。
  3. 不同事务抢同一个资源时发生冲突。

真实的数据库就像“资源争夺战场”,就算你觉得系统很稳,一个不小心的事务也能把一切“卡死”,就像 Git 里合并失败一样。

分析锁的工具:pg_locks

pg_locks 是 PostgreSQL 的系统视图,能看到当前有哪些锁被事务持有或者在等待。它能帮你搞清楚:“谁在持有锁,谁在等锁?”

pg_locks 主要字段:

  • locktype:锁的类型(比如 relation、transaction、page、tuple)。
  • database:数据库的 ID。
  • relation:表的 ID(如果锁和表有关)。
  • mode:锁的模式(比如 RowExclusiveLock、AccessShareLock)。
  • granted:标志,表示锁是不是已经拿到了(true),还是事务还在等(false)。

注意: PostgreSQL 用的是“分级锁模式”。意思是,不同操作会加不同强度的锁(比如读数据用 AccessShareLock,改表结构用 ExclusiveLock)。

例子:查看所有当前锁

SELECT *
FROM pg_locks;

但如果你直接查 pg_locks,信息会太多太杂。我们来点有用的!

例子:还没拿到的锁(也就是事务在等)

SELECT pid, locktype, relation::regclass AS table_name, mode, granted
FROM pg_locks
WHERE NOT granted;

这里发生了什么?

  • 我们筛选 granted = false 的记录,也就是锁还没拿到。
  • relation::regclass 把表的 ID 转成表名,看起来更直观。

输出大概长这样:

pid locktype table_name mode granted
1234 relation students RowExclusiveLock false
4321 relation courses RowShareLock false

这些查询能帮你定位到底是哪张表/资源被锁了,哪个事务可能是元凶。

分析冲突:pg_blocking_pids()

锁还算小事,要是一个事务卡住另一个怎么办?PostgreSQL 提供了个很方便的办法,用 pg_blocking_pids() 函数找“罪魁祸首”。

pg_blocking_pids() 会返回一组进程 ID(pid),这些进程正在阻塞当前事务。

例子:找出正在阻塞别人的事务

SELECT pid, pg_blocking_pids(pid) AS blocking_pids
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0;

这里发生了什么?

  • 我们用 pg_stat_activity 视图查系统里活跃的进程。
  • pg_blocking_pids(pid) 会返回每个 pid 被哪些进程卡住。如果列表不空(长度大于 0),说明这个进程被锁住了。

结果示例:

pid blocking_pids
4567 {1234, 5678}
6789 {4321}

比如 pid = 4567 这个事务被 1234 和 5678 卡住了。我们就找到了“元凶”。

终止阻塞进程

当你找到了阻塞别人的进程,可以用 pg_terminate_backend() 把它干掉:

SELECT pg_terminate_backend(1234); -- “干掉”进程 1234

但要小心!强制终止进程会让当前事务的数据回滚。这个操作就像“核按钮”,不到万不得已别乱用。

实战:锁分析场景

假设我们有个大学数据库,里面有 students 和 enrollments 两张表。现在有好几个事务同时往 enrollments 写数据,结果就出现了锁。

  1. 定位锁:
SELECT pid, locktype, relation::regclass AS table_name, mode, granted
FROM pg_locks
WHERE NOT granted;
  1. 找出阻塞进程:
SELECT pid, pg_blocking_pids(pid) AS blocking_pids
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0;
  1. 解除锁:

强制终止其中一个冲突进程:

SELECT pg_terminate_backend(1234); -- 终止进程 1234

注意: 在“干掉”进程之前,最好先搞清楚为啥会有锁。也许你该重新设计事务逻辑。

常见错误和预防方法

锁经常是因为事务管理不当造成的。比如:

错误: 一个事务拿着锁不干活,啥都不操作(状态是“idle in transaction”)。

解决: 用 pg_stat_activity 盯着事务状态,及时终止“挂起”的事务。

SELECT pid, state, query
FROM pg_stat_activity
WHERE state = 'idle in transaction';

错误: 查询忘了加索引,导致整张表都被锁了。

解决: 优化查询,对常用条件加索引。

“谁等谁”依赖树表格

为了方便排查,你可以画出依赖树,看看哪个事务卡着哪个:

WITH RECURSIVE blocking_tree AS (
  SELECT pid, pg_blocking_pids(pid) AS blocked_by
  FROM pg_stat_activity
  WHERE cardinality(pg_blocking_pids(pid)) > 0
  UNION ALL
  SELECT a.pid, pg_blocking_pids(a.pid)
  FROM pg_stat_activity a
  JOIN blocking_tree b ON a.pid = ANY(b.blocked_by)
)
SELECT pid, blocked_by FROM blocking_tree;

结果:

pid blocked_by
4567 {1234}
1234 {5678}
5678 {}

这里能看到,进程 5678 卡着 1234,1234 又卡着 4567。

2
任务
SQL SELF, 第 46 级, 课程 2
已锁定
获取系统中所有当前锁的信息
获取系统中所有当前锁的信息
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION