MySQL如何对order by优化?

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介:

对于order by的优化,MySQL若可以利用索引的有序性进行排序,则优先使用索引进行排序,这种情况的执行效率是最快的;若无法有效利用索引的情况下,MySQL主要有3排序种算法对其进行优化每个算法都有一定的适用场景。

一、 利用索引排序

B-tree索引可以很好的支持单点查询、范围查询、有序性查询。所以对于order by 的排序查询,我们可以利用B-tree的有序性来有效的利用索引进行排序查询。当然,如果可以利用索引进行排序对我们的SQL查询本身也是有一定的要求限制的。

1.1 利用索引排序的特点

1)排序列必须有B-tree索引

2)如果为多表关联查询,排序列必须是对驱动表字段的排序

1.2、示例

##建表语句,sbtest3与sbtest4表字段与索引一致,sbtest3的表数据量为30000,sbtest4的表数据量为60000
CREATE TABLE `sbtest4` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `k` int(11) NOT NULL DEFAULT '0',
  `c` char(120) NOT NULL DEFAULT '',
  `pad` char(60) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `k_4` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=62768 DEFAULT CHARSET=utf8mb4

##单表排序查询
##order by字段为B-tree索引字段,可以看到执行计划有效利用了索引进行排序查询
root@mysql57 13:25:  [db2]> explain select * from sbtest4 t4 order by t4.k desc limit 5;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | t4    | NULL       | index | NULL          | k_4  | 4       | NULL |    5 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)


##多表关联排序查询
##sbtest3为小表,在表关联中作为驱动表与sbtest4进行关联查询,order by字段为驱动表b-tree索引字段,可有效利用索引进行查询
root@mysql57 13:26:  [db2]> explain select * from sbtest4 t4 join sbtest3 t3 on t4.id=t3.id order by t3.k limit 5;
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+-------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref       | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+-------+
|  1 | SIMPLE      | t3    | NULL       | index  | PRIMARY       | k_3     | 4       | NULL      |    5 |   100.00 | NULL  |
|  1 | SIMPLE      | t4    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | db2.t3.id |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)

1.3、有效利用复合索引进行排序查询

1、当复合索引的最左前缀列为过滤条件的常量过滤时,order by字段配合常量过滤字段满足最左前缀时可以使用复合索引进行排序优化。

如,建立复合索引(a,b,c)可以使用复合索引扫描排序有:

from tbl_name where a=xx order by b,c;
from tbl_name where a=xx order by b;

2、过滤字段不是复合索引中的常量,但是order by列满足最左前缀是可以使用覆盖索引:

from tbl_name where a>xx order by a,b     #order by字段满足最左前缀

3、一些情况不能使用复合索引扫描排序的情况

from tbl_name where a=xx order by b desc,c asc;  #一列为升序一列为降序
from tbl_name where a=xx order by b,d;      #order by列引用了一个不在索引中的字段
from tbl_name where a=xx order by c;  #无法组合成索引的最左前缀
from tbl_name where a=xx and b in (xx,xx) order by c;    #存在范围查询

4、值得注意的一个小问题

如果我们建立单列索引(A),实际上相当于在(A,ID)上建立了索引,其中ID为主键。这中情况下对于 where A=xx order by ID的查询是非常有帮助的。但是如果我们建立了复合索引(A,B),那么就相当于在(A,B,ID)上建立了索引,那么对于where A=xx order by ID这样的查询,就使用不到索引扫描排序,只能用filesort排序(using filesort)了。

1.4、无法使用索引排序的几种情况

1)排序基准太多,无法有效利用索引满足所有的排序字段准则

2)对多个union子查询进行排序

3)需要随机获取结果记录

二、仅对驱动表排序

2.1、仅对驱动表进行排序的特点

1)多表关联查询中,排序字段为驱动表字段,且该字段无法有效利用索引

2)被驱动表关联字段为有效索引字段,有效利用INLJ算法进行表关联

2.2、示例

##sbtest3作为驱动表,通过k字段的索引进行过滤找到满足where条件的记录
##由于order by字段c无法有效利用索引,所以必须将满足过滤条件的记录放至sort buffer进行排序处理
##在执行计划中,我们可以看到“Using filesort”表示使用filesort进行了排序处理

root@mysql57 13:55:  [db2]> explain select * from sbtest4 t4 join sbtest3 t3 on t4.id=t3.id where t3.k<1000 order by t3.c limit 5;
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref       | rows | filtered | Extra                                 |
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+---------------------------------------+
|  1 | SIMPLE      | t3    | NULL       | range  | PRIMARY,k_3   | k_3     | 4       | NULL      |    1 |   100.00 | Using index condition; Using filesort |
|  1 | SIMPLE      | t4    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | db2.t3.id |    1 |   100.00 | NULL                                  |
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+---------------------------------------+
2 rows in set, 1 warning (0.01 sec)

2.3、对于使用驱动表排序的优化

若SQL无法有效利用索引进行优化,且仅仅是对驱动表进行排序处理,这已然是一种相对较好的情况,我们更多的只需要关注SQL的where过滤条件是否可以有效利用索引减少驱动表需要扫描以及排序的记录数。

三、使用临时表进行排序

3.1、使用临时表表进行排序的特点

多表关联查询中,排序字段为被驱动表字段,MySQL必须获取到多表关联的结果后才可以对这些记录进行排序处理

3.2、示例

##sbtest3作为驱动表,通过k字段的索引进行过滤找到满足where条件的记录
##由于order by字段为被驱动表的c字段,所以必须将获取到两表join的结果集,然后进行排序
##在执行计划中,我们可以看到“Using temporary; Using filesort”表示使用临时表进行了排序处理

root@mysql57 14:00:  [db2]> explain select * from sbtest4 t4 join sbtest3 t3 on t4.id=t3.id where t3.k<1000 order by t4.k limit 5;
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+--------------------------------------------------------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref       | rows | filtered | Extra                                                  |
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+--------------------------------------------------------+
|  1 | SIMPLE      | t3    | NULL       | range  | PRIMARY,k_3   | k_3     | 4       | NULL      |    1 |   100.00 | Using index condition; Using temporary; Using filesort |
|  1 | SIMPLE      | t4    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | db2.t3.id |    1 |   100.00 | NULL                                                   |
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+--------------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

3.3、对于使用临时表排序的优化

对于使用临时表进行排序的查询其资源消耗是以上说到的三种排序方式下资源消耗最大的一种排序方式。在这种模式下,优先考虑排序字段是否可以等价替换为驱动表字段,将其转换为只对驱动表进行排序;若以上手段无效,我们只能通过where过滤条件有效利用索引,通过索引过滤尽量减少SQL查询扫描数据量;select只查询需要的字段,避免select *,尽量减少磁盘临时表的使用。

四、三种排序方式对比

排序处理方式 执行计划extra列信息 排序性能比较
利用索引进行排序
只对驱动表进行排序 Using filesort
对临时表进行排序 Using temporary; Using filesort

4.1 利用索引进行排序

对于order by ... limit N的排序查询,MySQL会优先权衡是否可以通过优先级队列排序,在内存中完成排序。

4.2 filesort排序

MySQL优化器会优先选择通过索引进行排序查询,若SQL无有效索引可利用,一般会优先根据where条件进行索引过滤,将需要满足过滤条件的记录放在sort buffer中进行排序处理。若需要排序的记录较少,sort_buffer_size的大小即可以满足,此时的排序处理是相对比较快的;若需要排序的记录较大或者有textblob这种大字段,sort_buffer_size大小无法满足一次性对这些记录进行排序,那么MySQL会将需要排序的记录切分为多块儿,每块儿通过sort buffer进行排序后,将结果集转储至磁盘临时表,最终将这些排好序的记录进行合并返回,这种排序方式也较多“多路并归排序”。

4.3 关于sort_buffer_size

对于多路并归的排序方式,理论上只要我们的sort_buffer_size足够大,就可以避免使用到磁盘临时表,但是若该参数是基于会话级别的,若设置不合理极有可能占用过多内存,导致OOM。

4.4 排序相关参数以及具体含义

mysql>show global status like 'sort%';
+-------------------------+-----------------+
| Variable_name           | Value           |
+-------------------------+-----------------+
| Sort_merge_passes       | 279044          |   //多路并归方式处理的合并次数
| Sort_range              | 33816597        |   //通过索引范围扫描检索的结果进行排序的次数
| Sort_rows               | 7349842715      |   //目前为止已排序的全部记录数
| Sort_scan               | 148047752       |   //通过全表扫描检索的结果进行排序的次数
+-------------------------+-----------------+
返回行数:[4],耗时:8 ms.

五、三种排序扫描算法

5.1 两次扫描算法

通过两次扫描算法的基本处理流程:

1、排序时,只将排序列和主键值放入sort buffer中进行排序处理,此步骤为第一次扫描,顺序IO;

2、若sort buffer可以一次性存储所有需要排序字段,则直接在sort buffer中进行排序;

3、若sort_buffer_size无法满足一次性存储全部的排序字段,则会将每次读取到sort buffer中的排序记录固化到磁盘,多路归并排序算法,保证临时文件中记录是有序的。

4、根据排好序的记录通过主键回表查询,读取需要的表数据。由于此时是根须排序字段进行排序的,通过主键回表查询会产生大量的随机IO,所以MySQL会将这些记录放至缓冲区按照主键进行排序,缓冲区大小由read_rnd_buffer_size控制,最终通过排好序的主键进行回表扫描查询,此为第二次扫描。

5、一般需要排序记录较大超过max_length_for_sort_data设置值或者查询select中包含BLOB或者TEXT类型字段的情况下会使用该算法进行排序。

5.2 一次扫描算法

一次扫描算法的基本处理流程:

1、排序时,将查询的所有列(排序列以及非排序列)全部放入sort buffer进行排序,此为一次扫描;

2、若sort_buffer_size不够大,需要将每次排好序的记录固化到磁盘;

3、按照排好序的记录直接输出结果;

4、该算法下,只需要扫描一次表数据,避免了回表查询。只有当需要排序的记录小于max_length_for_sort_data定义参数大小时,MySQL才会优先使用一次扫描算法。

5.3 优先队列排序

优先队列排序也成为堆排序,主要是针对order by ... limit M,N的优化。虽然该排序算法需要扫描所有的记录,但是对于sort_buffer_size来讲该排序算法下仅仅需要M+N个元组的空间即可进行排序,避免了sort buffer不够而导致需要临时文件进行归并排序的问题。对于升序,采用大顶堆,最终堆中的元素组成了最小的N个元素,对于降序,采用小顶堆,最终堆中的元素组成了最大的N的元素。

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
9天前
|
SQL 关系型数据库 MySQL
深入解析MySQL的EXPLAIN:指标详解与索引优化
MySQL 中的 `EXPLAIN` 语句用于分析和优化 SQL 查询,帮助你了解查询优化器的执行计划。本文详细介绍了 `EXPLAIN` 输出的各项指标,如 `id`、`select_type`、`table`、`type`、`key` 等,并提供了如何利用这些指标优化索引结构和 SQL 语句的具体方法。通过实战案例,展示了如何通过创建合适索引和调整查询语句来提升查询性能。
72 9
|
1月前
|
SQL 关系型数据库 MySQL
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
MySQL慢查询优化、索引优化,是必知必备,大厂面试高频,本文深入详解,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验分享。
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
|
13天前
|
缓存 关系型数据库 MySQL
MySQL 索引优化以及慢查询优化
通过本文的介绍,希望您能够深入理解MySQL索引优化和慢查询优化的方法,并在实际应用中灵活运用这些技术,提升数据库的整体性能。
52 18
|
12天前
|
缓存 关系型数据库 MySQL
MySQL 索引优化以及慢查询优化
通过本文的介绍,希望您能够深入理解MySQL索引优化和慢查询优化的方法,并在实际应用中灵活运用这些技术,提升数据库的整体性能。
18 7
|
11天前
|
缓存 关系型数据库 MySQL
MySQL 索引优化与慢查询优化:原理与实践
通过本文的介绍,希望您能够深入理解MySQL索引优化与慢查询优化的原理和实践方法,并在实际项目中灵活运用这些技术,提升数据库的整体性能。
41 5
|
1月前
|
SQL 关系型数据库 MySQL
MySQL慢查询优化、索引优化、以及表等优化详解
本文详细介绍了MySQL优化方案,包括索引优化、SQL慢查询优化和数据库表优化,帮助提升数据库性能。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
MySQL慢查询优化、索引优化、以及表等优化详解
|
1月前
|
关系型数据库 MySQL Java
MySQL索引优化与Java应用实践
【11月更文挑战第25天】在大数据量和高并发的业务场景下,MySQL数据库的索引优化是提升查询性能的关键。本文将深入探讨MySQL索引的多种类型、优化策略及其在Java应用中的实践,通过历史背景、业务场景、底层原理的介绍,并结合Java示例代码,帮助Java架构师更好地理解并应用这些技术。
32 2
|
3天前
|
存储 Oracle 关系型数据库
数据库传奇:MySQL创世之父的两千金My、Maria
《数据库传奇:MySQL创世之父的两千金My、Maria》介绍了MySQL的发展历程及其分支MariaDB。MySQL由Michael Widenius等人于1994年创建,现归Oracle所有,广泛应用于阿里巴巴、腾讯等企业。2009年,Widenius因担心Oracle收购影响MySQL的开源性,创建了MariaDB,提供额外功能和改进。维基百科、Google等已逐步替换为MariaDB,以确保更好的性能和社区支持。掌握MariaDB作为备用方案,对未来发展至关重要。
13 3
|
3天前
|
安全 关系型数据库 MySQL
MySQL崩溃保险箱:探秘Redo/Undo日志确保数据库安全无忧!
《MySQL崩溃保险箱:探秘Redo/Undo日志确保数据库安全无忧!》介绍了MySQL中的三种关键日志:二进制日志(Binary Log)、重做日志(Redo Log)和撤销日志(Undo Log)。这些日志确保了数据库的ACID特性,即原子性、一致性、隔离性和持久性。Redo Log记录数据页的物理修改,保证事务持久性;Undo Log记录事务的逆操作,支持回滚和多版本并发控制(MVCC)。文章还详细对比了InnoDB和MyISAM存储引擎在事务支持、锁定机制、并发性等方面的差异,强调了InnoDB在高并发和事务处理中的优势。通过这些机制,MySQL能够在事务执行、崩溃和恢复过程中保持
18 3
|
3天前
|
SQL 关系型数据库 MySQL
数据库灾难应对:MySQL误删除数据的救赎之道,技巧get起来!之binlog
《数据库灾难应对:MySQL误删除数据的救赎之道,技巧get起来!之binlog》介绍了如何利用MySQL的二进制日志(Binlog)恢复误删除的数据。主要内容包括: 1. **启用二进制日志**:在`my.cnf`中配置`log-bin`并重启MySQL服务。 2. **查看二进制日志文件**:使用`SHOW VARIABLES LIKE &#39;log_%&#39;;`和`SHOW MASTER STATUS;`命令获取当前日志文件及位置。 3. **创建数据备份**:确保在恢复前已有备份,以防意外。 4. **导出二进制日志为SQL语句**:使用`mysqlbinlog`
22 2