greenplum在执行vacuum和insert产生死锁问题定位及解决方案

首先声明:未经本人同意,请勿转载,谢谢!

本人使用自己编译的开源版本的greenplum数据库用于学习,版本为PostgreSQL 8.3.23 (Greenplum Database 4.3.99.00 build dev) on x86_64-unknown-linux-gnu, compiled by GCC gcc (GCC) 4.8.5 20150623

在使用的过程中遇到不少的问题,今天记录一下高并发的情况下,执行insert和vacuum操作造成的死锁,以及解决方案

一、问题描述:

在对ao分区表进行高并发测试的时候同时执行了vacuum的动作,发现会有死锁的问题产生,可以通过如下手段复现:

1)多个线程循环执行insert into t3 select * from XX(t3为本例中用于测试的分区表),为了提高问题复现的速度,可以仅对表中的一个分区进行操作

2)单个线程循环执行vacuum的动作

一段时间后会发现vacuum和insert都卡住,至此问题被复现出来。

二、结论:

该问题仅在AO分区表中会出现,产生的原因是由于数据库加锁的流程设计不合理导致(详情见分析过程)。

三、问题定位及解决方案

抛砖引玉,仅分享自己知道的一点点东西,如果有错误的地方还请指正,欢迎大家一起来讨论这个问题。

1)首先连接master执行:

select * from pg_stat_activity;

查到如下结果:

(图一)

发现有一个session是lock状态,为了弄清楚这里的lock的具体情况,则连接master和standby查看锁的具体情况,执行如下sql:

select a.locktype,b.relname,substring(c.current_query,1,50),c.xact_start,a.pid,a.mode,a.granted from pg_locks a,pg_class b,pg_stat_activity c
 where a.relation = b.oid and a.pid = c.procpid and relname like ‘t3%‘;

master:

(图二)

segment1:

(图三)

segment2:

(图四)

需要注意的是,这里图三和图四中在pg_stat_activity表中的current_query一列有<IDLE> in transaction,这里的值和我们平时看到的<IDLE>不是一回事,并不是一个空闲的连接,要弄清楚它的意义来看下官方文档:

(图五)

官方的文档里,对该值仅有这么一小段描述,这里并不能完全的表达出这个字段的意思,我们来看源码:

(图六)

源码里就可以看出这种状态说明该连接的事务是处于阻塞状态。

那么又是为什么会有锁等待的出现呢,因为同一时间,针对同一个对象,有些锁是不能够同时被不同的事务所持有的,如果一个事务持有了某个锁,另一个事务需要获取相同的锁或者是与这个锁冲突的锁,就会出现等待的情况,我们来看下锁之间的冲突情况:

(图七)

综合以上的所有信息,画出了如下的图:

(图七)

说明:

相同颜色的色块表示同一个连接(在物理机上,由ps -ef | grep $pid查到的con确定),红色的线表示等待关系。

由上图可以清晰的看出,在master上,con52等待con49持有的锁,而在segment2中,con49等待con52持有的锁,因此产生了死锁。

那么又是什么造成了这种锁的状态的形成?官方的描述中AccessShareLock仅仅是在对表数据不产生任何影响的查询语句才会申请:

(图八)

这里看图七的master节点,明明产生的是AccessShareLock,与官方的描述出现了不一致,对于这个疑惑,只有源码能告诉我们答案:

(图九)

这里有个if条件,这说明在处理ao分区表的时候,会在处理完成后会在master上加上AccessShareLock,结合图七和图九,也就解释了为什么在master会产生insert的锁等待vacuum的情况了,同时也回答了为什么结论中说该问题仅会出现在ao分区表中。

至此,产生这个问题的原因基本明晰了,那么遇到了这种情况该如何解决?

要知道不同的锁之间谁等待了谁,提供如下sql,很方便的就能知道等待关系:

create or replace function f_lock_level(i_mode text) returns integer as $$
begin
  RETURN  (select case i_mode
    when ‘INVALID‘ then 0
    when ‘AccessShareLock‘ then 1
    when ‘RowShareLock‘ then 2
    when ‘RowExclusiveLock‘ then 3
    when ‘ShareUpdateExclusiveLock‘ then 4
    when ‘ShareLock‘ then 5
    when ‘ShareRowExclusiveLock‘ then 6
    when ‘ExclusiveLock‘ then 7
    when ‘AccessExclusiveLock‘ then 8
    else 0
  end );
end;
$$ language plpgsql strict;

--查询
with t_wait as
(select a.mode,a.locktype,a.database,a.relation,a.page,a.tuple,a.classid,a.objid,a.objsubid,
a.pid,a.virtualtransaction,a.virtualxid,a,transactionid,b.current_query,b.xact_start,b.query_start,
b.usename,b.datname from pg_locks a,pg_stat_activity b where a.pid=b.procpid and not a.granted),
t_run as
(select a.mode,a.locktype,a.database,a.relation,a.page,a.tuple,a.classid,a.objid,a.objsubid,
a.pid,a.virtualtransaction,a.virtualxid,a,transactionid,b.current_query,b.xact_start,b.query_start,
b.usename,b.datname from pg_locks a,pg_stat_activity b where a.pid=b.procpid and a.granted)
select r.locktype,r.mode r_mode,r.usename r_user,r.datname r_db,r.relation::regclass,r.pid r_pid,
r.page r_page,r.tuple r_tuple,r.xact_start r_xact_start,r.query_start r_query_start,
now()-r.query_start r_locktime,r.current_query r_query,w.mode w_mode,w.pid w_pid,w.page w_page,
w.tuple w_tuple,w.xact_start w_xact_start,w.query_start w_query_start,
now()-w.query_start w_locktime,w.current_query w_query
from t_wait w,t_run r where
  r.locktype is not distinct from w.locktype and
  r.database is not distinct from w.database and
  r.relation is not distinct from w.relation and
  r.page is not distinct from w.page and
  r.tuple is not distinct from w.tuple and
  r.classid is not distinct from w.classid and
  r.objid is not distinct from w.objid and
  r.objsubid is not distinct from w.objsubid and
  r.transactionid is not distinct from w.transactionid and
  r.pid <> w.pid
  order by f_lock_level(w.mode)+f_lock_level(r.mode) desc,r.xact_start;

注意:上面的sql适用于文章开头部分greenplum内核版本,不同的版本会有些许差异,这个sql是我根据文章最后参考资料德哥给出的SQL自己修改的,如果您的环境中无法运行,请使用德哥给出的语句。

该sql会返回类似如下的结果(部分):

(图十)

每条结果以w_开头的结果表示正在等待的会话信息,以r_开头的代表正在运行的会话信息。

为了不影响正常的插入流程,可以找到vacuum语句的pid,使用select pg_terminate_backend($pid);语句终止vacuum的会话,业务即可继续进行。

那么问题是不是就此可以结束?其实并没有,我们来看官方文档:

官方指出,在发生死锁的情况下,会自动的回滚一个事务,保证另一个事务的正常运行,那么在上面所述的情况下,为什么没有发生事务的自动回滚?这是我一直没有想明白的问题,如果有相关的专业人士看到这篇文章请指点一二,解答我心中的疑问,感激不尽!

参考资料:

https://yq.aliyun.com/articles/86631

https://github.com/greenplum-db/gpdb/pull/425

https://github.com/greenplum-db/gpdb/issues/2837

在github中,有提出使用debug的方式复现死锁的问题,这是最快捷的方式,同时也给出了源码级别的修复建议,感兴趣的朋友可以自行阅读。

结语:

本人一直做的是java的开发工作,C并不是特别熟悉,要解决这个问题,根本上还得从源码入手,本人没有这个能力,如果有朋友知道如何修复这个问题,还请告知。同时本文如有表述不对的地方,还请各位指出,我会加强自身的学习,保证分享出来的东西是正确的。

另外,推荐一个好朋友的公众号,我觉得里面文章特别好,很多问题是真正讲清楚了,全都是他自己一点点整理出来的,我一直在读他的文章,受益良多。

时间: 2024-11-14 19:16:44

greenplum在执行vacuum和insert产生死锁问题定位及解决方案的相关文章

Greenplum 调优--VACUUM系统表

1.VACUUM系统表原因 Greenplum是基于MVCC版本控制的,所有的delete并没有删除数据,而是将这一行数据标记为删除, 而且update其实就是delete加insert.所以,随着操作越来越多,表的大小也会越来越大.对于OLAP 应用来说,大部分表都是一次导入后不再修改,所以不会出现这个问题. 但是对于数据字典来说,就会随着时间表越来越大,其中的数据垃圾越来越多. 2.Greenplum的VACUUM工具 Greenplum的VACUUM工具,可以回收已经删除行占据的存储空间.

sql server中高并发情况下 同时执行select和update语句死锁问题 (一)

 最近在项目上线使用过程中使用SqlServer的时候发现在高并发情况下,频繁更新和频繁查询引发死锁.通常我们知道如果两个事务同时对一个表进行插入或修改数据,会发生在请求对表的X锁时,已经被对方持有了.由于得不到锁,后面的Commit无法执行,这样双方开始死锁.但是select语句和update语句同时执行,怎么会发生死锁呢?看完下面的分析,你会明白的- 首先看到代码中使用的查询的方法Select <span style="font-size:18px;"> /// &

Python 3.6.0的sqlite3模块无法执行VACUUM语句

Python 3.6.0的sqlite3模块存在一个bug(见issue 29003),无法执行VACUUM语句. 一执行就出现异常: Traceback (most recent call last):  File "D:\desktop\cannot_vacuum.py", line 25, in <module>    conn.execute('VACUUM')sqlite3.OperationalError: cannot VACUUM from within a

sqlserver 抓取所有执行语句 SQL语句分析 死锁 抓取

原文:sqlserver 抓取所有执行语句 SQL语句分析 死锁 抓取 在多人开发中最头疼的是人少事多没有时间进行codereview,本来功能都没时间写,哪有时间来开会细细来分析代码.软件能跑就行,但是一些影响性能的语句写出来,有可能本人都不知道.找就更 麻烦了.幸亏sqlserver提供了工具可以导出执行语句进行分析.可以看看是哪些语句影响整体性能.工具叫sql server profiler,这玩意可以抓取实例上执行的所有语句\死锁\事物,为分析提供帮助. 开始->sqlserver目录-

彻底搞懂OC中GCD导致死锁的原因和解决方案

GCD提供了功能强大的任务和队列控制功能,相比于NSOperationQueue更加底层,因此如果不注意也会导致死锁. 所谓死锁,通常指有两个线程A和B都卡住了,并等待对方完成某些操作.A不能完成是因为它在等待B完成.但B也不能完成,因为它在等待A完成.于是大家都完不成,就导致了死锁(DeadLock). 有一定GCD使用经验的新手通常认为,死锁是很高端的操作系统层面的问题,离我很远,一般不会遇上.其实这种想法是非常错误的,因为只要简单三行代码(如果愿意,甚至写在一行就可以)就可以人为创造出死锁

牛人笔记----(死锁问题定位与解决方法)

1 --死锁问题定位与解决方法 2 3 --为了解决死锁问题,SQLSERVER数据库引擎死锁监视器会定期检查陷入死锁的任务 4 --如果监视器检测到这种依赖循环关系,会选择其中一个任务作为牺牲品,然后 5 --终止其事务并提示错误.这就是用户会遇到的死锁错误 6 7 --可以发生死锁的资源 8 --需要说明的是,死锁不是只发生在锁资源上,以下类型的资源都可能会造成阻塞,并 9 --最终导致死锁 10 11 --1.锁:例如:页,行,元数据和应用程序上的锁 12 --2.工作线程:如果排队等待线

阻塞与死锁(三)——死锁的定位及解决方法

原文:阻塞与死锁(三)--死锁的定位及解决方法 死锁所在的资源和检测: 在SQL Server的两个或多个任务中,如果某个任务锁定了其他任务试图锁定的资源.会造成这些任务的永久阻塞,从而出现死锁. 下图为例: l  事务T1获得了行R1的共享锁. l  事务T2获得了行R2的共享锁. l  然后事务T1请求行R2的排它锁,但是T2完成并释放其对R2的共享锁之前被阻塞. l  T2请求行R1的排它锁,但是事务T1完成并释放其对R1持有的共享锁之前被阻塞. 现在T2与T1相互等待,导致了死锁.一般情

客户端cmd打开mysql,执行插入中文报错或插入中文乱码解决方案

最近在制作一个安装包,需要安装的时候执行mysql脚本儿,做了一个批处理,但是发现总是执行到 插入中文的时候报错,或者插入中文是乱码. 网上查了好多资料,说是把编码改成GBK什么的,终究还是不成功. 最后经过多次测试,现把解决方案分享给大家. 第一步:打开mysql中的配置文件,my.ini,看一看配置文件中 [mysql] default-character-set=utf8 [mysqld] character-set-server=utf8 看是不是这样配置的.因为utf8 是国际通用的,

Java 程序死锁问题原理及解决方案

本文已经于2015年8月24日发表于IBM开发者论坛 Java 语言通过 synchronized 关键字来保证原子性,这是因为每一个 Object 都有一个隐含的锁,这个也称作监视器对象.在进入 synchronized 之前自动获取此内部锁,而一旦离开此方式,无论是完成或者中断都会自动释放锁.显然这是一个独占锁,每个锁请求之间是互斥的.相对于众多高级锁 (Lock/ReadWriteLock 等),synchronized 的代价都比后者要高.但是 synchronzied 的语法比较简单,