分页查询和分页查询的性能优化

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
简介: 分页查询和分页查询的性能优化

1.limit offset分页查询结果的不确定性

对于分页查询,类似sql语句select * from table [where ...] limit m,n;一般情况下执行结果是不稳定的,每次输出的n条记录不唯一不固定。例如如下sql:

select
lo_orderkey,lo_linenumber,lo_custkey,lo_partkey,lo_suppkey
from lineorder
where lo_linenumber = 1
limit 10000000,5;

连续查询三次,结果如下:

执行计划如下:

根据执行计划,所有节点下发QueryCountEstimate,根据count值选取一个或者多个节点返回n条记录,结果具有一定的随机性。

关闭limit优化参数(set _t_gcluster_limit_optimize=0)时,结果也是不稳定的。执行计划是所有节点选取m+n条记录汇总,然后选取offset m的limit n条,汇总时也有随机性,顺序也

不固定。多次执行结果如下:

参数_t_gcluster_limit_optimize关闭时的执行计划:

所以,不管优化参数打开还是关闭,分页结果都是不稳定的。

2.limit offset分页查询结果的唯一性优化
最近在产品上做了一个优化功能,使得分页数据稳定唯一。新增如下两个参数:

| gcluster_flip_pages | OFF |
| gcluster_force_node_serial_for_flip_pages | OFF |

两个参数默认都是关闭的。在打开参数情况下,分页结果集稳定。

优化的原理是对每个表按照分片号和行号内部固定了一个查询物化的顺序,以保持每次的查询顺序固定。测试结果如下:

3.order by limit offset分页查询性能不稳定问题
除了使用上面产品上的优化以外,想要保持分页结果集固定还可以增加一些排序字段之后,再取offset limit。

对于排序后再分页的sql语句,例如select * from table [where ...] order by col limit m,n;这类sql上面的分页固定优化参数就无效了。

多次测试执行加了排序列后的分页sql,执行结果固定。

在执行limit 10000000,5时,结果集:

在执行limit 10000,5时,结果集:

我们发现,加了order by之后分页结果固定了,但又发现了一个性能问题。在limit n值固定,offset越大时,执行越慢,也就是在分页每页的记录条数固定时,翻页越往后越慢。

4.order by limit offset分页查询性能不稳定原因分析

我们尝试分析一下上面性能不稳定的原因,并尝试分析看能否加以优化。

对于这类排序分页sql,执行计划如下:

上面这个执行计划是在参数gcluster_order_by_limit_offset_optimize默认关闭的情况下获取的。执行计划显示:

第一步,所有节点将等值查询过滤后的结果进行节点内排序;

第二步,取节点内排序后结果集的limit m+n条汇总到一个节点;

第三步,对汇总后的结果集再次排序取offset m limit n条。

在打开参数gcluster_order_by_limit_offset_optimize后,对于offset大的分页,也并没有变快。

参数gcluster_order_by_limit_offset_optimize打开的执行计划,看起来更复杂了一些,而且参数默认关闭,所以可能参数优化的适用场景有一些限制。

我们还是先从分析参数关闭时的执行计划入手。

如上执行计划显示:

每个节点执行where条件过滤之后进行局部排序,再取limit 10000000+5条做汇总。汇总到一个节点临时表里面排序,取limit 10000000,5。可以看到,执行计划里面涉及到两次排序,一般情况下排序是比较耗时的。

先看第一次排序。在limit值n固定,offset值m变大时,局部排序的数据量始终不变,排序后物化的数据条数m+n变大,排序后的物化dc相对集中,物化列数不是特别多的情况下,这一步性能相对稳定。

再看第二次排序。需要全局排序的数据条数是(m+n)*nodes数。在limit值n固定,offset值m变大时,需要全局排序的数据量增大,节点数越多越明显。

5.order by limit offset分页查询性能不稳定优化

排序分页sql里面涉及局部排序和全局排序两次排序,我们的优化思路就是看能不能减少排序次数,只做一次排序。优化思路:

(1)不做各节点的局部排序,那么各节点汇总的需要是过滤后的全量数据,类似于使用了复制表。

(2)不做汇总后的全局排序,即不做汇总,那就需要结果集只分布在一个节点上,可以走单节点hash优化。

具体优化方案示例如下。

原始sql语句:
select
lo_orderkey,lo_linenumber,lo_custkey,lo_partkey,lo_suppkey
from lineorder
where lo_linenumber = 1
order by lo_orderkey,lo_linenumber
limit 10000000,5;

建表语句:
CREATE TABLE "lineorder" (
"lo_orderkey" bigint(20) DEFAULT NULL,
"lo_linenumber" int(11) DEFAULT NULL,
"lo_custkey" int(11) DEFAULT NULL,
"lo_partkey" int(11) DEFAULT NULL,
"lo_suppkey" int(11) DEFAULT NULL,
"lo_orderdate" int(11) DEFAULT NULL,
"lo_orderpriority" varchar(15) DEFAULT NULL COMMENT 'lookup',
"lo_shippriority" varchar(1) DEFAULT NULL COMMENT 'lookup',
"lo_quantity" int(11) DEFAULT NULL,
"lo_extendedprice" int(11) DEFAULT NULL,
"lo_ordtotalprice" int(11) DEFAULT NULL,
"lo_discount" int(11) DEFAULT NULL,
"lo_revenue" int(11) DEFAULT NULL,
"lo_supplycost" int(11) DEFAULT NULL,
"lo_tax" int(11) DEFAULT NULL,
"lo_commitdate" int(11) DEFAULT NULL,
"lo_shipmode" varchar(10) DEFAULT NULL COMMENT 'lookup'
) ENGINE=EXPRESS DISTRIBUTED BY('lo_orderkey') DEFAULT CHARSET=utf8 TABLESPACE='sys_tablespace';

数据统计:
gbase> select count() from lineorder;
+----------+
| count(
) |
+----------+
| 59986052 |
+----------+
1 row in set (Elapsed: 00:00:00.08)
gbase> select count(distinct lo_orderkey || lo_linenumber) from lineorder;
+----------------------------------------------+
| count(distinct lo_orderkey || lo_linenumber) |
+----------------------------------------------+
| 59986052 |
+----------------------------------------------+
1 row in set (Elapsed: 00:01:12.29)

gbase> select count() from lineorder where lo_linenumber=1;
+----------+
| count(
) |
+----------+
| 15000000 |
+----------+
1 row in set (Elapsed: 00:00:00.19)

(1)复制表排序方案。

过滤后的结果集插入到复制表1,将复制表1排序后的数据插入到复制表2,分页查询时直接单线程(多线程物化依然可能导致结果不稳定)查询复制表2的对应offset limit值。sql操作

示例如下:

gccli ssbm -vvv -e"drop table if exists lineorder_rep;create table lineorder_rep replicated as select * from lineorder where lo_linenumber = 1;"
-- Query OK, 15000000 rows affected (Elapsed: 00:00:19.51)

gccli ssbm -vvv -e"drop table if exists lineorder_rep_order;create table lineorder_rep_order replicated as select * from lineorder_rep order by lo_orderkey,lo_linenumber;"
-- Query OK, 15000000 rows affected (Elapsed: 00:01:10.66)

gccli ssbm -vvv -e"set gbase_parallel_degree=1;select lo_orderkey,lo_linenumber,lo_custkey,lo_partkey,lo_suppkey from lineorder_rep_order limit 10000000,5;"
-- 5 rows in set (Elapsed: 00:00:00.49)

(2)hash分布表单节点排序方案。

过滤后的结果集插入到以等值过滤列为hash分布列的hash分布表1,将hash分布表1排序后的数据插入到相同表结构的hash分布表2(此步骤需要使用sql直接下发的hint),分页查询

时直接单线程(多线程物化依然可能导致结果不稳定)查询hash分布表2的对应offset limit值。Sql操作示例如下:

gccli ssbm -vvv -e"drop table if exists lineorder_hash;create table lineorder_hash distributed by ('lo_linenumber') as select * from lineorder where lo_linenumber = 1;"
-- Query OK, 15000000 rows affected (Elapsed: 00:00:18.01)

gccli ssbm -vvv -c -e"drop table if exists lineorder_hash_order;create table lineorder_hash_order distributed by ('lo_linenumber') as select /+distribute_exec/ * from lineorder_hash order by lo_orderkey,lo_linenumber;"
-- Query OK, 15000000 rows affected (Elapsed: 00:01:14.41)

gccli ssbm -vvv -e"set gbase_parallel_degree=1;select lo_orderkey,lo_linenumber,lo_custkey,lo_partkey,lo_suppkey
from lineorder_hash_order
limit 10000000,5;"
-- 5 rows in set (Elapsed: 00:00:00.46)

经过上面优化方案的处理,虽然在生成中间排序临时表时多了一些耗时,但是因为临时表是可以复用的,所以整体上达到了分页数据固定且分页性能稳定的目的。

相关文章
|
架构师 NoSQL Java
【案例实战】SpringBoot3.x自定义封装starter实战
【案例实战】SpringBoot3.x自定义封装starter实战
【案例实战】SpringBoot3.x自定义封装starter实战
|
Kubernetes 前端开发 Cloud Native
混动工程平台 ChaosBlade-Box 新版重磅发布 | 学习笔记
快速学习混动工程平台 ChaosBlade-Box 新版重磅发布
混动工程平台 ChaosBlade-Box 新版重磅发布 | 学习笔记
|
存储 缓存 关系型数据库
⑩⑧【MySQL】InnoDB架构、事务原理、MVCC多版本并发控制
⑩⑧【MySQL】InnoDB架构、事务原理、MVCC多版本并发控制
449 0
|
SQL 缓存 关系型数据库
如何优化分页查询的性能?
【8月更文挑战第3天】如何优化分页查询的性能?
802 37
|
10月前
|
网络架构
CondaHTTPError HTTP 000 CONNECTION FAILED错误解决方案
以上就是解决"CondaHTTPError: HTTP 000 CONNECTION FAILED"错误的一些方法。希望这些方法能够帮助你解决问题。如果以上方法都无法解决你的问题,你可能需要寻求专业的技术支持。
1298 23
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
2246 25
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
存储 SQL 关系型数据库
深入探究InnoDB的MVCC快照机制
本文详细介绍了InnoDB存储引擎中的两种并发控制方法:MVCC(多版本并发控制)和LBCC(基于锁的并发控制)。MVCC通过记录版本信息和使用快照读取机制,实现了高并发下的读写操作,而LBCC则通过加锁机制控制并发访问。文章深入探讨了MVCC的工作原理,包括插入、删除、修改流程及查询过程中的快照读取机制。通过多个案例演示了不同隔离级别下MVCC的具体表现,并解释了事务ID的分配和管理方式。最后,对比了四种隔离级别的性能特点,帮助读者理解如何根据具体需求选择合适的隔离级别以优化数据库性能。
734 5
深入探究InnoDB的MVCC快照机制
|
JSON 安全 算法
Spring Boot 应用如何实现 JWT 认证?
Spring Boot 应用如何实现 JWT 认证?
1408 8
|
安全 小程序 Java
Java“AccessControlException”报错解决
Java中的“AccessControlException”通常发生在尝试访问受安全策略限制的资源时。解决方法包括:1. 检查安全策略文件(java.policy)配置;2. 确保代码具有足够的权限;3. 调整JVM启动参数以放宽安全限制。
988 1
|
安全 算法 Java
强大!基于Spring Boot 3.3 六种策略识别上传文件类型
【10月更文挑战第1天】在Web开发中,文件上传是一个常见的功能需求。然而,如何确保上传的文件类型符合预期,防止恶意文件入侵,是开发者必须面对的挑战。本文将围绕“基于Spring Boot 3.3 六种策略识别上传文件类型”这一主题,分享一些工作学习中的技术干货,帮助大家提升文件上传的安全性和效率。
813 0