PostgreSQL 查询涉及分区表过多导致的性能问题 - 性能诊断与优化(大量BIND, spin lock, SLEEP进程)...

摘要: 标签 PostgreSQL , 分区表 , bind , spin lock , 性能分析 , sleep 进程 , CPU空转 , cache 背景 实际上我写过很多文档,关于分区表的优化: 《PostgreSQL 商用版本EPAS(阿里云ppas) - 分区表性能优化 (堪比pg_pathman)》 《PostgreSQL 传统 hash 分区方法和性能》 《PostgreSQL 10 内置分区 vs pg_pathman perf profiling》 实际上native分区表的性能问题主要还是在于分区表过多的时候,执行计划需要耗时很久。

点此查看原文:https://yq.aliyun.com/articles/405176?spm=a2c4e.11153959.teamhomeleft.44.8WKxt7

实际上native分区表的性能问题主要还是在于分区表过多的时候,执行计划需要耗时很久。

因此有了

1、PPAS的edb_enable_pruning参数,可以在生成执行计划前,使用简单SQL的话,直接过滤到目标分区,从而不需要的分区不需要进入执行计划的环节。

2、pg_pathman则支持的更全面,除了简单SQL,复杂的表达式,immutable都可以进行过滤,过滤到目标分区,从而不需要的分区不需要进入执行计划的环节。

因分区表过多引发的问题通常出现在OLTP系统(主要是OLTP系统的并发高,更容易把这种小问题放大),本来一次请求只需要1毫秒的,但是执行计划可能需要上百毫秒,也就是说执行耗时变成了小头,而执行计划(SPIN LOCK)变成了大头。

下面这个例子也是OLTP系统相关的,有具体的原因分析。

SQL访问的分区表过多,并发高时CPU负载高,但是大量的是SLEEP状态的BIND进程。

某个业务系统,单次SQL请求很快,几十毫秒,但是并发一高,QPS并没有线性的增长。

而且大量的进程处于BIND,SLEEP的状态。

图片描述

经过诊断,

《PostgreSQL 源码性能诊断(perf profiling)指南》

《Linux 性能诊断 perf使用指南》

主要的原因是大量的SPIN LOCK,导致CPU空转。

perf record -ag    

perf report -g

图片描述

比如某个进程BIND时的pstack

#pstack  18423    
#0  0x00002ad051f3ef67 in semop () from /lib64/libc.so.6  -- 这边到了内核,上spin lock    
#1  0x0000000000656117 in PGSemaphoreLock ()    
#2  0x00000000006c274a in LWLockAcquire ()      
#3  0x00000000006bd136 in LockAcquireExtended ()      
#4  0x00000000006b8768 in LockRelationOid ()  -- 对所有的子表都会调用这个函数,导致spinlock    
#5  0x000000000050c10a in find_inheritance_children ()      
#6  0x000000000050c212 in find_all_inheritors ()  -- 找到所有子表    
#7  0x0000000000645e4e in expand_inherited_tables ()    
#8  0x000000000063a6e8 in subquery_planner ()    
#9  0x0000000000618c4f in set_rel_size ()    
#10 0x0000000000618e7c in set_rel_size ()    
#11 0x0000000000619587 in make_one_rel ()    
#12 0x0000000000636bd1 in query_planner ()    
#13 0x000000000063862c in grouping_planner ()    
#14 0x000000000063a9c4 in subquery_planner ()    
#15 0x0000000000618c4f in set_rel_size ()    
#16 0x0000000000619587 in make_one_rel ()    
#17 0x0000000000636bd1 in query_planner ()    
#18 0x000000000063862c in grouping_planner ()    
#19 0x000000000063a9c4 in subquery_planner ()    
#20 0x0000000000618c4f in set_rel_size ()    
#21 0x0000000000619587 in make_one_rel ()    
#22 0x0000000000636bd1 in query_planner ()    
#23 0x000000000063862c in grouping_planner ()    
#24 0x000000000063b0d0 in standard_planner ()    
#25 0x00000000006d1597 in pg_plan_queries ()    
#26 0x00000000007ca156 in BuildCachedPlan ()    
#27 0x00000000007ca525 in GetCachedPlan ()    
#28 0x00000000006d1d07 in exec_bind_message ()    
#29 0x00000000006d44de in PostgresMain ()    
#30 0x000000000066bd5f in PostmasterMain ()    
#31 0x00000000005f474c in main ()    

由于业务使用了prepared statement,所以过程会变成bind 过程

1、prepare statement

2、bind parameters

3、代入参数、(设置了constraint_exclusion时)判断哪些分区需要被过滤

4、execute prepared statement

在find_all_inheritors过程中,涉及的分区表过多,最后每个分区都要取LOCK(后面加载了系统的spin lock),所以我们会看到CPU很高,同时大量的BIND,进程处于SLEEP状态,也就是CPU空转,CPU时间片被独占的状态。

spinlock (自旋锁)
自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,不需要自旋锁)。

自旋锁最多只能被一个内核任务持有,如果一个内核任务试图请求一个已被争用(已经被持有)的自旋锁,那么这个任务就会一直进行忙循环——旋转——等待锁重新可用。

要是锁未被争用,请求它的内核任务便能立刻得到它并且继续进行。自旋锁可以在任何时刻防止多于一个的内核任务同时进入临界区,因此这种锁可有效地避免多处理器上并发运行的内核任务竞争共享资源。

事实上,自旋锁的初衷就是:

在短期间内进行轻量级的锁定。一个进程去获取被争用的自旋锁时,请求它的线程在等待锁重新可用的期间进行自旋(特别浪费处理器时间),所以自旋锁不应该被持有时间过长(等待时CPU被独占)。如果需要长时间锁定的话, 最好使用信号量(睡眠,CPU资源可出让)

简单的说,自旋锁在内核中主要用来防止多处理器中并发访问临界区,防止内核抢占造成的竞争。另外自旋锁不允许任务睡眠(持有自旋锁的任务睡眠会造成自死锁——因为睡眠有可能造成持有锁的内核任务被重新调度,而再次申请自己已持有的锁),它能够在中断上下文使用。

死锁:假设有一个或多个内核任务和一个或多个资源,每个内核都在等待其中的一个资源,但所有的资源都已经被占用了。这便会发生所有内核任务都在相互等待,但它们永远不会释放已经占有的资源,于是任何内核任务都无法获得所需要的资源,无法继续运行,这便
意味着死锁发生了。自死琐是说自己占有了某个资源,然后自己又申请自己已占有的资源,显然不可能再获得该资源,因此就自缚手脚了。

spinlock特性:

防止多处理器并发访问临界区,

1、非睡眠(该进程/LWP(Light Weight Process)始终处于Running的状态)

2、忙等 (cpu一直检测锁是否已经被其他cpu释放)

3、短期(低开销)加锁

4、适合中断上下文锁定

5、多cpu的机器才有意义(需要等待其他cpu释放锁)

以下截取自

http://blog.sina.com.cn/s/blog_458d6ed5010110hv.html

Spinlock的目的是用来同步SMP中会被多个CPU同时存取的变量。在Linux中,普通的spinlock由于不带额外的语义,是用起来反而要非 常小心。 在Linux kernel中执行的代码大体分normal和interrupt context两种。tasklet/softirq可以归为normal因为他们可以进入等待

Spinlock的目的是用来同步SMP中会被多个CPU同时存取的变量。在Linux中,普通的spinlock由于不带额外的语义,是用起来反而要非常小心。

在Linux kernel中执行的代码大体分normal和interrupt context两种。tasklet/softirq可以归为normal因为他们可以进入等待;nested interrupt是interrupt context的一种特殊情况,当然也是interrupt context。Normal级别可以被interrupt抢断,interrupt会被另一个interrupt抢断,但不会被normal中断。各个 interrupt之间没有优先级关系,只要有可能,每个interrupt都会被其他interrupt中断。

我们先考虑单CPU的情况。在这样情况下,不管在什么执行级别,我们只要简单地把CPU的中断关掉就可以达到独占处理的目的。从这个角度来说,spinlock的实现简单地令人乍舌:cli/sti。只要这样,我们就关闭了preemption带来的复杂之门。

单CPU的情况很简单,多CPU就不那么简单了。单纯地关掉当前CPU的中断并不会给我们带来好运。当我们的代码存取一个shared variable时,另一颗CPU随时会把数据改得面目全非。我们需要有手段通知它(或它们,你知道我的意思)——spinlock正为此设。这个例子是 我们的第一次尝试:

extern spinlock_t lock;  
// ...  
spin_lock(&lock);  
// do something  
spin_unlock(&lock);  

他能正常工作吗?答案是有可能。在某些情况下,这段代码可以正常工作,但想一想会不会发生这样的事:

// in normal run level  
extern spinlock_t lock;  
// ...  
spin_lock(&lock);  
// do something  
// interrupted by IRQ ...  

// in IRQ  
extern spinlock_t lock;  
spin_lock(&lock); 

喔,我们在normal级别下获得了一个spinlock,正当我们想做什么的时候,我们被interrupt打断了,CPU转而执行interrupt level的代码,它也想获得这个lock,于是“死锁”发生了!解决方法很简单,看看我们第二次尝试:

extern spinlock_t lock;  
// ...  
cli; // disable interrupt on current CPU  
spin_lock(&lock);  
// do something  
spin_unlock(&lock);  
sti; // enable interrupt on current CPU  

在获得spinlock之前,我们先把当前CPU的中断禁止掉,然后获得一个lock;在释放lock之后再把中断打开。这样,我们就防止了死锁。事实上,Linux提供了一个更为快捷的方式来实现这个功能:

extern spinlock_t lock;  
// ...  
spin_lock_irq(&lock);  
// do something  
spin_unlock_irq(&lock);  

如果没有nested interrupt,所有这一切都很好。加上nested interrupt,我们再来看看这个例子:

// code 1  
extern spinlock_t lock;  
// ...  
spin_lock_irq(&lock);  
// do something  
spin_unlock_irq(&lock);  


// code 2  
extern spinlock_t lock;  
// ...  
spin_lock_irq(&lock);  
// do something  
spin_unlock_irq(&lock);  

Code 1和code 2都运行在interrupt context下,由于中断可以嵌套执行,我们很容易就可以想到这样的运行次序:

Code 1  
extern spinlock_t lock;  
// ...  
spin_lock_irq(&lock);      

Code 2  
extern spinlock_t lock;  
// ...  
spin_lock_irq(&lock);  
// do something  
spin_unlock_irq(&lock);  

Code 1  
// do something  
spin_unlock_irq(&lock); 

问题是在第一个spin_unlock_irq后这个CPU的中断已经被打开,“死锁”的问题又会回到我们身边!

解决方法是我们在每次关闭中断前纪录当前中断的状态,然后恢复它而不是直接把中断打开。

unsigned long flags;  
local_irq_save(flags);  
spin_lock(&lock);  
// do something  
spin_unlock(&lock);  
local_irq_restore(flags);  

Linux同样提供了更为简便的方式:

unsigned long flags;  
spin_lock_irqsave(&lock, flags);  
// do something  
spin_unlock_irqrestore(&lock, flags);  

小结

优化方法:

1、假设我们的QUERY进程要查询多个分区(指很多个分区),那么建议把分区的粒度降低,尽量让QUERY减少真正被访问的分区数,从而减少LWLockAcquire次数。

2、如果我们的分区很多,但是通过QUERY的WHERE条件过滤后实际被访问的分区不多,那么分区表的选择就非常重要。(目前尽量不要使用NATIVE分区)。尽量使用PPAS的edb_enable_pruning。对于PostgreSQL社区版本用户,在社区优化这部分代码前,请尽量使用pg_pathman分区功能。

扫描二维码获取更多消息:

图片描述

PostgreSQL常用SQL优化技巧 PostgreSQL的SQL优化技巧其实和大多数使用CBO优化器的数据库类似,因此一些常用的SQL优化改写技巧在PostgreSQL也是能够使用的。当然也会有一些不同的地方,今天我们来看看一些在PostgreSQL常用的SQL优化改写技巧。 1、标量子查询filter 当一个查询在select和from之间,那么这种子查询就是标量子查询。实际应用中,很多人在写SQL时为了方便会写一堆标量子查询的SQL,在表数据不大时,一般并不会有什么影响,但当数据量较大时,往往会对性能造成巨大影响。 因为标量子查询类似于 阅读详情

相关推荐

PostgreSQL 性能优化实战:从 SQL 到架构的全维度指南

PostgreSQL 优化是一项系统性工程,需从 SQL 编写规范、索引设计、执行计划调优、架构扩展等多维度协同推进。通过实际案例中的性能对比可见,每一处细节优化均可能带来数量级的性能提升。建议建立常态化的慢查询监控机制,结合业务特性定制优化方案,确保数据库在不同负载下保持高效稳定运行。

ITPUBD的博客 945

PostgreSQL查询优化:让你的SQL查询更高效

在数据库应用中,查询是一项频繁的操作。查询优化旨在通过调整数据库查询语句和索引设计,提高查询性能,减少响应时间,从而使系统更加高效运行。对于特定的数据类型和查询需求,可以考虑创建自定义索引。这需要实现相应的操作符和查询支持。-- 示例:在自定义类型上创建GiST索引。

竹林幽深 2270

PostgreSQL 查询涉及分区表过多导致性能问题 - 性能诊断优化(大量BIND, spin lock, SLEEP进程)

摘要: 标签 PostgreSQL , 分区表 , bind , spin lock , 性能分析 , sleep 进程 , CPU空转 , cache 背景 实际上我写过很多文档,关于分区表优化: 《PostgreSQL 商用版本EPAS(阿里云ppas) - 分区表性能优化 (堪比pg_pathman)》 《PostgreSQL 传统 hash 分区方法和性能》 《PostgreSQL 10

阿里云云栖号 2853

PostgreSQL数据库信号量机制— PGSemaphore底层原理

PGSemaphoreTryLock

肥叔菌的博客 1323

PostgreSQL分区表性能损耗验证

分类: Mysql/postgreSQL 今年的PG用户大会,华为的许中清分享了《PostgreSQL分区实践》(当时我在另一个会场)。其中最后一页的测试结果表明,100分区的继承分区表查询更新删除比不分区的普通表慢了2个数量级。虽然PG的手册中也说: 主表的所有分区的所有约束在约束排除中被审查,所以大量分区将大大增加查询规划时间。 分区使用这些技术或许可以将分区

l284849736的专栏 2464

PostgreSQL源码学习笔记(4)-存储管理

文章目录引言(Introduction)存储管理器的体系结构存储管理器的主要任务读写元组过程外存管理元组在页面中的布局磁盘管理器VFD(Virtual File Descriptor)空闲空间映射表(Free Space Map)FSM page layoutFSM pagesAddressing可见性映射表(Visibility Mapping Table)TOAST(The Oversized-Attribute Storage Technique)Out-of-Line, On-Disk TOAST

爱敲键盘的吉他手 1015

postgresql 主表分区(数据量3千万)解决查询慢速度提升

上面替换自己表名即可,可以解决数据问题。2.创建分区表主表一致,并建索引。6.为主表创建触发器。

weixin_45003528的博客 1723

数据库技术核心:查询优化PostgreSQL

案例展示了通过合理使用索引、复合索引、部分索引、多表联接、窗口函数、查询计划分析、表分区、分片策略、热数据访问、缓存机制、物化视图和分布式数据库等技术,大幅优化复杂查询性能的过程。其主要涉及合理的表设计、索引的高效数据访问、减少子查询次数、分区裁剪机制,以及数据预计算等。

三余知行 2210

优化PostgreSQL数据库性能的最佳实践:从查询优化、索引调整到服务器配置

本文介绍了针对PostgreSQL数据库的优化技巧,从SQL查询优化、索引调整、服务器硬件配置等方面进行探讨,帮助读者了解如何最大化PostgreSQL数据库的性能和可靠性。其中包括了如何分析查询计划、优化索引、调整缓存和内存配置、使用分区表、负载均衡和高可用方案等技术。通过实际应用案例和实验结果,本文提供了一些可行的方案和经验教训,帮助读者解决PostgreSQL数据库性能问题和瓶颈。

qq_28245087的博客 7417

进阶数据库系列(二十三):PostgreSQL 性能优化

点击下方名片,设为星标!回复“1024”获取2TB学习资源!前面介绍了PostgreSQL基于 Patroni 高可用架构部署及故障切换、基于 repmgr 高可用架构实践、基于 pgpool 实现读写分离实践、数据库备份恢复、主从数据目录同步工具 pg_rewind、数据库作业调度工具等相关的知识点,今天我将详细的为大家介绍 PostgreSQL 性能优化相关知识,希望大家能够从中收获多多...

民工哥的博客 2913

PostgreSQL 11 分区表用法及增强

作者:瀚高软件 杨杰 发布:2018-06-15 欢迎大家踊跃投稿,投稿信箱:press@postgres.cn 简介 PostgreSQL 10 是第一个支持内置声明式分区表的版本。支持 range、list 分区以前的版本相比,提供了显著的性能和易用性优势,但却忽略了许多功能特性和性能优化PostgreSQL 11 为分区表功能提供更多的改进。这些特性包括:hash 分区、索引...

rocklee的专栏 6880

PostgreSQL查询优化--逻辑查询优化--视图优化(一)

8.1.1 视图重写 PostgreSQL有一个模块,称为规则模块,用以处理规则。规则系统把查询修改为需要考虑规则的形式,然后把修改过的查询传递给查询优化器执行。视图被作为规则的子部分,在此被处理。所以,PostgreSQL通过规则模块(pg_rewrite_query函数)支持逻辑查询优化的视图重写,也就是把视图用视图的定义替代,视图定义在SQL中相当于子查询PostgreSQL统一对子

dongdong_java的专栏 2344

Postgresql杂谈 16—Postgresql中的锁机制

今天,我们学习下Postgresql中的锁机制。锁是数据库事务的基础,通过锁才能保证数据库在并发时能够保证数据的安全和一致,才能够达到事务的一致性和隔离性。但是任何事物都有它的两面性,引入锁同样会增加性能开销和可能的死锁问题,不过好在都存在解决方法。Postgresql和其它数据库一样,从锁作用的对象上来说可以分为表锁、行锁两种,从排他性来说可以大体分为共享锁和排他锁,但是由于Postgresql是通过多版本的方式对数据库进行更新,它也引入了Access锁。接下来我们就对这些内容分类进行学习。...

Hyman的博客 8126

postgreSQL堆内元组

堆内元组

yunmu666的博客 255

postgresql 一次插入多条记录_PostgreSQL表膨胀的前世今生

当你的数据库快速增长的时候,一定需要注意一件事,那就是“表膨胀”。内置的方法是使用VACUUM或者VACUUMFULL来解决表膨胀问题,但是有一些缺点。[一、什么是表膨胀]PostgreSQL使用多版本模型MVCC。实现的方法和Oracle和MySQL不同,当执行update或者是delete的时,Oracle和MySQL会在undo中维护前镜像,用于实现数据库的一致性(C)。例如一个旧...

weixin_39734399的博客 607

PostgreSQL的元组、页面结构及索引查找原理

我们知道postgresql数据库通过数据多版本实现mvcc,pg又没有undo段,老版本的数据元组直接存放在数据页面中,这样带来的问题就是旧元组需要不断地进行清理以释放空间,这也是数据库膨胀的根本原因。本文简单介绍一下postgresql数据库的元组、页面的结构以及索引查找流程。 元组结构 元组,也叫tuple,这个叫法是很学术的叫法,但是现在数据库中一般叫行或者记录。下面是元组的结构: typ...

数据库架构之美 2843

sql删除元组_PostgreSQL的元组、页面结构及索引查找原理

我们知道postgresql数据库通过数据多版本实现mvcc,pg又没有undo段,老版本的数据元组直接存放在数据页面中,这样带来的问题就是旧元组需要不断地进行清理以释放空间,这也是数据库膨胀的根本原因。本文简单介绍一下postgresql数据库的元组、页面的结构以及索引查找流程。元组结构元组,也叫tuple,这个叫法是很学术的叫法,但是现在数据库中一般叫行或者记录。下面是元组的结构:t...

weixin_39842271的博客 450
上一篇: PostgreSQL Oracle 兼容性之 - rownum
下一篇: PostgreSQL 多重含义数组检索与条件过滤 (标签1:属性, 标签n:属性) - 包括UPSERT操作如何修改数组、追加数组元素...
maoreyou
博客等级 码龄9年 149粉丝 409原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值