首页 > 数据库 >SQL优化——深分页&排序

SQL优化——深分页&排序

时间:2025-01-05 23:57:36浏览次数:1  
标签:code 分页 索引 item SQL 排序 type id

问题背景

在开发 Web 应用或处理数据库查询时,分页是一项常见需求。然而,当面对深度分页(即页码较大,偏移量较高的分页情况)时,性能问题往往接踵而至。比如对一些需要拉特定的页面查询、范围导出、范围计算等业务需求,都会涉及大量的深分页查询的SQL,不当的SQL会导致执行超时,页面响应显著上升等问题。本文重点讨论Mysql下以Innodb为存储引擎时,深分页造成性能恶化的根因以及一般解决方案。

为什么Limit在offset很大时性能会变差?

假设当前存在一张item表,单表有千万级数据,其核心字段有id, item_code, item_name, type, sub_type, biz_type, status等,并且在type, sub_type, biz_type三个字段上存在索引, id上存在默认的主键索引

当执行SQL

-- SQL1  -> 3ms; 如果把候选列设置为* -> 4ms
Select id, item_code, type, sub_type 
From item
where type = 'normal'
order by id ASC
limit 0, 20;

-- SQL2 2300ms; 如果把候选列设置为* -> 3700ms
Select id, item_code, type, sub_type 
From item
where type = 'normal'
order by id ASC
limit 1000000, 20;

-- SQL3 500ms; 只查找主键和覆盖索引上的东西
Select id, type 
From item
where type = 'normal'
order by id ASC
limit 1000000, 20;


不必要的回表

我们都知道innodb采用B+树作为索引(如上图),在普通索引的条件下,非叶子阶段上存储了索引列值的比较锚点,叶子节点上存储索引列值到主键id的pair。在普通索引上查询到叶子节点并获取对应的主键后,若SQL中需要获取索引列以外的值,则需要到主键索引上,利用先前定位到的主键进行回表操作,获取完整的数据。

SQL2和SQL3的对比正好是是否需要触发回表的对比,可以发现执行时间差了4倍以上。通过执行两者的执行计划,也可以发现,在SQL3的extra信息中,会有Using Index的信息,这代表命中了覆盖索引,不需要额外进行回表。

同时对比SQL1和SQL2,两者只有在offset的部分存在区别,所以当offset变大时,会导致回表后需要扫描记录数显著增加(这也说明limit的执行逻辑是在回表之后,但是where条件执行是会在索引或者回表时就应用的,那么有办法把limit的操作前置到where中呢?)

延迟关联

-- 500ms
Select id, item_code, type, sub_type 
From item
INNER JOIN (
    Select id 
    From item
    where type = 'normal'
    order by id ASC
    limit 1000000, 20
) t1 using (id);

子查询

-- 500ms 
Select id, item_code, type, sub_type 
From item
where id >= (
    Select id 
    From item
    where type = 'normal'
    order by id ASC
    limit 1000000, 1
) limit 20;

两种方式都非常类似,都是通过避免回表,拿到数据的主键id,再通过join 或者id直接比较的方式,跳过了无意义的回表扫描。(相当于通过人为的方式将limit操作前置到回表之前了)

禁止跳页

还有一种简单的方式就是,需要客户端传入上一次调用的last_id,然后在where条件里加上last_id的条件即可。但是这种做法使得客户端无法进行跳页访问了,只能连续的进行上一页或者下一页操作。(批处理或者导出的场景还是非常适用的)

关于OrderBy的影响

上述的SQL当order by 条件发生变化时,SQL的执行效率也会发生巨大的变化,甚至比limit本身影响更大。因为order By会决定mysql优化器的索引选择,以及会触发FileSort(即在内存中开辟专属空间进行排序)。

-- SQL5 9000 ms
Select id, item_code, type, sub_type 
From item
where type = 'normal'
order by item_code ASC 
-- 此处基于item_code排序
limit 0, 20;

全字段排序

可以看到SQL5仅仅是换了一个排序条件,并且查询计划显示命中的索引仍与先前一致,但是执行时间却来到了夸张的9000ms,相比SQL1,多了近20倍,为什么会产生这样的结果呢?可以先了解一下OrderBy的基本执行逻辑:

  1. 初始化sort_buffer, 放入字段id, item_code, type, sub_type

  2. 从type索引中找到符合type = 'normal'的记录,获取id

  3. 根据id,从主键索引中获取id, item_code, type, sub_type,放入sort_buffer

  4. 重复2~3,直到没有符合type = 'normal'的记录

  5. sort_buffer根据item_code进行排序

  6. 排序结果取前20行返回

当排序的数据过大时,会启用外部排序(临时文件归并排序)

  • 这里可能会有些疑问,为什么要把不排序的字段也放到sort_buffer中?是因为排完序后,可以直接从排序的结果集中取出完整的Select所需要的字段。

部分字段排序

  • 那如果是Select *呢?并且表的字段非常多,是否会过度浪费sort_buffer的资源,导致触发外部排序呢?是的,但是Mysql中存在一个配置,max_length_for_sort_data,当所放字段大于这个值时,就不会把所有select的字段放入sort_buffer,而是选择排完序之后,再次进行回表,得到完整的数据:
  1. 初始化sort_buffer, 放入字段id, item_code

  2. 从type索引中找到符合type = 'normal'的记录,获取id

  3. 根据id,从主键索引中获取id, item_code,放入sort_buffer

  4. 重复2~3,直到没有符合type = 'normal'的记录

  5. sort_buffer根据item_code进行排序

  6. 排序结果取前20行, 得到对应的id,从聚簇索引中回表,得到完整的数据

通常来说,Mysql规格够大时,不建议使用这种排序方式,因为会额外回表。

覆盖索引跳过排序

如果此时存在type, item_code的覆盖索引,则无需额外排序,即可返回结果集,执行效率是最高的。

但是如果type的条件变为in呢?

-- SQL5 9000 ms
Select id, item_code, type, sub_type 
From item
where type in ('normal', 'abnormal')
order by item_code ASC 
-- 此处基于item_code排序
limit 0, 20;

答案是,需要排序。因此这种情况下推荐在业务代码中,将其拆为两句 type = 'normal' 和 type = 'abnormal'的SQL,然后业务代码中自行实现归并排序即可。

参考文献

  1. MySQL实战45讲

  2. 从根儿上理解Mysql

  3. Mysql官方文档/排序优化

标签:code,分页,索引,item,SQL,排序,type,id
From: https://www.cnblogs.com/xy1997/p/18654151

相关文章

  • C语言冒泡排序教程简介
    冒泡排序(BubbleSort)是一种简单的排序算法,因其工作原理像气泡一样逐渐上浮而得名。其基本思想是通过一轮一轮地比较相邻的元素,将较大的元素逐步“冒泡”到数组的尾部。在本篇博客中,我们将详细讲解冒泡排序的基本概念,如何在C语言中实现冒泡排序,并提供一些示例来帮助大家理解。......
  • MySQL下载安装及环境配置
    MySQL下载安装及环境配置本文提供了一步一步的MySQL在Windows上的下载、安装和环境配置教程。从MySQL官网下载CommunityServer,选择Windows版本,解压并创建data和my.ini文件,配置环境变量,最后通过CMD以管理员权限完成初始化、安装、启动数据库及密码修改。MySQL是什么?MySQL是一......
  • SQLite 调试与性能优化指南
    在前几篇文章中,我们深入了解了SQLite的基础和高级功能,以及如何利用其扩展能力。本篇文章将重点讲解SQLite的调试工具和性能优化技巧,以帮助您解决常见问题并进一步提升数据库性能。常见问题及解决方法SQLite的轻量级特性使其非常易用,但在某些场景下可能会遇到以下常......
  • SQLite 进阶:扩展功能与最佳实践
    在前两篇文章中,我们探讨了SQLite的基础知识和高级功能。本篇将进一步探讨SQLite的扩展功能,包括加密、与其他工具的集成、多线程使用、性能优化,以及如何实现跨平台兼容性。数据加密SQLite本身不直接支持加密,但可以通过SQLite的扩展(如SQLiteEncryptionExtension,......
  • SQL 基础教程 - SQL SELECT 语句
    SQL SELECT 语句SELECT语句用于从数据库中选取数据。SQLSELECT语句SELECT语句用于从数据库中选取数据。结果被存储在一个结果表中,称为结果集。SQLSELECT语法SELECTcolumn1,column2,...FROMtable_name;与SELECT*FROMtable_name;参数说明:column1,co......
  • SQL 基础教程 - SQL UPDATE 语句
    SQL UPDATE 语句UPDATE语句用于更新表中的记录。SQLUPDATE语句UPDATE语句用于更新表中已存在的记录。SQLUPDATE语法UPDATEtable_nameSETcolumn1=value1,column2=value2,...WHEREcondition;参数说明:table_name:要修改的表名称。column1,column2,........
  • 学习随想:高维AI数据的训练和推理与一维数据的排序和查找
    以下是看AttentionIsAllYouNeed这篇文章的一点随想。说实话,我没看懂transformer是咋回事,但突然一个类比念头,让我感觉有点概念了,虽然所有的类比都是不完备的。学习随想记录如下,仅供查考:物理世界高维AI数据一维数据物理对象n维矩阵向量,Word2vec一维数组观察与实验数据......
  • ​SQLite​的下载与安装(简洁版),附带建表和四个基本的增删改查语句
    1.下载SQLite安装包SQLiteDownloadPage下载后解压执行sqlite3.exe2.创建数据库.open[路径+数据库名字]【Tips:没有找到指定的数据库文件则会默认创建】.openckk.db3.建表和四个基本的增删改查语句数据类型常用的有:int整形、real浮点数、text文本、blob......
  • MySQL索引原理及慢查询优化7
    背景MySQL凭借着出色的性能、低廉的成本、丰富的资源,已经成为绝大多数互联网公司的首选关系型数据库。虽然性能出色,但所谓“好马配好鞍”,如何能够更好的使用它,已经成为开发工程师的必修课,我们经常会从职位描述上看到诸如“精通MySQL”、“SQL语句优化”、“了解数据库原理”等......
  • MySQL索引原理及慢查询优化5
    背景MySQL凭借着出色的性能、低廉的成本、丰富的资源,已经成为绝大多数互联网公司的首选关系型数据库。虽然性能出色,但所谓“好马配好鞍”,如何能够更好的使用它,已经成为开发工程师的必修课,我们经常会从职位描述上看到诸如“精通MySQL”、“SQL语句优化”、“了解数据库原理”等......