GaussDB(DWS)运维 -- 一键式锁等待和分布式死锁检测
锁是GaussDB(DWS)实现并发管理的关键要素,GaussDB(DWS)锁类别有表级锁、分区级锁(和表级锁一致)、事务锁、咨询锁等,当前业务最常用的是表级锁、分区级锁(和表级锁一致)、事务锁。不同的SQL语句执行时需要申请并持有对应的锁,当这些锁资源存在互斥时,对应的业务SQL就会产生等待;这种等待会产生下面几种后果
- 持锁的一方释放锁(一般对应的动作为持锁的事物提交),等待锁的一方申请到锁,然后继续执行
- 持锁的一方事物长时间未提交,等待锁的一方因为锁等待超时导致作业报错
- A实例上持锁事物和申请锁的事物在B实例上角色互换,产生分布式死锁(具体见下文介绍)。这种场景下需要首先达到锁等待超时的事物报错回滚时释放锁资源,然后另外一个事物申请到才能正常进行
从上述的描述可以看到,锁等待特别是分布式死锁对业务影响很大,轻则产生等待导致业务性能抖动和下降,甚至业务报错。GaussDB(DWS)提供了两个集群级别的视图快速识别和查询锁等待和分布式死锁信息,可实现此类问题的秒级问题的定位和分析
1)锁等待检测视图pgxc_lock_conflicts
【功能】查询当前库里面不同节点上的锁等待信息
字段名称 | 数据类型 | 字段描述 |
locktype | text | 被锁定对象的类型,当前支持relation、partition、transactionid三种类型的锁类型 |
nodename | name | 被锁定对象的节点的名称 |
dbname | name | 被锁定对象的数据库的名称。如果被锁定对象是事务,则为NULL |
nspname | name | 被锁定对象的命名空间的名称 |
relname | name | 被锁定对象对应的relation的名称。如果被锁定对象既不是relation,也不是relation的一部分,则为NULL |
partname | name | 被锁定对象对应的分区的名称。如果被锁定对象不是分区,则为NULL |
page | integer | 被锁定对象对应的页面的编号。如果被锁定对象既不是页面,也不是元组,则为NULL |
tuple | smallint | 被锁定对象对应的元组的编号。如果被锁定对象不是元组,则为NULL |
transactionid | xid | 被锁定对象对应的事务的ID。如果被锁定对象不是事务,则为NULL |
username | name | 当前线程所处session的用户名,一般也是当前线程执行语句的用户名称 |
gxid | xid | 当前线程所处事物的事物ID |
xactstart | timestamptz | 当前线程所处事务的事物开始时间 |
queryid | bigint | 申请锁的线程的最新查询的queryid |
query | text | 申请锁的线程的最新查询语句 |
pid | bigint | 申请锁的线程的ID |
mode | text | 锁的级别 |
granted | boolean |
|
【解析】执行如下查询结果
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里面的表public.xxx上存在表级别(relation)的锁冲突。线程140420489144064持有锁AccessExclusiveLock(mode = true),线程140418767369984在等待(mode = false)AccessShareLock锁
2)分布式锁等待检测视图pgxc_deadlock
【功能】查询当前库里面不同节点上的分布式死锁信息
字段名称 | 数据类型 | 字段描述 |
locktype | text | 被锁定对象的类型,当前支持relation、partition、transactionid三种类型的锁类型 |
nodename | name | 被锁定对象的节点的名称 |
dbname | name | 被锁定对象的数据库的名称。如果被锁定对象是事务,则为NULL |
nspname | name | 被锁定对象的命名空间的名称 |
relname | name | 被锁定对象对应的关系的名称。如果被锁定对象既不是关系,也不是关系的一部分,则为NULL |
partname | name | 被锁定对象对应的分区的名称。如果被锁定对象不是分区,则为NULL |
page | integer | 被锁定对象对应的页面的编号。如果被锁定对象既不是页面,也不是元组,则为NULL |
tuple | smallint | 被锁定对象对应的元组的编号。如果被锁定对象不是元组,则为NULL |
transactionid | xid | 被锁定对象对应的事务的ID。如果被锁定对象不是事务,则为NULL |
waitusername | name | 等待锁的用户的名称。 |
waitgxid | xid | 等待锁的事务的ID |
waitxactstart | timestamptz | 等待锁的事务的开始时间 |
waitqueryid | bigint | 等待锁的线程的最新查询ID |
waitquery | text | 等待锁的线程的最新查询语句 |
waitpid | bigint | 等待锁的线程的ID |
waitmode | text | 等待的锁的级别 |
holdusername | name | 持有锁的用户的名称 |
holdgxid | xid | 持有锁的事务的ID |
holdxactstart | timestamptz | 持有锁的事务的开始时间 |
holdqueryid | bigint | 持有锁的线程的最新查询ID |
holdquery | text | 持有锁的线程的最新查询语句 |
holdpid | bigint | 持有锁的线程的ID |
holdmode | text |
持有的锁的级别 |
【解析】执行如下查询结果
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库里面
- 节点cn_5001上
事务24112465通过线程140160421234432持有表public.t2的AccessExclusiveLock锁
事务24112406通过线程140160505136896在等待申请表public.t2的AccessShareLock锁
- 节点cn_5002上
事务24112465通过线程140418784151296在等待申请表public.t1的AccessShareLock锁
事物24112406通过线程140421763163904持有表public.t1的AccessExclusiveLock锁
如果我们把资源的持有情况按照持有到申请定义一个防线的话,可以形成如下表格
节点 | 事务24112465 | 方向 | 事务24112406 |
cn_5001 | 持有表public.t2的AccessExclusiveLock锁 | → | 申请表public.t2的AccessShareLock锁 |
cn_5002 | 申请表public.t1的AccessShareLock锁 | ← | 持有表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),等待锁的事务会因为锁等待超时失败。
- 点赞
- 收藏
- 关注作者
评论(0)