GBase 8c 会话、锁等待和长 SQL 的日常巡检写法
数据库状态正常,不代表业务访问一定顺畅。GBase 8c 日常运维里,经常会遇到实例状态是正常的,CPU 也没有完全打满,但应用接口开始变慢,报表任务排队,甚至某些更新长时间不返回。继续往下查,才发现是锁等待、长事务、长 SQL 或会话堆积引起的局部堵塞。
这类问题最适合通过日常巡检提前发现。官方例行维护资料中提到,可以检查锁信息、查询等待锁的线程状态、查看 pg_stat_activity 中的时间字段、统计当前会话数、查询内存占用较高的会话等。把这些检查做成固定动作,比出问题后临时翻 SQL 更可靠。
巡检先看"有没有堵",再看"谁最重"
会话巡检不要一开始就追求复杂。最基础的三个问题是:当前会话数是否异常、是否存在锁等待、是否有 SQL 运行时间过长。只要这三个问题回答清楚,已经能覆盖很多现场故障。
sql
-- 当前会话总数
SELECT count(*) AS session_count
FROM pg_stat_activity;
-- 查看会话关键时间点
SELECT pid,
usename,
datname,
backend_start,
xact_start,
query_start,
state_change,
state,
query
FROM pg_stat_activity
ORDER BY query_start NULLS LAST;
backend_start 表示连接建立时间,xact_start 表示事务开始时间,query_start 表示当前 SQL 开始时间,state_change 表示状态变化时间。排查时不要只看 query_start。一个会话可能当前没有执行 SQL,但事务已经开了很久,这种长事务同样可能影响对象维护和锁释放。
| 字段 | 关注点 | 典型问题 |
|---|---|---|
backend_start |
连接存在多久 | 连接池泄露、僵尸连接 |
xact_start |
事务持续多久 | 长事务不提交 |
query_start |
SQL 运行多久 | 慢 SQL 或阻塞 SQL |
state_change |
状态停留多久 | idle in transaction 等待 |
state |
当前状态 | active、idle、等待状态 |
锁等待要单独拉出来看
锁等待问题不能只靠业务反馈。某些锁等待在一开始只影响少量会话,等应用线程池被占满后才会变成明显事故。日常巡检可以直接查锁信息和等待锁线程状态。
sql
-- 查询锁信息
SELECT * FROM pg_locks;
-- 查询等待锁的线程状态信息
SELECT *
FROM pg_thread_wait_status
WHERE wait_status = 'acquire lock';
如果发现等待锁,会继续关联 pg_stat_activity 看等待会话和可能的阻塞会话。不同版本字段可能略有差异,现场可以按实际字段调整。
sql
SELECT a.pid,
a.usename,
a.datname,
a.xact_start,
a.query_start,
a.state,
a.query
FROM pg_stat_activity a
WHERE a.pid IN (
SELECT pid FROM pg_locks WHERE NOT granted
);
锁等待处理要谨慎。不要看到等待就立刻 kill。先判断阻塞者在做什么,是正常批处理、DDL、未提交事务,还是异常会话。如果阻塞者是关键交易或正在执行不可中断维护,随手杀进程可能造成更大问题。
长事务比长 SQL 更隐蔽
长 SQL 通常容易被发现,因为它一直 active;长事务更隐蔽,因为它可能处于 idle 状态,但事务没有提交。应用代码里开启事务后异常退出,或者连接池复用时没有正确提交/回滚,都可能留下这类会话。
sql
SELECT pid,
usename,
datname,
now() - xact_start AS xact_age,
state,
query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_age DESC;
对长事务要结合业务判断。如果是批量更新、数据修复或迁移任务,长事务可能有合理原因;如果是普通应用账号,事务开了几个小时还 idle,就很可疑。处理前最好先联系业务方确认,必要时保留 SQL、账号、客户端地址和时间点。
内存占用高的会话要和 SQL 一起看
GBase 8c 例行维护资料中提到可以通过 pv_session_memory_detail() 查询当前使用内存最多的会话信息。内存高不一定是异常,大排序、大聚合、大 Hash Join 都可能短时间占用较多内存。但如果同一类 SQL 长期占用大量内存,就要考虑执行计划、统计信息、参数和业务写法。
sql
SELECT *
FROM pv_session_memory_detail()
ORDER BY usedsize DESC
LIMIT 10;
拿到高内存会话后,不要只记录 pid,还要回到 pg_stat_activity 找 SQL。否则只知道哪个会话重,不知道为什么重。
sql
SELECT pid, usename, datname, query_start, state, query
FROM pg_stat_activity
WHERE pid = <pid_from_memory_detail>;
巡检报告里建议写成"会话 + SQL + 资源 + 时间"的组合,而不是只写"某 pid 内存高"。
结束进程要有分级策略
官方例行维护文档提到可以查找正在运行的系统进程并使用 kill 命令结束进程。但生产环境处理会话和进程时必须分级。能通过数据库函数取消 SQL 的,不要直接操作系统 kill -9;能等待业务提交的,不要急着中断;确实要杀,也要记录原因和影响范围。
一个更稳的分级思路是:
text
1. 先确认会话是否仍有业务价值
2. 联系业务或调度负责人确认是否可中断
3. 优先取消当前 SQL
4. 取消无效再终止会话
5. 操作系统级 kill 作为最后手段
6. 操作后确认锁释放、业务恢复、无异常回滚扩大
直接 kill -9 的问题在于它绕过了很多正常收尾路径。极端情况下可以用,但不应该成为常规处理方式。
巡检输出要可读
巡检脚本不应该只吐一堆原始 SQL 结果。真正有用的巡检报告应该能告诉值班人员:有没有异常、异常是谁、持续多久、建议动作是什么。
text
检查时间:2026-05-12 09:00
会话总数:326,较昨日同时间高 18%
锁等待:2 个,最长等待 00:08:31
长事务:1 个,账号 app_order,持续 03:12:44,状态 idle in transaction
长 SQL:3 个,最长 00:26:10,均为 rpt_app 报表查询
内存 Top1:pid 287631,usedsize 8.2GB,SQL 为月度汇总
建议:联系 app_order 负责人确认长事务;报表查询进入慢 SQL 复盘
这种报告比单纯贴查询结果更适合日常交接。尤其是夜间值班,明确建议动作能减少误操作。
常见坑
| 常见坑 | 后果 | 建议 |
|---|---|---|
| 只看实例状态 | 局部堵塞被忽略 | 增加会话和锁巡检 |
| 只看 active SQL | 长事务漏掉 | 同时看 xact_start |
| 看到锁等待就杀 | 可能中断关键任务 | 先识别阻塞者 |
| 只记录 pid | 复盘时找不到业务 | 同时记录账号、库、SQL |
| 不看客户端来源 | 无法定位应用 | 记录客户端地址和应用名 |
| kill 后不复查 | 锁未释放或任务重试 | 操作后重新巡检 |
结尾总结
GBase 8c 日常巡检不应该只停留在实例存活。会话数量、锁等待、长事务、长 SQL、内存占用,这些指标更贴近业务体验。很多故障在变成全局事故前,都会先以少量锁等待或长事务形式出现。
比较稳的做法是把 pg_stat_activity、pg_locks、pg_thread_wait_status、pv_session_memory_detail() 等检查固定下来,输出成可读报告。发现异常后先定位业务和 SQL,再决定取消、终止还是等待。这样处理会比临时登录机器找进程安全得多,也更容易形成可复盘的运维闭环。
参考资料
text
[1] GBase 8c 例行维护官方文档 https://www.gbase.cn/docs/gbase-8c/02%20%E7%AE%A1%E7%90%86%E5%91%98%E6%8C%87%E5%8D%97/03%20%E8%BF%90%E7%BB%B4%E7%AE%A1%E7%90%86/%E4%BE%8B%E8%A1%8C%E7%BB%B4%E6%8A%A4
[2] GBase 社区优质文章区 https://www.gbase.cn/community/section/11