本文分享自华为云社区《GaussDB(DWS)运维 -- 一键式锁等待和分布式死锁检测》,作者:譡里个檔。
锁是 GaussDB(DWS)实现并发管理的关键要素,GaussDB(DWS)锁类别有表级锁、分区级锁(和表级锁一致)、事务锁、咨询锁等,当前业务最常用的是表级锁、分区级锁(和表级锁一致)、事务锁。不同的 SQL 语句执行时需要申请并持有对应的锁,当这些锁资源存在互斥时,对应的业务 SQL 就会产生等待;这种等待会产生下面几种后果:
持锁的一方释放锁(一般对应的动作为持锁的事物提交),等待锁的一方申请到锁,然后继续执行
持锁的一方事物长时间未提交,等待锁的一方因为锁等待超时导致作业报错
A 实例上持锁事物和申请锁的事物在 B 实例上角色互换,产生分布式死锁(具体见下文介绍)。这种场景下需要首先达到锁等待超时的事物报错回滚时释放锁资源,然后另外一个事物申请到才能正常进行
从上述的描述可以看到,锁等待特别是分布式死锁对业务影响很大,轻则产生等待导致业务性能抖动和下降,甚至业务报错。GaussDB(DWS)提供了两个集群级别的视图快速识别和查询锁等待和分布式死锁信息,可实现此类问题的秒级定位和分析。
1)锁等待检测视图 pgxc_lock_conflicts
【功能】查询当前库里面不同节点上的锁等待信息
【解析】执行如下查询结果
postgres=# SELECT * FROM pgxc_lock_conflicts ORDER BY nodename,dbname,locktype,nspname,relname,partname;
locktype | nodename | dbname | nspname | relname | partname | page | tuple | transactionid | username | gxid | xactstart | queryid | query | pid | mode | granted
-----------+----------+----------+---------+-----------------------+----------+------+-------+---------------+-----------+----------+-------------------------------+--------------------+----------------------------------------------------------+-----------------+---------------------+---------
partition | cn_5001 | postgres | public | table_partition_num_3 | p1 | | | | dfm | 24097147 | 2022-02-17 17:56:03.113194+08 | 104145741383084190 | alter table table_partition_num_3 truncate partition p1; | 140160505136896 | AccessExclusiveLock | f
partition | cn_5001 | postgres | public | table_partition_num_3 | p1 | | | | dfm | 24102679 | 2022-02-17 18:41:36.580348+08 | 0 | alter table table_partition_num_3 truncate partition p1; | 140160568055552 | AccessExclusiveLock | t
relation | cn_5002 | postgres | public | xxx | | | | | dfm | 24102679 | 2022-02-17 18:41:36.580348+08 | 175921860444402398 | truncate xxx; | 140418767369984 | AccessShareLock | f
relation | cn_5002 | postgres | public | xxx | | | | | dfm | 24097147 | 2022-02-17 17:56:03.113194+08 | 0 | truncate xxx; | 140420489144064 | AccessExclusiveLock | t
(4 rows)
复制代码
如上的 SQL 显示
在节点 cn_5001 的 postgres 里面的表 public.table_partition_num_3 的分区 p1 上存在分区级别(partition)的锁冲突。在当前的锁冲突中线程 140160568055552 持有锁(mode = true),锁级别是 AccessExclusiveLock,执行语句为 alter table table_partition_num_3 truncate partition p1。线程 140160568055552 在等待(mode = false)AccessExclusiveLock 锁,等待锁的语句也是 alter table table_partition_num_3 truncate partition p1。
在节点 cn_5002 的 postgres 里面的表http://public.xxx上存在表级别(relation)的锁冲突。线程 140420489144064 持有锁 AccessExclusiveLock(mode = true),线程 140418767369984 在等待(mode = false)AccessShareLock 锁
2)分布式锁等待检测视图 pgxc_deadlock
【功能】查询当前库里面不同节点上的分布式死锁信息
【解析】执行如下查询结果
postgres=# SELECT * FROM pgxc_deadlock ORDER BY nodename,dbname,locktype,nspname,relname,partname;
locktype | nodename | dbname | nspname | relname | partname | page | tuple | transactionid | waitusername | waitgxid | waitxactstart | waitqueryid | waitquery | waitpid | waitmode | holdusername | holdgxid | holdxactstart | holdqueryid | holdquery | holdpid | holdmode
----------+----------+----------+---------+---------+----------+------+-------+---------------+--------------+----------+-------------------------------+--------------------+-----------------------------------------------------+-----------------+-----------------+--------------+----------+-------------------------------+-------------+--------------+-----------------+---------------------
relation | cn_5001 | postgres | public | t2 | | | | | j00565968 | 24112406 | 2022-02-17 20:01:57.421532+08 | 104145741383110084 | EXECUTE DIRECT ON(dn_6003_6004) 'SELECT * FROM t2'; | 140160505136896 | AccessShareLock | j00565968 | 24112465 | 2022-02-17 20:02:24.220656+08 | 0 | TRUNCATE t2; | 140160421234432 | AccessExclusiveLock
relation | cn_5002 | postgres | public | t1 | | | | | j00565968 | 24112465 | 2022-02-17 20:02:24.220656+08 | 175921860444446866 | EXECUTE DIRECT ON(dn_6001_6002) 'SELECT * FROM t1'; | 140418784151296 | AccessShareLock | j00565968 | 24112406 | 2022-02-17 20:01:57.421532+08 | 0 | TRUNCATE t1; | 140421763163904 | AccessExclusiveLock
(2 rows)
复制代码
如上的 SQL 显示,在 postgres 库里面
事务 24112465 通过线程 140160421234432 持有表 public.t2 的 AccessExclusiveLock 锁
事务 24112406 通过线程 140160505136896 在等待申请表 public.t2 的 AccessShareLock 锁
事务 24112465 通过线程 140418784151296 在等待申请表 public.t1 的 AccessShareLock 锁
事物 24112406 通过线程 140421763163904 持有表 public.t1 的 AccessExclusiveLock 锁
如果我们把资源的持有情况按照持有到申请定义一个防线的话,可以形成如下表格
从上述可以看出,事务 24112465 在节点 cn_5001 持有表 public.t2 的 AccessExclusiveLock 锁,等待申请申请表 public.t1 的 AccessShareLock 锁;事务 24112406 在节点 cn_5002 上持有表 public.t1 的 AccessExclusiveLock 锁,等待申请申请表 public.t2 的 AccessShareLock 锁;事务 24112406 和事务 24112465 只有等待彼此提交才能申请到锁资源,让自己继续执行,这种在多个实例上的分布式等待关系形成了一个环状,我们称这种现象为分布式死锁。
3) 锁等待和分布式死锁的区别
对于分布式死锁,只能一个事务因为锁等待(参数lockwait_timeout)超时回滚的时候,另外一个事务才能进行下去;或者人工干预 kill 或者 cancel 其中一个事务,让另外一个事务进行下去。
对于没有分布式死锁的锁等待,这种一般不需要人工干涉,等待持锁事务正常执行完成之后另外一个事务就可以正常执行;但是如果事务持锁时间超过锁等待超时参数(参数lockwait_timeout),等待锁的事务会因为锁等待超时失败。
点击关注,第一时间了解华为云新鲜技术~
评论