赞
踩
#设置提示符 cd cat >.psqlrc <<EOF \set PROMPT1 '%/(%p)%R%#' \pset border 2 EOF #效果如下,postgres(11288)中11288为会话ID [pgsql@centos7:/home/pgsql]$psql postgresql://postgres:postgres@localhost:5432/postgres Border style is 2. psql (12.2) Type "help" for help. postgres(11288)=#select pg_backend_pid(); +----------------+ | pg_backend_pid | +----------------+ | 11288 | +----------------+ (1 row)
PostgreSQL提供了两个视图:
名称 | 类型 | 引用 | 描述 |
---|---|---|---|
locktype | text | 可锁对象的类型: relation, extend, page, tuple, transactionid, virtualxid, object, userlock或 advisory | |
database | oid | pg_database.oid | 锁定对象的数据库的OID,如果对象是一个共享对象,不属于任何数据库,此值为 “0”,如果对象是"transaction ID",此值为空 |
relation | oid | pg_class.oid | 如果对象不是表或只是表的一部分,则此值为"NULL",否则此值是表的OID |
page | integer | 表中的页号,如果对象不是表行(tuple)或表页(relation page),则此值为"NULL" | |
tuple | smallint | 页内的行号(tuple),如果对象不是表行(tuple),则此值为空 | |
virtualxid | text | 是一个虚拟事务ID,如果对象不是虚拟事务,则此值为"NULL" | |
transactionid | xid | 事务ID,如果对象不是事务,则此值为"NULL" | |
classid | oid | pg_class.oid | 包含该对象的系统目录的OID,如果对象不是通常的数据库对象,则此值为空 |
objid | oid | 任意OID列 | 对象在系统目录中的OID,如果对象不是通常的数据库对象,则此值为空。对于advisory locks,此字段用于区别两类key空间("1"表示int8的key,"2"表示 two int4的key) |
objsubid | smallint | 如果对象是表列,此列的值为列号,这时"classid" 和"objid"指向表,在其他的数据库类型中,此值为"0"。如果不是数据库对象,则此值为"NULL" | |
virtualtransaction | text | 持有或等待这把锁的虚拟事务的ID | |
pid | integer | 持有或者等待这把锁的服务进程的PID。如果此锁是被一个两阶段提交的事务持有,则此值为"NULL" | |
mode | text | 锁的模式名称,如"ACCESS SHARE" “SHARE” "EXCLUSIVE"等锁模式 | |
granted | boolean | 如果锁已被持有,此值为true,如果等待获得此锁,则此值为false | |
fastpath | boolean | 如果锁通过快速路径获得则为真,通过主锁表获得则为假 |
描述事务ID的字段说明:
transactionid
代表事务ID,简写为"xid"
每产生一个事务ID,都会在pg_clog下的commit log文件中占用2bit。
virtualxid
代表虚拟事务ID,简写为"vxid"。
最早在PostgreSQL中是没有虚拟事务ID的,但后来发现,有一些事务根本没有产生任何实质的变更,如一个只读事务或一个空事务,若在这种情况下也分配一个事务ID会造成资源浪费,于是提出了虚拟事务ID的概念。
对于这类只读事务,只分配一个虚拟事务ID,而不实际分配一个真实的事务ID,这样就不需要在commit log中占用2bit的空间了
virtualtransaction
pg_locks这张视图的字段分为以下两个部分:
virtualtransaction字段之前的字段(不包括virtualtransaction字段),我们称其为"第一部分"
第一部分字段用于描述锁定对象(Locked Object)的信息
virtualtransaction字段之后的字段(包括virtualtransaction字段)我们称其为"第二部分"。
第二部分字段描述的是持有锁或等待锁session的信息
“virtualxid"在第一部分字段中,表示锁对象是一个"virtualxid”,而"virtualtransaction"表示持有锁或等待锁session的虚拟事务ID。
列 | 类型 | 描述 |
---|---|---|
datid | oid | 这个后端连接到的数据库的OID |
datname | name | 这个后端连接到的数据库的名称 |
pid | integer | 这个后端的进程 ID |
usesysid | oid | 登录到这个后端的用户的 OID |
usename | name | 登录到这个后端的用户的名称 |
application_name | text | 连接到这个后端的应用的名称 |
client_addr | inet | 连接到这个后端的客户端的 IP 地址。如果这个域为空,它表示客户端通过服务器机器上的一个 Unix 套接字连接或者这是一个内部进程(如自动清理)。 |
client_hostname | text | [已连接的客户端的主机名,由client_addr的反向 DNS 查找报告。这个域将只对 IP 连接非空,并且只有log_hostname被启用时才会非空。 |
client_port | integer | 客户端用以和这个后端通信的 TCP 端口号,如果使用 Unix 套接字则为-1 |
backend_start | timestamp with time zone | 这个进程被启动的时间。对客户端后端来说就是客户端连接到服务器的时间。 |
xact_start | timestamp with time zone | 这个进程的当前事务被启动的时间,如果没有活动事务则为空。如果当前查询是它的第一个事务,这一列等于query_start。 |
query_start | timestamp with time zone | 当前活动查询被开始的时间,如果state不是active,这个域为上一个查询被开始的时间 |
state_change | timestamp with time zone | state上一次被改变的时间 |
wait_event_type | text | 后端正在等待的事件类型,如果不存在则为 NULL。可能的值有: **LWLock:**后端正在等待一个轻量级锁。每一个这样的锁保护着共享内存中的一个特殊数据结构。wait_event将含有一个标识该轻量级锁目的的名称(一些锁具有特定的名称,其他是一组具有类似目的的锁中的一部分)。 **Lock:**后端正在等待一个重量级锁。重量级锁,也称为锁管理器锁或者简单锁,主要保护 SQL 可见的对象,例如表。不过,它们也被用于确保特定内部操作的互斥,例如关系扩展。wait_event将标识等待的锁的类型。 **BufferPin:**服务器进程正在等待访问一个数据缓冲区,而此时没有其他进程正在检查该缓冲区。如果另一个进程持有一个最终从要访问的缓冲区中读取数据的打开的游标,缓冲区 pin 等待可能会被拖延。 **Activity:**服务器进程处于闲置状态。这被用于在其主处理循环中等待活动的系统进程。wait_event将标识特定的等待点。 **Extension:**服务器进程正在一个扩展模块中等待活动。这一个分类被用于要跟踪自定义等待点的模块。 **Client:**服务器进程正在一个套接字上等待来自用户应用的某种活动,并且该服务器预期某种与其内部处理无关的事情发生。wait_event将标识特定的等待点。 **IPC:**服务器进程正在等待来自服务器中另一个进程的某种活动。wait_event将标识特定的等待点。 Timeout:服务器进程正在等待一次超时发生。wait_event将标识特定的等待点。 **IO:**服务器进程正在等待一次IO完成。wait_event将标识特定的等待点。 |
wait_event | text | 如果后端当前正在等待,则是等待事件的名称,否则为 NULL。 |
state | text | 这个后端的当前总体状态。可能的值是: active:后端正在执行一个查询。 **idle:**后端正在等待一个新的客户端命令。 **idle in transaction:**后端在一个事务中,但是当前没有正在执行一个查询。 **idle in transaction (aborted):**这个状态与idle in transaction相似,不过在该事务中的一个语句导致了一个错误。 **fastpath function call:**后端正在执行一个 fast-path 函数。 **disabled:**如果在这个后端中track_activities被禁用,则报告这个状态。 |
backend_xid | xid | 这个后端的顶层事务标识符(如果存在)。 |
backend_xmin | xid | 当前后端的xmin范围。 |
query | text | 这个后端最近查询的文本。如果state为active,这个域显示当前正在执行的查询。在所有其他状态下,它显示上一个被执行的查询。默认情况下,查询文本会被截断至1024个字符,这个值可以通过参数track_activity_query_size更改 |
backend_type | text | 当前后端的类型。可能的类型是 autovacuum launcher, autovacuum worker, logical replication launcher, logical replication worker, parallel worker, background writer, client backend, checkpointer, startup, walreceiver, walsender 以及 walwriter。 除此以外,由扩展注册的后台Worker可能有额外的类型。 |
#创建测试表
drop table if exists testtab01;
create table testtab01 (id int primary key,note varchar(100));
insert into testtab01 select generate_series(1,100),md5(random()::text);
#会话 14735 锁定表 testtab01 postgres(14735)=#begin; BEGIN postgres(14735)=#lock table testtab01; LOCK TABLE postgres(14735)=# #其他会话查询 postgres(14736)=#select locktype, postgres-# relation::regclass as rel, postgres-# virtualxid as vxid, postgres-# transactionid as xid , postgres-# virtualtransaction as vxid2, postgres-# pid, postgres-# mode, postgres-# granted postgres-# from pg_locks postgres-# where pid = 14735; +---------------+-----------+------+-----+-------+-------+---------------------+---------+ | locktype | rel | vxid | xid | vxid2 | pid | mode | granted | +---------------+-----------+------+-----+-------+-------+---------------------+---------+ | virtualxid | | 3/2 | | 3/2 | 14735 | ExclusiveLock | t | | relation | testtab01 | | | 3/2 | 14735 | AccessExclusiveLock | t | | transactionid | | | 510 | 3/2 | 14735 | ExclusiveLock | t | +---------------+-----------+------+-----+-------+-------+---------------------+---------+ (3 rows) postgres(14736)=#
第一行显示的是事务在自己的“virtualxid”上加的ExclusiveLock锁,这是必定会加上的。
如上第二行可以看出实际在表上加的锁“AccessExclusiveLock”
#会话 14737 也锁定表 testtab01
postgres(14737)=#begin;
BEGIN
postgres(14737)=#lock table testtab01;
此时的锁情况
postgres(14736)=#select locktype, postgres-# relation::regclass as rel, postgres-# virtualxid as vxid, postgres-# transactionid as xid , postgres-# virtualtransaction as vxid2, postgres-# pid, postgres-# mode, postgres-# granted postgres-# from pg_locks postgres-# where pid in (14735,14737); +---------------+-----------+------+-----+-------+-------+---------------------+---------+ | locktype | rel | vxid | xid | vxid2 | pid | mode | granted | +---------------+-----------+------+-----+-------+-------+---------------------+---------+ | virtualxid | | 5/2 | | 5/2 | 14737 | ExclusiveLock | t | | virtualxid | | 3/2 | | 3/2 | 14735 | ExclusiveLock | t | | transactionid | | | 511 | 5/2 | 14737 | ExclusiveLock | t | | relation | testtab01 | | | 3/2 | 14735 | AccessExclusiveLock | t | | transactionid | | | 510 | 3/2 | 14735 | ExclusiveLock | t | | relation | testtab01 | | | 5/2 | 14737 | AccessExclusiveLock | f | +---------------+-----------+------+-----+-------+-------+---------------------+---------+ (6 rows) postgres(14736)=#
如上可以看出进程14735和14737都对表testtab01加 了锁,14735的granted字段为t,14737的granted字段为f,表示14737就成没有获得这把锁,从而被阻塞
从上面的示例中可以看出,想查看被锁阻塞的进程只要查询视图“pg_locks”中“granted”字段值为“False”的进程就可以了
# 14735 加行锁 postgres(14735)=#select * from testtab01 where id=1; +----+----------------------------------+ | id | note | +----+----------------------------------+ | 1 | 22c12446639eea7dbe86f5b85e7bbb98 | +----+----------------------------------+ (1 row) postgres(14735)=#begin; BEGIN postgres(14735)=#select * from testtab01 where id=1 for update; +----+----------------------------------+ | id | note | +----+----------------------------------+ | 1 | 22c12446639eea7dbe86f5b85e7bbb98 | +----+----------------------------------+ (1 row) postgres(14735)=#
其他窗口查询
postgres(14736)=# postgres(14736)=#select locktype, postgres-# relation::regclass as rel, postgres-# virtualxid as vxid, postgres-# transactionid as xid , postgres-# virtualtransaction as vxid2, postgres-# pid, postgres-# mode, postgres-# granted postgres-# from pg_locks postgres-# where pid in (14735); +---------------+----------------+------+-----+-------+-------+---------------+---------+ | locktype | rel | vxid | xid | vxid2 | pid | mode | granted | +---------------+----------------+------+-----+-------+-------+---------------+---------+ | relation | testtab01_pkey | | | 3/4 | 14735 | RowShareLock | t | | relation | testtab01 | | | 3/4 | 14735 | RowShareLock | t | | virtualxid | | 3/4 | | 3/4 | 14735 | ExclusiveLock | t | | transactionid | | | 512 | 3/4 | 14735 | ExclusiveLock | t | +---------------+----------------+------+-----+-------+-------+---------------+---------+ (4 rows)
加行锁的过程,是先在表上加一个表级意向锁,从上可以看出,行锁不仅会在表上加意向锁,也会在相应的主键上加意向锁。其中“testtab01_pkey”就是表“testtab01”的主键。
为什么没有发现行锁?
实际上pg_locks并不能显示出每个行锁的信息,原因也很简单,行锁信息并不会记录到共享内存中。这也很好理解,如果每个行锁在内存中都有一条记录的话,在对表做全表更新时,表有多少行就需要在内存中记录多少条行锁信息,那么内存会吃不消。所以PostgreSQL被设计成不在内存中记录行锁信息。
如果在pg_locks中没有行锁信息,如何知道一个进程被另一个进程的行锁阻塞了呢?
pg_locks中提供了另一种信息来表示这种阻塞关系
# 14737 也加行锁
postgres(14737)=#begin;
BEGIN
postgres(14737)=#select * from testtab01 where id=1 for update;
# 该会话被阻塞
查询当前的锁情况
postgres(14736)=#select locktype, postgres-# relation::regclass as rel, postgres-# virtualxid as vxid, postgres-# transactionid as xid , postgres-# virtualtransaction as vxid2, postgres-# pid, postgres-# mode, postgres-# granted postgres-# from pg_locks postgres-# where pid in (14735,14737); +---------------+----------------+------+-----+-------+-------+---------------------+---------+ | locktype | rel | vxid | xid | vxid2 | pid | mode | granted | +---------------+----------------+------+-----+-------+-------+---------------------+---------+ | relation | testtab01_pkey | | | 5/3 | 14737 | RowShareLock | t | | relation | testtab01 | | | 5/3 | 14737 | RowShareLock | t | | virtualxid | | 5/3 | | 5/3 | 14737 | ExclusiveLock | t | | relation | testtab01_pkey | | | 3/4 | 14735 | RowShareLock | t | | relation | testtab01 | | | 3/4 | 14735 | RowShareLock | t | | virtualxid | | 3/4 | | 3/4 | 14735 | ExclusiveLock | t | | transactionid | | | 512 | 3/4 | 14735 | ExclusiveLock | t | | transactionid | | | 512 | 5/3 | 14737 | ShareLock | f | | tuple | testtab01 | | | 5/3 | 14737 | AccessExclusiveLock | t | +---------------+----------------+------+-----+-------+-------+---------------------+---------+ (9 rows)
前面说过,如果想查看哪个进程被阻塞住了,只需要查看“granted”字段值为“False”的PID,如上就是倒数第2行,从中可以了解到,14737进程申请一个类型为“transactionid”的锁时被阻塞了,这个transactionid锁对应的xid为“512”。从倒数第3行可以看出,xid为“512”的锁被进程“14735”持有了。
从上面的分析中我们知道,实际上,行锁中的阻塞信息是通过transactionid类型的锁体现出来的。从原理上来说,行锁是会在数据行上加上自己的xid的,另一个进程读到这一行时,如果发现这一行上有行锁,会把行上另一个事务的xid读出来,然后申请在这个xid上加SHARE锁。而持有行锁的进程已经在此xid上加了EXCLUSIVE锁,所以后面要更新的行的进程会被阻塞。
若要查询因行锁被阻塞的进程信息,只需要查询视图“pg_locks”中类型为“transactionid”的锁信息就可以了。
那么进程是在哪一行上被阻塞的呢?
可以通过查看pg_locks的“page”和“tuple”字段来了解。
# 14735 更新表行
postgres(14735)=#begin;
BEGIN
postgres(14735)=#update testtab01 set note='aaaa' where id=1;
UPDATE 1
postgres(14735)=#
# 14737 同时更新同一行
postgres(14737)=#begin;
BEGIN
postgres(14737)=#update testtab01 set note='aaaa' where id=1;
#14737 会被阻塞
查询锁情况
postgres(14736)=#select locktype, postgres-# relation::regclass as rel, postgres-# page||','||tuple as ctid, postgres-# virtualxid as vxid, postgres-# transactionid as xid , postgres-# virtualtransaction as vxid2, postgres-# pid, postgres-# mode, postgres-# granted postgres-# from pg_locks postgres-# where pid in (14735,14737); +---------------+----------------+-------+------+-----+-------+-------+------------------+---------+ | locktype | rel | ctid | vxid | xid | vxid2 | pid | mode | granted | +---------------+----------------+-------+------+-----+-------+-------+------------------+---------+ | relation | testtab01_pkey | | | | 5/5 | 14737 | RowExclusiveLock | t | | relation | testtab01 | | | | 5/5 | 14737 | RowExclusiveLock | t | | virtualxid | | | 5/5 | | 5/5 | 14737 | ExclusiveLock | t | | relation | testtab01_pkey | | | | 3/7 | 14735 | RowExclusiveLock | t | | relation | testtab01 | | | | 3/7 | 14735 | RowExclusiveLock | t | | virtualxid | | | 3/7 | | 3/7 | 14735 | ExclusiveLock | t | | transactionid | | | | 517 | 5/5 | 14737 | ShareLock | f | | tuple | testtab01 | 0,103 | | | 5/5 | 14737 | ExclusiveLock | t | | transactionid | | | | 517 | 3/7 | 14735 | ExclusiveLock | t | | transactionid | | | | 518 | 5/5 | 14737 | ExclusiveLock | t | +---------------+----------------+-------+------+-----+-------+-------+------------------+---------+ (10 rows)
此时,可以看到事务被阻塞在表“testtab01”的ctid为“(0,103)”的行上,查询该行信息的SQL命令如下:
postgres(14736)=#SELECT * FROM testtab01 WHERE ctid='(0,103)';
+----+------+
| id | note |
+----+------+
| 1 | aaaa |
+----+------+
(1 row)
从上面的运行结果中我们知道,事务阻塞在表“testtab01”中的“id”为“1”的行上。
锁的等级和对应操作在源码中有定义
/* * These are the valid values of type LOCKMODE for all the standard lock * methods (both DEFAULT and USER). */ /* NoLock is not a lock mode, but a flag value meaning "don't get a lock" */ #define NoLock 0 #define AccessShareLock 1 /* SELECT */ #define RowShareLock 2 /* SELECT FOR UPDATE/FOR SHARE */ #define RowExclusiveLock 3 /* INSERT, UPDATE, DELETE */ #define ShareUpdateExclusiveLock 4 /* VACUUM (non-FULL),ANALYZE, CREATE INDEX CONCURRENTLY */ #define ShareLock 5 /* CREATE INDEX (WITHOUT CONCURRENTLY) */ #define ShareRowExclusiveLock 6 /* like EXCLUSIVE MODE, but allows ROW SHARE */ #define ExclusiveLock 7 /* blocks ROW SHARE/SELECT...FOR UPDATE */ #define AccessExclusiveLock 8 /* ALTER TABLE, DROP TABLE, VACUUM FULL, and unqualified LOCK TABLE */
pg锁兼容性模式
锁兼容性矩阵 | access share | row share | row exclusive | share update exclusive | share | share row exclusive | exclusive | access exclusive |
---|---|---|---|---|---|---|---|---|
access share | √ | √ | √ | √ | √ | √ | √ | X |
row share | √ | √ | √ | √ | √ | √ | X | X |
row exclusive | √ | √ | √ | √ | X | X | X | X |
share update exclusive | √ | √ | √ | X | X | X | X | X |
share | √ | √ | X | X | √ | X | X | X |
share row exclusive | √ | √ | X | X | X | X | X | X |
exclusive | √ | X | X | X | X | X | X | X |
access exclusive | X | X | X | X | X | X | X | X |
说明:
-- 查看阻塞的会话(granted=t 在等待申请锁的会话) SELECT database, locktype, relation, relation :: regclass, mode, pid FROM pg_locks where granted = 'f'; -- 根据pid和relation找到阻塞源(granted=t) SELECT database, locktype, relation :: regclass, mode, pid FROM pg_locks where granted = 't'; -- 根据被阻塞/阻塞源的pid查正在执行的语句 select pid, usename, substring(query from 0 for50), now() - query_start as time, wait_event, state from pg_stat_activity where pid = xxx; -- 慢查询会话 SELECT pgsa.pid, pgsa.client_port, pgsa.datname AS datname, pgsa.usename AS usename, pgsa.client_addr client_addr, pgsa.application_name AS application_name, pgsa.state AS state, pgsa.wait_event, pgsa.wait_event_type, pgsa.backend_start AS backend_start, pgsa.xact_start AS xact_start, state_change, pgsa.query_start AS query_start, TRUNC(EXTRACT( epoch FROM (NOW() - pgsa.xact_start) )) AS xact_stay, trunc(EXTRACT( epoch FROM (NOW() - pgsa.query_start) )) AS query_stay, REPLACE(pgsa.QUERY, chr(10), ' ') AS QUERY, pgsa.backend_type, 'select pg_terminate_backend('||pgsa.pid||');' kill1, 'select pg_cancel_backend('||pgsa.pid||');' kill2 FROM pg_stat_activity AS pgsa WHERE pgsa.state not in ( 'idle' ,'idle in transaction (aborted)' ) ORDER BY query_stay DESC,xact_stay DESC LIMIT 100; -- 锁 -- granted=t是阻塞别人的,f是被阻塞的 SELECT database dbid, pd.datname, locktype, relation, relation :: regclass relation_name, mode, pid, granted FROM pg_locks pl left join pg_database pd on pl.database = pd.oid where pl.database > 0; -- 锁查询 SELECT pg_locks.pid as pid, -- 进程ID transactionid as transaction_id, -- 事务ID pd.datname db_name, nspname as schemaname, -- schema名 relname as object_name, -- 对象名 locktype as lock_type, -- 锁类型 mode lock_mode, -- 锁模式 CASE WHEN granted = 'f' THEN 'get_lock' WHEN granted = 't' THEN 'wait_lock' END lock_satus, -- 锁状态:持有锁|等待锁 least(query_start, xact_start) AS query_start, -- query请求开始时间 substr(query, 1, 25) AS query_text -- 当前SQL语句 FROM pg_locks left join pg_database pd on (pg_locks.database=pd.oid) LEFT OUTER JOIN pg_class ON (pg_locks.relation = pg_class.oid) LEFT OUTER JOIN pg_namespace ON (pg_namespace.oid = pg_class.relnamespace), pg_stat_activity WHERE NOT pg_locks.pid = pg_backend_pid() AND pg_locks.pid = pg_stat_activity.pid ORDER BY query_start; -- 杀会话 select 'select pg_terminate_backend(' || a.pid || ');' kill1, 'select pg_cancel_backend(' || a.pid || ');' kill2 from pg_stat_activity a where pid <> pg_backend_pid() and state in ('idle'); SELECT pg_cancel_backend(pid); -- Cancel a backend's current query. SELECT pg_terminate_backend(pid); -- Terminate a backend.
create view vw_lock1 as with t_wait as ( select a.mode, a.locktype, a.database, a.relation, a.page, a.tuple, a.classid, a.granted, a.objid, a.objsubid, a.pid, a.virtualtransaction, a.virtualxid, a.transactionid, a.fastpath, b.state, b.query, b.xact_start, b.query_start, b.usename, b.datname, b.client_addr, b.client_port, b.application_name from pg_locks a, pg_stat_activity b where a.pid = b.pid and not a.granted ), t_run as ( select a.mode, a.locktype, a.database, a.relation, a.page, a.tuple, a.classid, a.granted, a.objid, a.objsubid, a.pid, a.virtualtransaction, a.virtualxid, a.transactionid, a.fastpath, b.state, b.query, b.xact_start, b.query_start, b.usename, b.datname, b.client_addr, b.client_port, b.application_name from pg_locks a,pg_stat_activity b where a.pid = b.pid and a.granted ), t_overlap as ( select r.* from t_wait w join t_run r on ( r.locktype is not distinct from w.locktype and r.database is not distinct from w.database and r.relation is not distinct from w.relation and r.page is not distinct from w.page and r.tuple is not distinct from w.tuple and r.virtualxid is not distinct from w.virtualxid and r.transactionid is not distinct from w.transactionid and r.classid is not distinct from w.classid and r.objid is not distinct from w.objid and r.objsubid is not distinct from w.objsubid and r.pid <> w.pid ) ), t_unionall as ( select r.* from t_overlap r union all select w.* from t_wait w ) select locktype, datname, relation :: regclass, page, tuple, virtualxid, transactionid :: text, classid :: regclass, objid, objsubid, string_agg( 'Pid: ' || case when pid is null then 'NULL' else pid :: text end || chr(10)|| 'Lock_Granted: ' || case when granted is null then 'NULL' else granted :: text end || ' , Mode: ' || case when mode is null then 'NULL' else mode :: text end || ' , FastPath: ' || case when fastpath is null then 'NULL' else fastpath :: text end || ' , VirtualTransaction: ' || case when virtualtransaction is null then 'NULL' else virtualtransaction :: text end || ' , Session_State: ' || case when state is null then 'NULL' else state :: text end || chr(10)|| 'Username: ' || case when usename is null then 'NULL' else usename :: text end || ' , Database: ' || case when datname is null then 'NULL' else datname :: text end || ' , Client_Addr: ' || case when client_addr is null then 'NULL' else client_addr :: text end || ' , Client_Port: ' || case when client_port is null then 'NULL' else client_port :: text end || ' , Application_Name: ' || case when application_name is null then 'NULL' else application_name :: text end || chr(10)|| 'Xact_Start: ' || case when xact_start is null then 'NULL' else xact_start :: text end || ' , Query_Start: ' || case when query_start is null then 'NULL' else query_start :: text end || ' , Xact_Elapse: ' || case when (now() - xact_start) is null then 'NULL' else (now() - xact_start):: text end || ' , Query_Elapse: ' || case when (now() - query_start) is null then 'NULL' else (now() - query_start):: text end || chr(10)|| 'SQL (Current SQL in Transaction): ' || chr(10)|| case when query is null then 'NULL' else query :: text end, chr(10)|| '--------' || chr(10) order by ( case mode when 'INVALID' then 0 when 'AccessShareLock' then 1 when 'RowShareLock' then 2 when 'RowExclusiveLock' then 3 when 'ShareUpdateExclusiveLock' then 4 when 'ShareLock' then 5 when 'ShareRowExclusiveLock' then 6 when 'ExclusiveLock' then 7 when 'AccessExclusiveLock' then 8 else 0 end ) desc, (case when granted then 0 else 1 end) ) as lock_conflict from t_unionall group by locktype, datname, relation, page, tuple, virtualxid, transactionid :: text, classid, objid, objsubid;
create view vw_lock2 as with recursive tmp_lock as ( select distinct --w.mode w_mode,w.page w_page, --w.tuple w_tuple,w.xact_start w_xact_start,w.query_start w_query_start, --now()-w.query_start w_locktime,w.query w_query w.pid as id,--w_pid, r.pid as parentid--r_pid, --r.locktype,r.mode r_mode,r.usename r_user,r.datname r_db, --r.relation::regclass, --r.page r_page,r.tuple r_tuple,r.xact_start r_xact_start, --r.query_start r_query_start, --now()-r.query_start r_locktime,r.query r_query, from ( select a.mode,a.locktype,a.database, a.relation,a.page,a.tuple,a.classid, a.objid,a.objsubid,a.pid,a.virtualtransaction,a.virtualxid, a.transactionid, b.query as query, b.xact_start,b.query_start,b.usename,b.datname from pg_locks a, pg_stat_activity b where a.pid=b.pid and not a.granted ) w, ( select a.mode,a.locktype,a.database, a.relation,a.page,a.tuple,a.classid, a.objid,a.objsubid,a.pid,a.virtualtransaction,a.virtualxid, a.transactionid, b.query as query, b.xact_start,b.query_start,b.usename,b.datname from pg_locks a, pg_stat_activity b -- select pg_typeof(pid) from pg_stat_activity where a.pid=b.pid and a.granted ) r where 1=1 and r.locktype is not distinct from w.locktype and r.database is not distinct from w.database and r.relation is not distinct from w.relation and r.page is not distinct from w.page and r.tuple is not distinct from w.tuple and r.classid is not distinct from w.classid and r.objid is not distinct from w.objid and r.objsubid is not distinct from w.objsubid and r.transactionid is not distinct from w.transactionid and r.pid <> w.pid ),tmp0 as ( select * from tmp_lock tl union all -- 查找root,同一时刻可能有多个root select t1.parentid,0::int4 from tmp_lock t1 where 1=1 and t1.parentid not in (select id from tmp_lock) ),tmp3 (pathid,depth,id,parentid) as ( -- 对过滤出的机构向下递归,构成tree SELECT array[id]::text[] as pathid,1 as depth,id,parentid FROM tmp0 where 1=1 and parentid=0 union SELECT t0.pathid||array[t1.id]::text[] as pathid,t0.depth+1 as depth,t1.id,t1.parentid FROM tmp0 t1, tmp3 t0 where 1=1 and t1.parentid=t0.id ) select distinct '/'||array_to_string(a0.pathid,'/') as pathid, a0.depth, a0.id,a0.parentid,lpad(a0.id::text, 2*a0.depth-1+length(a0.id::text),' ') as tree_id, --'select pg_cancel_backend('||a0.id|| ');' as cancel_pid, --'select pg_terminate_backend('||a0.id|| ');' as term_pid, case when a0.depth =1 then 'select pg_terminate_backend('|| a0.id || ');' else null end as term_pid, case when a0.depth =1 then 'select cancel_backend('|| a0.id || ');' else null end as cancel_pid ,a2.datname,a2.client_addr,a2.wait_event_type,a2.wait_event,a2.state --,a2.backend_start,a2.xact_start,a2.query_start from tmp3 a0 left outer join (select distinct '/'||id||'/' as prefix_id,id from tmp0 where 1=1 ) a1 on position( a1.prefix_id in '/'||array_to_string(a0.pathid,'/')||'/' ) >0 left outer join pg_stat_activity a2 -- select * from pg_stat_activity on a0.id = a2.pid order by '/'||array_to_string(a0.pathid,'/'),a0.depth;
创建测试数据
#创建测试表
drop table if exists testtab01;
create table testtab01 (id int primary key,note varchar(100));
insert into testtab01 select generate_series(1,100),md5(random()::text);
会话 14735 插入数据
postgres(14735)=#begin;
BEGIN
postgres(14735)=#insert into testtab01 values(101,'fsadfasdf');
INSERT 0 1
postgres(14735)=#
查看锁情况
postgres(14736)=#SELECT database, locktype,relation::regclass, mode, pid FROM pg_locks where granted='t';
+----------+---------------+-----------+------------------+-------+
| database | locktype | relation | mode | pid |
+----------+---------------+-----------+------------------+-------+
| 13593 | relation | pg_locks | AccessShareLock | 14736 |
| | virtualxid | | ExclusiveLock | 14736 |
| 13593 | relation | testtab01 | RowExclusiveLock | 14735 |
| | virtualxid | | ExclusiveLock | 14735 |
| | transactionid | | ExclusiveLock | 14735 |
+----------+---------------+-----------+------------------+-------+
(5 rows)
#如上第三行信息,可以看出testtab01表上持有一个RowExclusiveLock级别的锁,insert未提交产生的
会话 14737 此时想要删除testtab01表
postgres(14737)=#drop table testtab01;
#当前会话会等待
查询此时锁情况
postgres(14736)=#SELECT database,locktype,relation,relation::regclass,mode,pid,granted FROM pg_locks where pid in (14735,14737);
+----------+---------------+----------+-----------+---------------------+-------+---------+
| database | locktype | relation | relation | mode | pid | granted |
+----------+---------------+----------+-----------+---------------------+-------+---------+
| | virtualxid | | | ExclusiveLock | 14737 | t |
| | virtualxid | | | ExclusiveLock | 14735 | t |
| | transactionid | | | ExclusiveLock | 14735 | t |
| 13593 | relation | 16400 | testtab01 | AccessExclusiveLock | 14737 | f |
| 13593 | relation | 16400 | testtab01 | RowExclusiveLock | 14735 | t |
| | transactionid | | | ExclusiveLock | 14737 | t |
+----------+---------------+----------+-----------+---------------------+-------+---------+
(6 rows)
#如上倒数第2行,14735持有锁(granted=t)锁模式为RowExclusiveLock,与如上倒数第3行14737申请的AccessExclusiveLock冲突,所以会话14737被阻塞
vw_lock1视图查询的结果
#lock_conflict信息
Pid: 14737
Lock_Granted: false , Mode: AccessExclusiveLock , FastPath: false , VirtualTransaction: 5/6 , Session_State: active
Username: postgres , Database: postgres , Client_Addr: ::1/128 , Client_Port: 54771 , Application_Name: psql
Xact_Start: 2023-06-29 20:26:21.563007+08 , Query_Start: 2023-06-29 20:26:21.563007+08 , Xact_Elapse: 00:15:17.081875 , Query_Elapse: 00:15:17.081875
SQL (Current SQL in Transaction):
drop table testtab01;
--------
Pid: 14735
Lock_Granted: true , Mode: RowExclusiveLock , FastPath: false , VirtualTransaction: 3/13 , Session_State: idle in transaction
Username: postgres , Database: postgres , Client_Addr: ::1/128 , Client_Port: 54769 , Application_Name: psql
Xact_Start: 2023-06-29 20:22:39.503155+08 , Query_Start: 2023-06-29 20:23:07.535802+08 , Xact_Elapse: 00:18:59.141727 , Query_Elapse: 00:18:31.10908
SQL (Current SQL in Transaction):
insert into testtab01 values(101,'fsadfasdf');
vw_lock2视图查询的结果
#第一行term_pid信息
select pg_terminate_backend(14735);
##第一行cancel_pid信息
select cancel_backend(14735);
#/14735/14737表示 14735会话阻塞了14737会话,可以手动执行term_pid和cancel_pid的命令终止会话
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。