GaussDB(DWS)运维 -- 一键式锁等待和分布式死锁检测

举报
譡里个檔 发表于 2022/02/17 18:44:15 2022/02/17
【摘要】 锁是GaussDB(DWS)实现并发管理的关键要素,当并发场景下不同session的SQL语句执行时持有和申请的锁存在互斥时,对应的业务SQL就会产生等待甚至分布式死锁,从而导致业务性能抖动和下降,甚至业务报错。GaussDB(DWS)提供了两个集群级别的视图快速识别和查询锁等待和分布式死锁信息,可实现此类问题的秒级问题的定位和分析

锁是GaussDB(DWS)实现并发管理的关键要素,GaussDB(DWS)锁类别有表级锁、分区级锁(和表级锁一致)、事务锁、咨询锁等,当前业务最常用的是表级锁、分区级锁(和表级锁一致)、事务锁。不同的SQL语句执行时需要申请并持有对应的锁,当这些锁资源存在互斥时,对应的业务SQL就会产生等待;这种等待会产生下面几种后果

  1. 持锁的一方释放锁(一般对应的动作为持锁的事物提交),等待锁的一方申请到锁,然后继续执行
  2. 持锁的一方事物长时间未提交,等待锁的一方因为锁等待超时导致作业报错
  3. 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
  • TRUE表示当前线程持有上述指定的锁
  • FALSE表示当前线程正在申请锁,但是没有申请上,处在等待锁的状态

【解析】执行如下查询结果

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.t2AccessShareLock锁

  • 节点cn_5002上

        事务24112465通过线程140418784151296在等待申请表public.t1AccessShareLock

        事物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),等待锁的事务会因为锁等待超时失败。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。