面对锁等待难题,数仓如何实现问题的秒级定位和分析

作者: 小麦

更新时间:2022-03-26 14:30:25

513 阅读

摘要:GaussDB(DWS)提供了两个集群级别的视图快速识别和查询锁等待和分布式死锁信息,可实现此类问题的秒级问题的定位和分析。

本文分享自华为云社区《GaussDB(DWS)运维 -- 一键式锁等待和分布式死锁检测》,作者:譡里个檔。

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

  1. 持锁的一方释放锁(一般对应的动作为持锁的事物提交),等待锁的一方申请到锁,然后继续执行
  2. 持锁的一方事物长时间未提交,等待锁的一方因为锁等待超时导致作业报错
  3. 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里面的表上存在表级别(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库里面

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

 

点击关注,第一时间了解华为云新鲜技术~

版权声明:本文著作权归作者【小麦 】所有,不代表本网站立场。

侵权请联系:root_email@163.com

相关推荐