PostgreSQL、KingBase 数据库 ORDER BY LIMIT 查询缓慢案例
好久没写博客了,最近从人大金仓离职了,新公司入职了蚂蚁集团,正在全力学习 OcenaBase 数据库的体系结构中。
以后分享的案例知识基本上都是以 OcenaBase 分布式数据库为主了,呦西。

昨天帮朋友看了个金仓 KES数据库的 SQL 案例,废话不说,直接贴SQL:
慢SQL(执行时间 8s ,限制返回 30 行)
explain analyze
SELECT GI.ID,
GI.MODULE_ID,
GI.BT,
GI.WH,
GI.JJCD_TEXT,
GI.CREATE_DEPTNAME,
GI.CREATE_TIME,
GI.MODULE_NAME
FROM gifgifgif GI
INNER JOIN gufgufguf GUF ON (GUF.ifid = GI.ID)
WHERE GI.ROWSTATE > - 1
AND (GUF.usid = '0' OR GUF.usid = '210317100256if6gVcTb3Ado1o2ytLs')
AND ((GI.BT LIKE '%签%') OR (GI.MODULE_NAME LIKE '%签%') OR (GI.WH LIKE '%签%') OR (GI.JJCD_TEXT LIKE '%签%') OR
(GI.CREATE_DEPTNAME LIKE '%签%'))
ORDER BY GI.CREATE_TIME DESC LIMIT 30;
慢SQL执行计划
QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------
Limit (cost=1001.05..17578.06 rows=30 width=240) (actual time=6458.263..8763.733 rows=7 loops=1)
-> Gather Merge (cost=1001.05..3879467.79 rows=7019 width=240) (actual time=6458.261..8763.728 rows=7 loops=1)
Workers Planned: 4
Workers Launched: 4
-> Nested Loop (cost=0.99..3877631.71 rows=1755 width=240) (actual time=2843.144..8274.217 rows=1 loops=5)
-> Parallel Index Scan Backward using gifgifgif_CREATE_TIME1 on gifgifgif GI (cost=0.43..1158925.09 rows=433728 width=240) (actual time=0.043..2159.037 rows=350466 loops=5)
Filter: ((ROWSTATE > '-1'::numeric) AND (((BT)::text ~~ '%签%'::text) OR ((MODULE_NAME)::text ~~ '%签%'::text) OR ((WH)::text ~~ '%签%'::text) OR ((JJCD_TEXT)::text ~~ '%签%'::text) OR ((CREATE_DEPTNAME)::text ~~ '%
%'::text)))
Rows Removed by Filter: 423271
-> Index Only Scan using idx_gufgufguf_1_2_3 on gufgufguf GUF (cost=0.56..6.26 rows=1 width=32) (actual time=0.017..0.017 rows=0 loops=1752329) -- 慢:(1752329/5) * 0.017 / 1000 = 5.95s
Index Cond: (ifid = (GI.ID)::text)
Filter: (((usid)::text = '0'::text) OR ((usid)::text = '210317100256if6gVcTb3Ado1o2ytLs'::text))
Rows Removed by Filter: 3
Heap Fetches: 0
Planning Time: 0.832 ms
Execution Time: 8763.803 ms
(15 行记录)
我看到这计划简直无语,这种SQL不能 300 ms出来就绝对有问题,而且这么简单的语句都能用上并行,真的服了。
Index Only Scan using idx_gufgufguf_1_2_3 on gufgufguf GUF 每个并行进程执行 5.95s 这也太拉跨了。
看执行计划基本都是用 Index Scan 或者是 Index Only Scan,但是本SQL 谓词过滤条件很多 or ,其实优化器如果执行位图扫描才是最优解计划,但是CBO偏偏没执行!!!
SQL去掉 LIMIT 30限制条件:
explain analyze
SELECT GI.ID,
GI.MODULE_ID,
GI.BT,
GI.WH,
GI.JJCD_TEXT,
GI.CREATE_DEPTNAME,
GI.CREATE_TIME,
GI.MODULE_NAME
FROM gifgifgif GI
INNER JOIN gufgufguf GUF ON (GUF.ifid = GI.ID)
WHERE GI.ROWSTATE > - 1
AND (GUF.usid = '0' OR GUF.usid = '210317100256if6gVcTb3Ado1o2ytLs')
AND ((GI.BT LIKE '%签%') OR (GI.MODULE_NAME LIKE '%签%') OR (GI.WH LIKE '%签%') OR (GI.JJCD_TEXT LIKE '%签%') OR
(GI.CREATE_DEPTNAME LIKE '%签%'))
ORDER BY GI.CREATE_TIME DESC ;
去掉 LIMIT 30限制条件SQL执行计划:
QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------
Gather Merge (cost=98222.89..99026.61 rows=6792 width=240) (actual time=33.640..35.974 rows=7 loops=1)
Workers Planned: 3
Workers Launched: 3
-> Sort (cost=97222.85..97228.51 rows=2264 width=240) (actual time=26.724..26.725 rows=2 loops=4)
Sort Key: GI.CREATE_TIME DESC
Sort Method: quicksort Memory: 25kB
Worker 0: Sort Method: quicksort Memory: 25kB
Worker 1: Sort Method: quicksort Memory: 25kB
Worker 2: Sort Method: quicksort Memory: 26kB
-> Nested Loop (cost=510.90..97096.70 rows=2264 width=240) (actual time=11.118..26.693 rows=2 loops=4)
-> Parallel Bitmap Heap Scan on gufgufguf GUF (cost=510.35..59045.81 rows=5049 width=32) (actual time=0.480..3.498 rows=1178 loops=4)
Recheck Cond: (((usid)::text = '0'::text) OR ((usid)::text = '210317100256if6gVcTb3Ado1o2ytLs'::text))
Heap Blocks: exact=1464
-> BitmapOr (cost=510.35..510.35 rows=15652 width=0) (actual time=1.567..1.568 rows=0 loops=1)
-> Bitmap Index Scan on gufgufguf_usid (cost=0.00..251.26 rows=7826 width=0) (actual time=0.022..0.022 rows=0 loops=1)
Index Cond: ((usid)::text = '0'::text)
-> Bitmap Index Scan on gufgufguf_usid (cost=0.00..251.26 rows=7826 width=0) (actual time=1.545..1.545 rows=4713 loops=1)
Index Cond: ((usid)::text = '210317100256if6gVcTb3Ado1o2ytLs'::text)
-> Index Scan using gifgifgif_PKEY1 on gifgifgif GI (cost=0.56..7.54 rows=1 width=240) (actual time=0.019..0.019 rows=0 loops=4713)
Index Cond: ((ID)::text = (GUF.ifid)::text)
Filter: ((ROWSTATE > '-1'::numeric) AND (((BT)::text ~~ '%签%'::text) OR ((MODULE_NAME)::text ~~ '%签%'::text) OR ((WH)::text ~~ '%签%'::text) OR ((JJCD_TEXT)::text ~~ '%签%'::text) OR ((CREATE_DEPTNAME)::text ~~ '%
%'::text)))
Rows Removed by Filter: 1
Planning Time: 0.815 ms
Execution Time: 36.060 ms
(24 行记录)
可以看到去掉LIMIT 30 以后,CBO能正常使用上 Bitmap Index Scan + BitmapOr 的查询策略,SQL只需要 36ms就能跑出结果。
PG比较牛逼的地方是B+树索引能基于SQL的查询条件,自动能转换成位图索引的查询策略。
像这种情况就简单了,只需要改变下限制SQL返回条数的逻辑即可,kingbase也兼容Oracle rownum 的语法,我们可以将上面SQL等价改成 rownum来进行现在。
LIMIT 改写成 rownum :
explain analyze
SELECT * FROM (
SELECT GI.ID,
GI.MODULE_ID,
GI.BT,
GI.WH,
GI.JJCD_TEXT,
GI.CREATE_DEPTNAME,
GI.CREATE_TIME,
GI.MODULE_NAME
FROM gifgifgif GI
INNER JOIN gufgufguf GUF ON (GUF.ifid = GI.ID)
WHERE GI.ROWSTATE > - 1
AND (GUF.usid = '0' OR GUF.usid = '210317100256if6gVcTb3Ado1o2ytLs')
AND ((GI.BT LIKE '%签%') OR (GI.MODULE_NAME LIKE '%签%') OR (GI.WH LIKE '%签%') OR (GI.JJCD_TEXT LIKE '%签%') OR
(GI.CREATE_DEPTNAME LIKE '%签%'))
ORDER BY GI.CREATE_TIME DESC) WHERE ROWNUM <= 30;
LIMIT 改写成 rownum 执行计划:
QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------
Count (cost=98222.89..99162.45 rows=0 width=240) (actual time=31.418..33.691 rows=7 loops=1)
Stop Keys: (ROWNUM <= 30)
-> Gather Merge (cost=98222.89..99026.61 rows=6792 width=240) (actual time=31.415..33.686 rows=7 loops=1)
Workers Planned: 3
Workers Launched: 3
-> Sort (cost=97222.85..97228.51 rows=2264 width=240) (actual time=26.497..26.498 rows=2 loops=4)
Sort Key: GI.CREATE_TIME DESC
Sort Method: quicksort Memory: 25kB
Worker 0: Sort Method: quicksort Memory: 25kB
Worker 1: Sort Method: quicksort Memory: 27kB
Worker 2: Sort Method: quicksort Memory: 25kB
-> Nested Loop (cost=510.90..97096.70 rows=2264 width=240) (actual time=14.246..26.465 rows=2 loops=4)
-> Parallel Bitmap Heap Scan on gufgufguf GUF (cost=510.35..59045.81 rows=5049 width=32) (actual time=0.513..3.401 rows=1178 loops=4)
Recheck Cond: (((usid)::text = '0'::text) OR ((usid)::text = '210317100256if6gVcTb3Ado1o2ytLs'::text))
Heap Blocks: exact=1373
-> BitmapOr (cost=510.35..510.35 rows=15652 width=0) (actual time=1.664..1.664 rows=0 loops=1)
-> Bitmap Index Scan on gufgufguf_usid (cost=0.00..251.26 rows=7826 width=0) (actual time=0.024..0.024 rows=0 loops=1)
Index Cond: ((usid)::text = '0'::text)
-> Bitmap Index Scan on gufgufguf_usid (cost=0.00..251.26 rows=7826 width=0) (actual time=1.639..1.639 rows=4713 loops=1)
Index Cond: ((usid)::text = '210317100256if6gVcTb3Ado1o2ytLs'::text)
-> Index Scan using gifgifgif_PKEY1 on gifgifgif GI (cost=0.56..7.54 rows=1 width=240) (actual time=0.019..0.019 rows=0 loops=4713)
Index Cond: ((ID)::text = (GUF.ifid)::text)
Filter: ((ROWSTATE > '-1'::numeric) AND (((BT)::text ~~ '%签%'::text) OR ((MODULE_NAME)::text ~~ '%签%'::text) OR ((WH)::text ~~ '%签%'::text) OR ((JJCD_TEXT)::text ~~ '%签%'::text) OR ((CREATE_DEPTNAME)::text
~~ '%签%'::text)))
Rows Removed by Filter: 1
Planning Time: 0.897 ms
Execution Time: 33.778 ms
(26 行记录)
可以看到SQL通过将LIMIT 改写成 rownum 以后,原来执行时间 8s 降低到 33ms 就能跑出结果了,本条SQL到此已经优化完毕。
最后问题:那为什么原SQL使用 limit 会慢?改成 rownum 后速度能秒出,通常情况下来说 limit 是PG提供原生的语法,性能应该更好才是?
解答:是因为在PostgreSQL中,LIMIT子句本身不直接与索引类型相关联,而是用于指定返回的记录数。然而,当LIMIT与ORDER BY结合使用时,PostgreSQL的查询优化器可能会利用B+树索引来加速查询。
这是因为B+树索引能够有效地支持有序数据的检索,使得数据库能够快速地定位到需要的记录而不必扫描整个表或索引。
然而需要通过索引进行排序的话,必然要通过 Index Scan 或者 Index Only Scan 扫描才可以对数据进行升序或者降序排序,而位图索引是不支持对数据进行排序功能的。
所以为什么一开始SQL会使用 Index Scan 和 Index Only Scan 而不使用 Bitmap Index Scan + BitmapOr 的查询策略。
各位读者以后在kingbase数据库进行业务开发,如果需要谓词过滤条件中有 or 排序限制条件中有 order by + limit 的需求,尽量对业务SQL进行评估,从而选择使用 rownum 还是 limit 语句来进行限制数据。
如果在postgresql 进行开发的话遇到这种需求(pg不支持rownum写法),还需要在外面再包一层查询,使用 row_number() over() 窗口函数来进行限制。
PostgreSQL、KingBase 数据库 ORDER BY LIMIT 查询缓慢案例的更多相关文章
- Oracle数据库order by排序查询分页比不分页还慢问题解决办法
简单说下问题,有一个JDBC的查询SQL,分页查询语句中有一个排序order by create_time,理论上来说JDBC查询已经是比较底层的技术了,没有像Hibernate.MyBatis那样又 ...
- 第二百八十八节,MySQL数据库-索引、limit分页、执行计划、慢日志查询
MySQL数据库-索引.limit分页.执行计划.慢日志查询 索引,是数据库中专门用于帮助用户快速查询数据的一种数据结构.类似于字典中的目录,查找字典内容时可以根据目录查找到数据的存放位置,然后直接获 ...
- MySql学习(二) —— where / having / group by / order by / limit 简单查询
注:该MySql系列博客仅为个人学习笔记. 这篇博客主要记录sql的五种子句查询语法! 一个重要的概念:将字段当做变量看,无论是条件,还是函数,或者查出来的字段. select五种子句 where 条 ...
- where / having / group by / order by / limit 简单查询
目录 1.基础查询 -- where 2. group by 与 统计函数 3. having 4.where + group by + having + 函数 综合查询 5. order by + ...
- .NET Core ORM 类库Petapoco中对分页Page添加Order By对查询的影响
最近一直在使用Petapoco+Entity Framework Core结合开发一套系统. 使用EFCore进行Code First编码,使用PMC命令生成数据库表的信息. 使用Petapoco进行 ...
- MySQL数据库之单双表查询
单表查询 先创建表 #创建表 create table employee( id int not null unique auto_increment, name varchar(20) not nu ...
- mysql数据库补充知识2 查询数据库记录信息之单表查询
一 单表查询的语法 SELECT 字段1,字段2... FROM 表名 WHERE 条件 GROUP BY field HAVING 筛选 ORDER BY field LIMIT 限制条数 二 关键 ...
- postgresql查看数据库占用的物理存储空间大小
1.手动查看: 查看数据库postgres的oid postgres=# SELECT oid from pg_database where datname='postgres'; oid------ ...
- MySQL数据库之单表查询中关键字的执行顺序
目录 MySQL数据库之单表查询中关键字的执行顺序 1 语法顺序 2 执行顺序 3 关键字使用语法 MySQL数据库之单表查询中关键字的执行顺序 1 语法顺序 select distinct from ...
- MySQL/MariaDB数据库的多表查询操作
MySQL/MariaDB数据库的多表查询操作 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.单表查询小试牛刀 [root@node105.yinzhengjie.org.cn ...
随机推荐
- 日志框架简介-Slf4j+Logback入门实践 | 京东云技术团队
前言 随着互联网和大数据的迅猛发展,分布式日志系统和日志分析系统已广泛应用,几乎所有应用程序都使用各种日志框架记录程序运行信息.因此,作为工程师,了解主流的日志记录框架非常重要.虽然应用程序的运行结果 ...
- 收藏-即时通讯(IM)开源项目OpenIM-功能手册
OpenIM简介 OpenIM是由IM技术专家打造的开源即时通讯组件,也是目前最受欢迎的开源IM项目之一,目前github star近万.开发者通过集成OpenIM组件,并私有化部署服务端,可以将即时 ...
- #region在多种编程语言及IDE中进行代码折叠,包括python msvc++ c#等
vs/rider中折叠C#代码 在写C#的时候,在visual studio中可以使用#region和#endregion来进行代码折叠,那么在pycharm中是否可以呢? //这里有很多的代码... ...
- 虚拟IP绑定公网IP访问
绑定公网 IP 我们目前的虚拟 IP,还不能通过公网的形式进行访问,我们首先,来使用内部的 IP 进行访问看看效果如下: curl 虚拟IP 如上图我访问了两次,第一次访问返回的是 2222 的 ng ...
- FMEA:总监和架构师都在用的高可用架构分析方法
FMEA:总监和架构师都在用的高可用架构分析方法 记得之前准备春晚项目的时候,团队成员在一起过架构,老板最常问的问题是"这个组件挂了怎么办?有什么影响?",我当时还在心里默默嘀咕: ...
- 强化学习从基础到进阶--案例与实践[7.1]:深度确定性策略梯度DDPG算法、双延迟深度确定性策略梯度TD3算法详解项目实战
强化学习从基础到进阶--案例与实践[7.1]:深度确定性策略梯度DDPG算法.双延迟深度确定性策略梯度TD3算法详解项目实战 1.定义算法 1.1 定义模型 !pip uninstall -y par ...
- C++ Boost库 操作字符串与正则
字符串的查找与替换一直是C++的若是,运用Boost这个准标准库,将可以很好的弥补C++的不足,使针对字符串的操作更加容易. 字符串格式转换: #include <iostream> #i ...
- LeetCode刷题日记 2020/03/25
力扣刷题继续! 题目:计算三维形体表面积 题干 在 N * N 的网格上,我们放置一些 1 * 1 * 1 的立方体. 每个值 v = grid[i][j] 表示 v 个正方体叠放在对应单元格 (i ...
- ROS节点通信(二)service和client
目录 1.说明 2.创建功能包 3.自定义通信数据类型 4.编写代码 5.编译配置 5.1.CMakeLists.txt 5.2.package.xml 6.编译运行 1.说明 ROS的节点通信模式有 ...
- 飞桨paddlespeech语音唤醒推理C INT8 定点实现
前面的文章(飞桨paddlespeech语音唤醒推理C定点实现)讲了INT16的定点实现.因为目前商用的语音唤醒方案推理几乎都是INT8的定点实现,于是我又做了INT8的定点实现. 实现前做了一番调研 ...