背景:某一个功能测试时发现执操作数据库某张表报错,具体报错信息记不住了。可以查到该数据库中该功能进程的事务信息,该事务操作被其他事务给阻塞了。
以杀掉锁表的进程为例
1) 查询待操作数据库表锁表的进程id
方法1.1)
select oid from pg_class where relname='可能锁表了的表';
select pid from pg_locks where relation='上面查出的oid';
方法1.2)
2) 死锁进程查看pg_stat_activity
方法2.1)
SELECT * FROM pg_stat_activity WHERE datname='数据库名称' and waiting=true;
方法2.2)
3) 杀掉锁表进程,在确认以上pid不影响系统的情况下
select pg_cancel_backend(上面查到的pid);
pg_stat_activity
pg_stat_activity是一个非常有用的视图,可以分析排查当前运行的SQL任务以及一些异常问题。pg_stat_activity 每行展示的是一个“process” 的相关信息,“process”可以理解为一个用户连接。
几个重要的字段:
datname 后端连接到的数据库的名称
procpid 后端的进程id(4.3版本)
pid 后端的进程id(6.0版本)
current_query当前正在执行的查询(4.3版本)
query 如果state为active,为当前正在执行的查询(6.0版本)
state 后端目前的状态包括 active、idle、idle in transaction、idle in transaction (aborted)、fastpath function call、disabled (6.0版本)。active:后端正在执行一个查询。idle:后端正在等待一个新的客户端命令。idle in transaction:后端在一个事务中,但是当前没有正在执行一个查询。idle in transaction (aborted):与idle in transaction相似,在该事务中的一个语句导致了一个错误。fastpath function call:后端正在执行一个fast-path函数。 disabled:如果后端中track_activities被禁用,则报告这个状态。
state_change 上次状态切换的时间点(6.0版本)
waiting True当前SQL在锁等待,否则false
waiting_reason 当前执行等待的原因,可能是等锁或者等待节点间数据的复制
client_addr 连接到后端的客户端的IP地址。如果这个域为空,表示客户端通过服务器机器上的一个 Unix 套接字连接或者这是一个内部进程。
client_port 客户端和后端通信的TCP端口号,如果使用 Unix 套接字则为-1
应用场景
1) 查看当前运行中的耗时较长的SQL语句
4.3 版本
select current_timestamp - query_start as runtime, datname, usename, current_query
from pg_stat_activity
where current_query != '<IDLE>'
order by 1 desc;
6.0 版本
select current_timestamp - query_start as runtime, datname, usename, query
from pg_stat_activity
where state != 'idle'
order by 1 desc;
2) 异常SQL诊断及修复
4.3 版本
SELECT datname,usename,current_query
FROM pg_stat_activity
WHERE waiting;
6.0 版本
SELECT datname,usename,query
FROM pg_stat_activity
WHERE waiting;
只能获取当前block的SQL,但找不到原因。相互block的SQL信息:
SELECT
w.current_query as waiting_query,
w.procpid as w_pid,
w.usename as w_user,
l.current_query as locking_query,
l.procpid as l_pid,
l.usename as l_user,
t.schemaname || '.' || t.relname as tablename
from pg_stat_activity w
join pg_locks l1 on w.procpid = l1.pid and not l1.granted
join pg_locks l2 on l1.relation = l2.relation and l2.granted
join pg_stat_activity l on l2.pid = l.procpid
join pg_stat_user_tables t on l1.relation = t.relid
where w.waiting;
session是query,杀掉阻塞的进程id: SELECT pg_cancel_backend(pid);
session是idle,杀掉阻塞的进程id:SELECT pg_terminate_backend(pid);
pg_locks
pg_locks提供了数据库服务器上打开事务中保持的锁的信息。pg_locks是对每一个活动可锁对象、请求锁模式和相关事务的组合。如果多个事务持有或正在等待一个可锁对象上的锁,同一个可锁对象可能出现很多次。
有多种不同类型的可锁对象:整个关系(如表)、关系的单个页、关系的单个元组、事务ID(包括虚拟和永久ID)和普通数据库对象(由类OID和对象OID标识,和pg_description或pg_depend中的相同方式)。扩展一个关系的权力也被表示为一个独立的可锁对象。"咨询"锁可以具有用户定义的意义。
几个重要的字段:
locktype:可锁对象的类型: relation, extend, page, tuple, transactionid, virtualxid, object, userlock, or advisory
relation:pg_class.oid 锁目标的关系的OID
pid :保持这个锁或者正在等待这个锁的服务器进程的PID
pg_class
pg_class记载表和几乎所有有字段或者是那些类似表的东西。 包括索引、序列、视图、物化视图、 复合类型和一些特殊关系类型。
几个重要字段:
oid: 行标识符(隐藏属性; 必须明确选择)
relname:表、索引、视图等的名字。
relfilenode:这个关系在磁盘上的文件的名字,0表示这是一个"映射的"关系, 它的文件名取决于行级别的状态。
relpersistence : p = permanent table(永久表), u = unlogged table(未加载的表), t = temporary table (临时表)
relkind :r = ordinary table(普通表) , i = index(索引), S = sequence(序列), v = view(视图), m = materialized view (物化视图), c = composite type(复合类型), t = TOAST table(TOAST 表), f = foreign table(外部表)
pg_cancel_backend和pg_terminate_backend
pg_cancel_backend() 取消后台操作,回滚未提交事物
pg_terminate_backend() 中断session,回滚未提交事物
pg_cancel_backend(pid)杀掉某条sql,是温柔的杀,向后台发送sigint信号,关闭当前后台进程,用户只能关闭自己的后台进程,事务回滚。
pg_terminate_backend(pid)杀掉某条sql,是强杀,向后台发送sigterm信号,关闭当前后台进程,需要有超级用户权限,超级用户可以关闭所有后台进程,事务回滚。
使用pg_cancel_backend杀不掉的进程,但是其实pg_terminate_backend有时也无法杀掉某条sql,笔者在生产环境遇到过,这时我们可能会抓一下该连接的堆栈,然后我们可能想尽快杀掉该sql,问题原因后面再分析,这时我们就要从操作系统层面使用kill命令来杀掉连接了。通过上面查到的pid,在操作系统上ps -ef |grep pid查看当前连接的状态,然后kill -9 pid杀掉该连接。
参考