暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

MogDB数据库的事务锁

725

MogDB数据库事务锁分为两类:表级锁和行级锁

表级锁

表级锁有8种模式:

ACCESS SHARE

只与ACCESS EXCLUSIVE冲突。

SELECT命令在被引用的表上请求一个这种锁。通常,任何只读取表而不修改它的命令都请求这种锁模式。

ROW SHARE

与EXCLUSIVE和ACCESS EXCLUSIVE锁模式冲突。

SELECT FOR UPDATE和SELECT FOR SHARE命令会自动在目标表上请求ROW SHARE锁(且所有被引用但不是FOR SHARE/FOR UPDATE的其他表上,还会自动加上ACCESS SHARE锁)。

ROW EXCLUSIVE

与ROW SHARE锁相同,ROW EXCLUSIVE允许并发读取表,但是禁止修改表中数据。UPDATE,DELETE,INSERT命令会自动在目标表上请求这个锁(且所有被引用的其他表上还会自动加上的ACCESS SHARE锁)。通常情况下,所有会修改表数据的命令都会请求表的ROW EXCLUSIVE锁。

SHARE UPDATE EXCLUSIVE

这个模式保护一个表的模式不被并发修改,以及禁止在目标表上执行垃圾回收命令(VACUUM )。

VACUUM(不带FULL选项),ANALYZE,CREATE INDEX CONCURRENTLY命令会自动请求这样的锁。

SHARE

SHARE锁允许并发的查询,但是禁止对表进行修改。

CREATE INDEX(不带CONCURRENTLY选项)语句会自动请求这种锁。

SHARE ROW EXCLUSIVE

SHARE ROW EXCLUSIVE锁禁止对表进行任何的并发修改,而且是独占锁,因此一个会话中只能获取一次。

任何SQL语句都不会自动请求这个锁模式。

EXCLUSIVE

EXCLUSIVE锁允许对目标表进行并发查询,但是禁止任何其他操作。

这个模式只允许并发加ACCESS SHARE锁,也就是说,只有对表的读动作可以和持有这个锁模式的事务并发执行。

任何SQL语句都不会在用户表上自动请求这个锁模式。然而在某些操作的时候,会在某些系统表上请求它。

ACCESS EXCLUSIVE

这个模式保证其所有者(事务)是可以访问该表的唯一事务。

ALTER TABLE,DROP TABLE,TRUNCATE,REINDEX命令会自动请求这种锁。

在LOCK TABLE命令没有明确声明需要的锁模式时,它是缺省锁模式。

手动获取行级锁

可以在事务块内部执行lock table命令获取指定的表级锁。

LOCK [ TABLE ] {[ ONLY ] name [, …]| {name [ * ]} [, …]}
[ IN {ACCESS SHARE | ROW SHARE | ROW EXCLUSIVE | SHARE UPDATE EXCLUSIVE | SHARE | SHARE ROW EXCLUSIVE | EXCLUSIVE | ACCESS EXCLUSIVE} MODE ]
[ NOWAIT ];

postgres=# begin; BEGIN postgres=# lock table stu ; LOCK TABLE postgres=# select pg_backend_pid(); pg_backend_pid ----------------- 140645423245056 (1 row) postgres=# select * from pg_locks where pid ='140645423245056'; locktype | database | relation | page | tuple | bucket | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid | sessionid | mode | granted | fastpath | locktag ---------------+----------+----------+------+-------+--------+------------+---------------+---------+-------+----------+--------------------+-----------------+-----------------+---------------- -----+---------+----------+------------------- virtualxid | | | | | | 3/6288 | | | | | 3/6288 | 140645423245056 | 140645423245056 | ExclusiveLock | t | t | 3:1890:0:0:0:7 relation | 15103 | 16385 | | | | | | | | | 3/6288 | 140645423245056 | 140645423245056 | AccessExclusive Lock | t | f | 3aff:4001:0:0:0:0 transactionid | | | | | | | 73662 | | | | 3/6288 | 140645423245056 | 140645423245056 | ExclusiveLock | t | f | 11fbe:0:0:0:0:6 (3 rows)

行级锁

行级锁有2种模式:

FOR UPDATE

FOR UPDATE会导致由SELECT语句检索到的行被锁定。这样避免它们在当前事务结束前被其他事务修改或者删除,即其他企图UPDATE、 DELETE、 SELECT FOR UPDATE这些行的事务将被阻塞,直到当前事务结束。

FOR SHARE

在每个检索出来的行上要求一个共享锁,而不是一个排他锁。一个共享锁阻塞其它事务执行UPDATE、DELETE、SELECT,不阻塞SELECT FOR SHARE。

手动获取表级锁

可以使用select…for update|share命令获取行级锁。

postgres=# begin; BEGIN postgres=# select *from stu for update; id | name ----+------ 1 | a 2 | a 3 | a 4 | a (4 rows) postgres=# select pg_backend_pid(); pg_backend_pid ----------------- 140645423245056 (1 row) postgres=# postgres=# select * from pg_locks where pid ='140645423245056'; locktype | database | relation | page | tuple | bucket | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid | sessionid | mode | granted | fastpath | locktag ---------------+----------+----------+------+-------+--------+------------+---------------+---------+-------+----------+--------------------+-----------------+-----------------+---------------+ ---------+----------+------------------- relation | 15103 | 16385 | | | | | | | | | 3/6292 | 140645423245056 | 140645423245056 | RowShareLock | t | t | 3aff:4001:0:0:0:0 virtualxid | | | | | | 3/6292 | | | | | 3/6292 | 140645423245056 | 140645423245056 | ExclusiveLock | t | t | 3:1894:0:0:0:7 transactionid | | | | | | | 73665 | | | | 3/6292 | 140645423245056 | 140645423245056 | ExclusiveLock | t | f | 11fc1:0:0:0:0:6 (3 rows)

查询数据库中锁阻塞信息

查询视图pg_locks可以获取数据库中事务锁阻塞信息

select * from pg_locks;

postgres=# select * from pg_locks; locktype | database | relation | page | tuple | bucket | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid | sessionid | mode | granted | fastpath | locktag ---------------+----------+----------+------+-------+--------+------------+---------------+---------+-------+----------+--------------------+-----------------+-----------------+---------------- -----+---------+----------+------------------- virtualxid | | | | | | 9/48 | | | | | 9/48 | 140645423245056 | 140645423245056 | ExclusiveLock | t | t | 9:30:0:0:0:7 virtualxid | | | | | | 3/6309 | | | | | 3/6309 | 140645440026368 | 140645440026368 | ExclusiveLock | t | t | 3:18a5:0:0:0:7 relation | 15103 | 11835 | | | | | | | | | 8/19332 | 140645607831296 | 140645607831296 | AccessShareLock | t | t | 3aff:2e3b:0:0:0:0 virtualxid | | | | | | 8/19332 | | | | | 8/19332 | 140645607831296 | 140645607831296 | ExclusiveLock | t | t | 8:4b84:0:0:0:7 relation | 15103 | 16385 | | | | | | | | | 9/48 | 140645423245056 | 140645423245056 | AccessExclusive Lock | t | f | 3aff:4001:0:0:0:0 relation | 15103 | 16385 | | | | | | | | | 3/6309 | 140645440026368 | 140645440026368 | AccessExclusive Lock | f | f | 3aff:4001:0:0:0:0 transactionid | | | | | | | 73670 | | | | 9/48 | 140645423245056 | 140645423245056 | ExclusiveLock | t | f | 11fc6:0:0:0:0:6 transactionid | | | | | | | 73671 | | | | 3/6309 | 140645440026368 | 140645440026368 | ExclusiveLock | t | f | 11fc7:0:0:0:0:6 (8 rows) postgres=# select locktag_decode('11fc7:0:0:0:0:6'); locktag_decode --------------------------------------------- locktype:transactionid, transactionid:73671 (1 row)

其中各列的描述如下:

locktype:被锁定对象的类型,共12种。

Lock 等待事件 描述
relation 对表加锁。
extend 对表扩展空间时加锁。
partition 对分区表加锁。
partition_seq 对分区表的分区加锁。
page 对表页面加锁。
tuple 对页面上的tuple加锁。
transactionid 对事务ID加锁。
virtualxid 对虚拟事务ID加锁。
object 加对象锁。
cstore_freespace 对列存空闲空间加锁。
userlock 加用户锁。
advisory 加advisory锁。

database:被锁定对象所在数据库的OID。

relation:被锁定对象的OID。

page:被锁定对象的页面编号。

tuple:被锁定对象的页面里边的行编号。

bucket:子表对应的bucket number。

virtualxid:事务的虚拟ID。

transactionid:事务的ID。

classid:包含该被锁定对象的系统表的OID。

objid:被锁定对象在其系统表内的OID。

objsubid:对于表的一个字段,这是字段编号;对于其他对象类型,这个字段是零。

virtualtransaction:持有此锁或者在等待此锁的事务的虚拟ID。

pid:持有或者等待这个锁的服务器线程的逻辑ID。

sessionid:持有或者等待这个锁的会话ID。

mode:这个线程持有的或者是期望的锁模式。

granted: 如果锁是持有锁,则为TRUE。如果锁是等待锁,则为FALSE。

fastpath:如果通过fast-path获得锁,则为TRUE;如果通过主要的锁表获得,则为FALSE。

locktag:会话等待锁信息,可通过locktag_decode()函数解析。

阻塞会话查询

with lock as (select usename,granted,locktag,query_start,query,l.pid
from pg_locks l,pg_stat_activity a
where l.pid=a.pid and locktag in(select locktag from pg_locks where granted=‘f’))
select locker.usename locker_user,locker.query_start locker_query_start,locker.granted locker_granted,locker.query locker_query,locker.pid locker_pid,locked.pid locked_pid,locked.query locked_query,locked.query_start locked_query_start,locked.granted locked_granted,locked.usename locked_user,extract(epoch from now() - locked.query_start) as locked_times
from (select * from lock where granted=‘t’) as locker,(select * from lock where granted=‘f’) locked
where locker.locktag=locked.locktag
order by 1;

postgres=# with lock as (select usename,granted,locktag,query_start,query,l.pid postgres(# from pg_locks l,pg_stat_activity a postgres(# where l.pid=a.pid and locktag in(select locktag from pg_locks where granted='f')) postgres-# select locker.usename locker_user,locker.query_start locker_query_start,locker.granted locker_granted,locker.query locker_query,locker.pid locker_pid,locked.pid locked_pid,locked.query locked_query,locked.query_start locked_query_start,locked.granted locked_granted,locked.usename locked_user,extract(epoch from now() - locked.query_start) as locked_times postgres-# from (select * from lock where granted='t') as locker,(select * from lock where granted='f') locked postgres-# where locker.locktag=locked.locktag postgres-# order by 1; locker_user | locker_query_start | locker_granted | locker_query | locker_pid | locked_pid | locked_query | l ocked_query_start | locked_granted | locked_user | locked_times -------------+------------------------------+----------------+-------------------------------------------+-----------------+-----------------+-------------------------------------------+------- ------------------------+----------------+-------------+-------------- omm | 2021-12-13 16:13:04.75289+08 | t | lock table stu in ACCESS EXCLUSIVE mode; | 140645423245056 | 140645440026368 | lock table stu in ACCESS EXCLUSIVE mode; | 2021-1 2-13 16:13:09.585616+08 | f | omm | 143.055227 (1 row)

手动kill掉锁阻塞会话

postgres=# select pg_terminate_backend(140645423245056); pg_terminate_backend ---------------------- t (1 row)

锁等待超时

当申请锁的操作等待时间超过数据库lockwait_timeout参数的设定值时,系统会报错。lockwait_timeout参数默认20分钟。

当更新操作等待时间操作数据库update_lockwait_timeout参数的设定值时,系统会报错。update_lockwait_timeout参数默认2分钟。

死锁检测

MOGDB数据库能够自动侦测到死锁,然后退出其中一个事务,从而允许其他事物执行。

当申请锁的等待时间超过数据库参数deadlock_timeout的设定值时,系统会检查是否产生了死锁。deadlock_timeout参数默认值为1s。

测试

测试前修改数据库参数log_lock_waits参数值为on,控制当一个会话为获得一个锁等到超过deadlock_timeout时,产生一个日志消息。默认是off不记录。

测试1:会话2执行锁表操作时,发生锁等待,等待时间达到1S时,系统进行死锁检测。锁等待时间达到lockwait_timeout设置的20min时,会话2执行的锁表操作报错退出,提示锁等待超时。

#会话1: postgres=# begin; BEGIN postgres=# lock table stu in ACCESS EXCLUSIVE mode; LOCK TABLE postgres=# select now(); now ------------------------------- 2021-12-13 15:25:23.535138+08 (1 row) #会话2: postgres=# begin; BEGIN postgres=# lock table stu in ACCESS EXCLUSIVE mode; ERROR: Lock wait timeout: thread 140645440026368 on node dn_6001 waiting for AccessExclusiveLock on relation 16385 of database 15103 after 1199000.033 ms DETAIL: blocked by hold lock thread 140645423245056, statement <select now();>, hold lockmode AccessExclusiveLock. postgres=# #日志: 2021-12-13 15:26:41.395 61b6f5ab.5064 postgres 140645440026368 gsql 73671 dn_6001 00000 2533274790414298 [BACKEND] LOG: thread 140645440026368 still waiting for AccessExclusiveLock on relation 16385 of database 15103 after 1000.051 ms 2021-12-13 15:46:40.395 61b6f5ab.5064 postgres 140645440026368 gsql 73671 dn_6001 00000 2533274790414298 [BACKEND] LOG: thread 140645440026368 still waiting for AccessExclusiveLock on relation 16385 of database 15103 after 1199000.033 ms 2021-12-13 15:46:40.395 61b6f5ab.5064 postgres 140645440026368 gsql 73671 dn_6001 YY003 2533274790414298 [BACKEND] ERROR: Lock wait timeout: thread 140645440026368 on node dn_6001 waiting for AccessExclusiveLock on relation 16385 of database 15103 after 1199000.033 ms 2021-12-13 15:46:40.395 61b6f5ab.5064 postgres 140645440026368 gsql 73671 dn_6001 YY003 2533274790414298 [BACKEND] DETAIL: blocked by hold lock thread 140645423245056, statement <select now();>, hold lockmode AccessExclusiveLock. 2021-12-13 15:46:40.395 61b6f5ab.5064 postgres 140645440026368 gsql 73671 dn_6001 YY003 2533274790414298 [BACKEND] STATEMENT: lock table stu in ACCESS EXCLUSIVE mode;

测试2:更新操作,锁等待时间达到update_lockwait_timeout设置的2min时,会话2执行的update操作报错退出,提示锁等待超时。

#会话1: postgres=# begin; BEGIN postgres=# select *from stu for update; id | name ----+------ 1 | a 2 | a 3 | a 4 | a (4 rows) #会话2: postgres=# begin; BEGIN postgres=# update stu set name = 'b' where id=2; ERROR: Lock wait timeout: thread 140645607831296 on node dn_6001 waiting for ShareLock on transaction 73665 after 119000.041 ms DETAIL: blocked by hold lock thread 140645423245056, statement <select pg_backend_pid();>, hold lockmode ExclusiveLock. postgres=# #日志: 2021-12-13 14:56:24.802 61b6ee19.5067 postgres 140645607831296 gsql 73666 dn_6001 00000 2533274790414263 [BACKEND] LOG: thread 140645607831296 still waiting for ShareLock on transaction 73665 after 119000.041 ms 2021-12-13 14:56:24.802 61b6ee19.5067 postgres 140645607831296 gsql 73666 dn_6001 YY003 2533274790414263 [BACKEND] ERROR: Lock wait timeout: thread 140645607831296 on node dn_6001 waiting for ShareLock on transaction 73665 after 119000.041 ms 2021-12-13 14:56:24.802 61b6ee19.5067 postgres 140645607831296 gsql 73666 dn_6001 YY003 2533274790414263 [BACKEND] DETAIL: blocked by hold lock thread 140645423245056, statement <select pg_backend_pid();>, hold lockmode ExclusiveLock. 2021-12-13 14:56:24.802 61b6ee19.5067 postgres 140645607831296 gsql 73666 dn_6001 YY003 2533274790414263 [BACKEND] STATEMENT: update stu set name = 'b' where id=2;
最后修改时间:2021-12-13 16:19:24
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论