GaussDB常规锁简介
l spinLock(自旋锁),系统级共享资源的封锁操作
l LWLock(轻量锁):系统级共享资源的封锁操作,在系统运行期间,系统级的资源需要加锁,操作后,被释放。
l RegularLock(常规锁/重量锁):用于并发保护用户表的数据。
常规锁按照被锁对象按照类型可以分为10种:relation,extend,page,tuple,transactionid,virtualxid,object,userlock,advisory,pg_locks视图中第一列locktype的取值就是这些。下面我们主要介绍常见的表锁(relation)和行锁(tuple)。
1 表锁
当对表进行DDL/DML 操作时,数据库会对表进行加锁操作,在事务结束时释放。常规锁按照粒度可以分为8个等级,各操作对应的锁以及相容性如下表所示:
当两个事务的锁产生冲突时,未拿到锁的线程会等锁,等锁超过系统设置参数lockwait_timeout(默认值20min)就会报错。报错信息会将持锁语句等信息打印出来,例如:
ERROR: Lock wait timeout: thread 140354461361920 on node coordinator1 waiting for AccessShareLock on relation 16655 of database 14764 after 1200.057 s DETAIL: blocked by hold lock thread 140354804238080, statement <drop table aa;>, hold lockmode AccessExclusiveLock. |
2 行锁
2.1 行锁模式
GaussDB不支持for key share和for no key update模式的行级锁,支持以下两种模式:
l For share: 使用select for share语句时持有该模式锁,后台会对tuple加5级锁(ShareLock);
l For update: 使用select for update, delete, update等操作时持有该模式的锁,后台会对tuple加7级锁(ExclusiveLock),根据上图中各级锁的相容性可知,并发更新同一条语句时会产生行锁冲突;
2.2 并发更新参数
当allow_concurrent_tuple_update=false时,并发更新同一条记录不会等锁,直接报错:
abort transaction due to concurrent update |
当allow_concurrent_tuple_update=true时,并发更新同一条记录会产生锁冲突。等锁超过系统设置参数update_lockwait_timeout(默认值2min)就会报错。报错信息可以分为几种,最重要的标志就是超时时间。
2.3 行锁加锁流程
行锁等到事务提交才会释放,其他事物如果等待这个行锁,必须等待这个事务锁释放。tuple锁可以保证多个修改事务加锁的顺序问题,原则是先来先拿锁,修改完tuple后,tuple锁会立即释放,而事务锁不会释放。假设有3个事务,A、B、C依次对同一行修改,均未提交:
Session A |
Session B |
Session C |
begin; update t set b=1; |
||
set max_query_retry_times=0; begin; update t set b=2; |
||
set update_lockwait_timeout='1s'; set max_query_retry_times=0; begin; update t set b=3; |
此时A处于idle in transaction状态,B持有tuple锁但是等待A的事务锁,C等待B持有的tupe锁。如果B和C分别锁超时,那么超时报错信息如下所示:
C 先报错:
ERROR: Lock wait timeout: thread 139728477222656 on node datanode3 waiting for ExclusiveLock on tuple (0,1) of relation 33287 of database 14194 after 2000.096 ms DETAIL: blocked by hold lock thread 139728418502400, statement <UPDATE public.t SET b = 2>, hold lockmode ExclusiveLock. |
B报错:
ERROR: Lock wait timeout: thread 139728477222656 on node datanode3 waiting for ShareLock on transaction 24858 after 120018.206 ms DETAIL: blocked by hold lock thread 139728691128064, statement < update t set b=1; >, hold lockmode ExclusiveLock. |
注释:
1. 如果此时A事务中又执行了一条select * from t;那么B的报错信息中statement就是<select * from t;>。
2. 对于列存表,行锁可以理解为对一个cu的锁,因此,即时表面上更新的数据不是同一行,也可能出现等锁的情况。
2.4 其他报错案例
如果持锁事务残留或者正处于提交状态,detail信息可能是
ERROR: dn_6005_6006: Lock wait timeout: thread 140354804238080 on node dn_6001_6002 waiting for ShareLock on transaction 867484 fter 12000.453ms DETAIL: blocked by hold lock thread 0, statement <pending twophase transaction>, hold lockmode (null). |
此时可以根据事务号查询是否有残留事务:
Select * from pg_prepared_xacts; |
如果确认残留,调用gs_clean清理残留事务即可。
3 如何查询锁等待
在没有锁等待超时报错的情况下,我们通常会结合pgxc_stat_activity, pgxc_thread_wait_statusm pg_locks等视图来定位锁等待问题,这种方法过于繁琐。我们可以采用以下两种简单方式之一来快速定位锁等待问题:
方法一:
通过设置参数来达到提前报错的目的,具体可以参考案例:
https://bbs.huaweicloud.com/forum/thread-82664-1-1.html
方法二:
使用附件提供的工具查询锁等待情况.使用方法:
1. 创建存储过程
2. 在cn上执行
SELECT * FROM pgxc_locks_wait; |
4 锁超时处理办法
现场规避:通过以上方法,找到持锁线程,在对应的实例上执行以下sql杀掉持锁线程
Select pg_terminate_backend(xxxxx); --xxxxx为找到的持锁线程 |
长期手段:从业务侧避免锁冲突行为。
- 点赞
- 收藏
- 关注作者
评论(0)