postgresql-postgresql中检查慢查询的原因
在 PostgreSQL 数据库中,查询变慢的原因多种多样,其中锁等待和长时间未提交的事务是两种常见但容易被忽视的因素。本文将介绍如何利用 pg_stat_activity(SQL语句执行视图) 和 pg_locks(PG锁视图) 这两个系统视图,系统地排查锁与事务引起的问题。
事务与状态#
在 PostgreSQL 中,事务 是一组原子性的 SQL 操作,要么全部成功,要么全部回滚。会话状态(pg_stat_activity.state)则描述了当前连接在某一时刻正在做什么。一个会话可能处于以下主要状态:
active:正在执行查询。idle:空闲,没有事务,等待客户端发送命令。idle in transaction:事务已开启,但当前没有执行查询,正在等待客户端发送下一条 SQL 命令。idle in transaction (aborted):事务已开启,但遇到错误,等待客户端发送ROLLBACK或COMMIT。fastpath function call:正在执行快速路径函数(很少见)。
事务与状态的关系:事务可以影响状态,例如开启事务后若未执行查询,状态即为 idle in transaction;但状态并不总代表事务的存在,例如 idle 状态时可能没有事务(xact_start 为 NULL)。因此,排查问题时应同时关注状态和事务的开始时间。
常用字段及含义#
pg_stat_activity 视图关键字段#
- pid:后端进程 ID。
- datname:数据库名。
- usename:用户名。
- application_name:客户端应用名称(可在连接串中设置)。
- client_addr:客户端 IP 地址。
- backend_start:连接建立时间。
- xact_start:当前事务开始时间(若没有事务则为
NULL)。 - query_start:当前查询开始时间(若状态为
idle则为上一条查询的开始时间)。 - state_change:状态改变的时间(例如从
active变为idle的时刻)。 - state:会话当前状态。
- wait_event_type:等待事件类型(如
Lock、Client、IO、LWLock等)。表示会话正在等待的资源类型或子系统。 - wait_event:表示该
wait_event_type下的具体等待点,具体的等待事件名称(如ClientRead、transactionid、relation等)。 - query:当前或最近执行的 SQL 语句。
时间间隔转换示例:
SELECT pid, now() - xact_start AS xact_duration FROM pg_stat_activity WHERE xact_start IS NOT NULL;
pg_locks 视图关键字段#
- locktype:锁的类型,包括
relation(表锁)、transactionid(事务 ID 锁)、virtualxid、tuple(行级锁)、advisory(咨询锁)、object、page等。 - database:数据库 OID(可用
datname关联)。 - relation:关系 OID,当
locktype = 'relation'时有效,可用relation::regclass转换为表名。 - page / tuple:分别表示页号或元组号,用于行级锁。
- transactionid:事务 ID,用于事务级锁。
- classid / objid / objsubid:用于标识特定对象(如咨询锁的键)。
- mode:锁的模式,例如
AccessShareLock、RowShareLock、RowExclusiveLock、ShareLock、ExclusiveLock、AccessExclusiveLock等。 - granted:布尔值,
true表示已获得锁,false表示正在等待锁。 - pid:持有或等待该锁的进程 ID。
- fastpath:是否通过快速路径获取锁。
排查思路#
- 观察会话状态分布
通过pg_stat_activity查看各状态的会话数量,重点关注active(长时间运行的查询)和idle in transaction(长期未提交的事务)。 - 识别长时间运行的活动查询
对于active状态的会话,检查query_start与当前时间的差值。如果查询运行时间异常,进一步查看wait_event_type和wait_event,判断是否在等待锁。 - 检查锁等待与阻塞关系
利用pg_blocking_pids()函数找出阻塞者,或直接查询pg_locks中granted = false的记录,关联pg_stat_activity获取等待者的查询和状态。 - 分析空闲事务
idle in transaction状态的事务会持有已获得的锁,并占用连接资源。应查看其持续时间、客户端地址、最后执行的查询,判断是否是应用逻辑问题(如未及时提交或异常处理不当)。 - 定位具体客户端或应用
通过client_addr、application_name等字段,将问题会话与具体的应用实例关联起来,尤其在分布式环境中能快速定位故障节点。
| 视图 | 核心作用 | 关键信息 | 典型用途 |
|---|---|---|---|
pg_stat_activity |
监控会话(进程)的实时状态 | 进程ID(pid)、当前查询、状态(state)、等待事件(wait_event)、启动时间等 |
找出“谁在跑什么”、“是否在等待”、“等什么” |
pg_locks |
存储所有锁的请求与持有情况 | 锁类型、锁对象、锁模式、是否已获得(granted)、持有进程(pid)等 |
找出“谁持有锁”、“谁在等锁”、“锁的是什么对象” |
常用查询示例#
1. 查看会话状态分布及最长持续时间#
SELECT state,
count(*) AS session_count,
max(now() - xact_start) AS max_tx_age,
max(now() - state_change) AS max_state_age
FROM pg_stat_activity
WHERE datname = current_database()
GROUP BY state
ORDER BY session_count DESC;
判读:若 idle in transaction 状态的数量较多且 max_tx_age 很大,说明有事务长时间未提交,可能持有锁或导致连接耗尽。此时可进一步查看这些会话的 wait_event,通常为 ClientRead,表示数据库已返回结果,正等待客户端发送下一条命令。
2. 检查锁等待(是否存在阻塞)#
SELECT blocked.pid AS blocked_pid,
blocked.usename AS blocked_user,
blocking.pid AS blocking_pid,
blocking.usename AS blocking_user,
blocked.query AS blocked_query,
blocking.query AS blocking_query,
blocked.wait_event_type,
blocked.wait_event
FROM pg_stat_activity blocked
JOIN LATERAL unnest(pg_blocking_pids(blocked.pid)) AS b(pid) ON true
JOIN pg_stat_activity blocking ON blocking.pid = b.pid;
判读:如果查询无结果,说明当前没有会话正在等待锁。若有结果,每一行都表示一个阻塞关系,可以进一步终止阻塞进程(SELECT pg_terminate_backend(pid);)或分析其语句。
pg_blocking_pids(pid)是 PostgreSQL 提供的一个系统函数,它接受一个进程 ID,返回所有直接阻塞该进程的进程 ID 数组。
3. 获取所有未被授予的锁请求#
SELECT l.pid,
a.usename,
a.state,
a.query,
l.locktype,
l.mode,
l.granted,
l.relation::regclass AS table_name,
l.transactionid,
l.classid, l.objid
FROM pg_locks l
JOIN pg_stat_activity a ON l.pid = a.pid
WHERE NOT l.granted;
该查询直接列出了所有等待中的锁请求,结合锁类型和模式可以判断等待的是什么资源。
4. 定位 idle in transaction 的会话来源#
SELECT client_addr,
application_name,
count(*) FILTER (WHERE state = 'idle in transaction') AS stuck_sessions,
max(now() - xact_start) AS longest_tx
FROM pg_stat_activity
WHERE datname = current_database()
GROUP BY client_addr, application_name
ORDER BY stuck_sessions DESC;
此查询按客户端地址和应用名称分组,显示空闲事务的分布,有助于快速找到问题应用实例(如某个 pod 或服务节点)。
5. 检查咨询锁(Advisory Lock)的持有情况#
某些应用使用咨询锁(pg_advisory_lock / pg_try_advisory_lock)来实现应用级互斥。如果这些锁未被正确释放,也会阻塞其他会话。以下查询可以列出所有咨询锁及其持有者状态:
SELECT l.pid,
l.granted,
((l.classid::bigint << 32) | (l.objid::bigint & 0xFFFFFFFF)) AS lock_key,
a.state,
now() - a.state_change AS held_for,
now() - a.xact_start AS xact_age,
a.wait_event_type,
a.wait_event,
a.application_name,
a.client_addr,
left(coalesce(a.query, ''), 60) AS last_query
FROM pg_locks l
LEFT JOIN pg_stat_activity a ON a.pid = l.pid
WHERE l.locktype = 'advisory'
ORDER BY a.state_change;
判读要点:
held_for(now() - state_change)反映了锁被持有的时长,比连接年龄(conn_age)更准确,因为连接可能被连接池复用。last_query若恒为SELECT pg_try_advisory_lock($1),表明这是专用的持锁连接,用于应用层锁管理。granted = false表示有会话在等待该咨询锁,但使用try_系列函数时通常不会排队,因此这里多数为true。
总结与注意事项#
- 锁等待 通常表现为
active状态且wait_event_type = 'Lock',这时应重点分析pg_locks和阻塞关系。 - 空闲事务 是另一个常见问题,它不会立即表现为查询慢,但会逐渐积累锁和连接资源,最终拖垮数据库。
- 连接池 可能掩盖真实的问题来源,因为一个后端进程可能被不同的应用请求复用。此时需结合
state_change和query的内容来判断当前锁是何时、由哪个逻辑操作产生的。 - 咨询锁 容易遗漏,特别是当应用使用 ORM 或自定义锁管理时,应养成定期检查的习惯。
- 在实际生产环境中,建议建立监控告警,对长时间运行的查询、
idle in transaction会话数量、锁等待事件进行持续跟踪
其它常见文档#
连接池与连接管理问题#
连接池耗尽#
- 现象:应用无法获取数据库连接,请求排队甚至超时,表现为数据库
pg_stat_activity中连接数接近max_connections,但大量连接处于idle或idle in transaction。 - 排查:
SELECT state, count(*) FROM pg_stat_activity GROUP BY state;
SELECT count(*) FROM pg_stat_activity;
SHOW max_connections;
- 解决:优化应用连接释放逻辑,合理设置连接池大小,避免连接泄漏。
连接复用不当#
- 连接池复用的连接可能残留上一个请求的事务状态、临时表、
search_path变更、预备语句等,导致异常行为或性能下降。 - 排查:注意
pg_stat_activity中xact_start非空但state为idle的连接,检查其query是否为上一条命令。
硬件资源瓶颈#
CPU 饱和#
- 现象:查询变慢,系统 CPU 使用率高(用户态或系统态),
pg_stat_activity中大量active会话,wait_event可能为CPU(较少见)或无等待事件。 - 排查:使用
top、vmstat、mpstat查看 CPU 使用;结合pg_stat_statements找出消耗 CPU 最多的 SQL。 - 解决:优化查询(索引、重写),增加 CPU 资源,限制并发。
内存不足与交换#
- 现象:数据库性能急剧下降,操作系统开始使用 swap,
pg_stat_activity中等待事件可能出现IO或DataFileRead增加。 - 排查:
free -h、vmstat 1观察 swap 使用;检查shared_buffers、work_mem、maintenance_work_mem等参数是否过大导致内存压力。 - 解决:调整内存参数,确保 PostgreSQL 共享内存和每个后端的内存需求不超过物理内存,避免过度分配
work_mem。
I/O 瓶颈#
- 现象:磁盘 I/O 利用率高,查询大量扫描数据,等待事件为
DataFileRead、WALWrite、CheckpointWrite等。 - 排查:使用
iostat -x 1查看磁盘利用率、平均等待时间;pg_stat_statements中blk_read_time高的语句。 - 解决:优化索引减少扫描,增加
shared_buffers提高缓存命中,使用更快的存储(SSD、NVMe),调整checkpoint和bgwriter参数。
PostgreSQL 内部争用(非普通锁)#
轻量级锁(LWLock)#
- 现象:等待事件为
LWLock或具体名称如buffer_content、lock_manager、wal_insert等。 - 原因:并发访问共享内存结构(缓冲池、锁表、WAL 缓冲区)产生争用。
- 排查:
SELECT wait_event_type, wait_event, count(*)
FROM pg_stat_activity
WHERE state = 'active'
GROUP BY 1,2 ORDER BY 3 DESC;
- 解决:减少热点(如高频更新同一行、大量并发短事务),调整
max_wal_size、wal_buffers,使用分区、连接池控制并发。
参考文档#
- postgresql-postgresql中的锁
- postgresql-记一次因数据库锁导致的接口超时问题排查