要查看pg中的锁,需要查看pg_locks这个视图
pg_locks 字段
名称 |
类型 |
引用 |
描述 |
locktype |
text |
|
可锁对象的类型: relation, extend, page, tuple, transactionid, virtualxid, object, userlock, or advisory |
database |
oid |
g_database.oid |
锁目标存在的数据库的OID,如果目标是一个共享对象则为0,如果目标是一个事务ID则为空 |
relatio |
oid |
g_class.oid |
作为锁目标的关系的OID,如果目标不是一个关系或者只是关系的一部分则此列为空 |
age |
integer |
|
作为锁目标的页在关系中的页号,如果目标不是一个关系页或元组则此列为空 |
tuple |
mallint |
|
作为锁目标的元组在页中的元组号,如果目标不是一个元组则此列为空 |
virtualxid |
text |
|
作为锁目标的事务虚拟ID,如果目标不是一个虚拟事务ID则此列为空 |
transactionid |
xid |
|
作为锁目标的事务ID,如果目标不是一个事务ID则此列为空ID |
classid |
oid |
g_class.oid |
包含锁目标的系统目录的OID,如果目标不是一个普通数据库对象则此列为空 |
objid |
oid |
任意OID列 |
锁目标在它的系统目录中的OID,如果目标不是一个普通数据库对象则为空 |
objsubid |
mallint |
|
锁的目标列号(classid和objid指表本身),如果目标是某种其他普通数据库对象则此列为0,如果目标不是一个普通数据库对象则此列为空 |
virtualtransaction |
text |
|
保持这个锁或者正在等待这个锁的事务的虚拟ID |
id |
integer |
|
保持这个锁或者正在等待这个锁的服务器进程的PID,如果此锁被一个预备事务所持有则此列为空 |
mode |
text |
|
此进程已持有或者希望持有的锁模式(参见Section 13.3.1和Section 13.2.3) |
granted |
oolea |
|
如果锁已授予则为真,如果锁被等待则为假 |
fastpath |
oolea |
|
如果锁通过快速路径获得则为真,通过主锁表获得则为假 |
virtualxid:虚拟事务id,每次生成一个事务,会在pg_clog下的commit log 文件中占用2bit空间,因为有些事务中没有实际的操作数据的语句,所以这种分配事务id有些浪费空间了,于是提出了虚拟事务的概念,主要是为了节省空间。在mysql中针对只读事务是有优化的,只读事务是没有分配事务id的。
下面开2个session看下锁的情况
Session A:
select pg_backend_pid();
20353
创建表并lock
create table t(id integer);
insert into t values(1);
begin;
lock table t;
Session B:
select * from pg_locks where pid=20353;
或者
Select locktype,relation::regclass as rel,virtualxid as vxid,transactionid as xid,virtualtransaction as vxid2,pid,mode,granted
From pg_locks
Where pid=20353;
看到xid是空的,没有实际事务,granted是t,表示持有锁.
session B
也试图lock表看下情况
testdb=# select pg_backend_pid();
pg_backend_pid
----------------
20171
(1 row)
begin;
lock table t;
一直提示查询运行中,被阻塞了!
Session A
查看下锁的信息
select * from pg_locks where pid=20171;
或者
Select locktype,relation::regclass as rel,virtualxid as vxid,transactionid as xid,virtualtransaction as vxid2,pid,mode,granted
From pg_locks
Where pid=20171;
#这个sql 语句中字段relation::regclass,作用是可以直接获取到被锁定对象的名称!
看到20171,也就是session b的granted是f,也就是在发起请求获得锁,同时对于session B也有virtualxid的exclusiveLock锁,就是对于事务id加了锁。
Session A提交后,session B就获得了锁。
session A中对行操作,看下行锁是什么样的情况
A:
begin;
update t set id=5;
看到事务中有了行排他锁出现,并且出现了transactionid并且xid是1860的行,说明这个是有实际数据操作的事务
testdb=# select locktype,relation::regclass as rel,page||','||tuple as ctid,virtualxid as vxid,transactionid as xid,virtualtransaction as vxid2,pid,mode,granted from pg_locks where pid=20353;
session B中也更新下行,看下情况
begin;
update t set id=10;
出现了阻塞,看下视图的情况
启动Session C查看锁
testdb=# select locktype,relation::regclass as rel,page||','||tuple as ctid,virtualxid as vxid,transactionid as xid,virtualtransaction as vxid2,pid,mode,granted from pg_locks where pid in (20171,20353);
如果想看哪个进程被阻塞了,只需要看“granted”字段值为“f”的pid即可。
加行锁的过程,是首先在表上加一个表级意向锁’。
pg_locks并不能显示出每个行锁的信息,原因很简单,行锁信息并不会记录到共享内存中,如果每个行锁在内存中存在一条记录的话,在对表做全表更新时,表有多少行,就需要在内存中记录多少条行锁信息,内存会容易吃不消。
行锁的阻塞信息是通过“transactionid”类型的锁体现出来的。从原理上来说,行锁是会在数据行上加上自己的xid的,另一个进程读到这一行时,如果发现有行锁,会把行上另一个事务的xid读出来,然后申请在这个xid上加“share”锁。而持有行锁的进程已经在此xid上加了“Exclusive”的锁,所以后边要更新这行的进程会被阻塞。
如上边截图中进程20171获取“share”被xid为1860的事务阻塞,xid 1860 对应进程20353 已经获得 “Exclusive”
select locktype,relation::regclass as rel,page||','||tuple as ctid,virtualxid as vxid,transactionid as xid,virtualtransaction as vxid2,pid,mode,granted from pg_locks;
select * from t where ctid='(0,1)';
看到是t表中id=1的记录导致阻塞