MySql sql优化之order by desc/asc limit M

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介:

Order by desc/asc limit M是我在mysql sql优化中经常遇到的一种场景,其优化原理也非常的简单,就是利用索引的有序性,优化器沿着索引的顺序扫描,在扫描到符合条件的M行数据后,停止扫描;看起来非常的简单,但是我经常看到很多性能较差的sql没有利用这个优化规律,下面将结合一些实际的案例来分析说明:

案例一:

一条sql执行非常的慢,执行时间为:

root@test 02:00:44
 
SELECT * FROM test_order_desc WHERE  END_TIME>now() ORDER BY GMT_CREATE DESC,count_num DESC LIMIT 12, 12;
 
+---------+-----------+------------+------+---------------------+---------------------+-------------------
Data1.....................................................................................................
 
Data2.....................................................................................................
 
+---------+-----------+------------+------+---------------------+---------------------+-------------------
12 ROWS IN SET (0.49 sec)
执行计划如下:
root@test_db01:53:23
 
EXPLAIN SELECT * FROM test_order_desc  WHERE  END_TIME > now()
 ORDER BY GMT_CREATE DESC,count_num DESC LIMIT 12, 12;
 
+----+-------------+----------+-------+-----------------+-----------------+---------+------+--------+-----
 
| id | select_type | TABLE    | TYPE  | possible_keys   | KEY    | key_len | REF  | ROWS   | Extra     |
 
+----+-------------+----------+-------+-----------------+-----------------+---------+------+--------+-----
 
|  1 | SIMPLE      | test_order_desc | range | ind_hot_endtime | ind_hot_endtime | 9       | NULL | 113549 | USING WHEREUSING filesort |
 
+----+-------------+----------+-------+-----------------+-----------------+---------+------+--------+-----

Ind_hot_endtime索引为:

root@test_db01:52:45:SHOW INDEX FROM test_order_desc;
 
Ind_hot_endtime(end_time,count_num)

在注意到sql中满足过滤条件end_time>now()的有113549行,在加上剩余的条件中含有order by,这样会造成排序的结果集非常的大,执行非常的耗费资源;于是分析sql,在sql中包括了order by desc limit这样的排序条件后,新增适当的索引满足排序的条件,同时由于有limit的限制结果集,当扫描到满足条件的行数后退出查询,那么我们来看看优化效果:

添加索引:

root@test 02:01:06:ALTER TABLE test_order_desc ADD INDEX ind_gmt_create(gmt_create,count_num);
 
Query OK, 211945 ROWS affected (6.71 sec)
 
Records: 211945  Duplicates: 0  Warnings: 0

再次执行sql,观察其执行时间:

root@test 02:01:35:
 
SELECT  * FROM test_order_desc WHERE  END_TIME > now()   ORDER BY GMT_CREATE DESC,count_num DESC LIMIT 12, 12;
 
+---------+-----------+------------+------+---------------------+---------------------+
col2...................................................................................
 
+---------+-----------+------------+------+---------------------+---------------------+
 
Data1..................................................................................
 
Data2..................................................................................
 
+---------+-----------+------------+------+---------------------+---------------------+
 
12 ROWS IN SET (0.00 sec)

可以看到执行时间已经降到了毫秒以下,查看其执行计划:

root@test 02:01:42:
 
EXPLAIN SELECT * FROM test_order_desc  WHERE  END_TIME > now() ORDER BY GMT_CREATE DESC,count_num DESC LIMIT 12, 12;
 
+----+-------------+----------+-------+-----------------+----------------+---------+------+------+-------------+
 
| id | select_type | TABLE    | TYPE  | possible_keys   | KEY  | key_len | REF  | ROWS | Extra |
 
+----+-------------+----------+-------+-----------------+----------------+---------+------+------+--------
 
|  1 | SIMPLE      | test_order_desc | INDEX | ind_hot_endtime | ind_gmt_create | 14      | NULL |  48 | USING WHERE |

可以看到优化器已经选择了ind_gmt_create索引扫描,这样的话就避免了对结果集进行排序的过程,同时优化器预估扫描14行数据就会得到满足查询条件的数据(END_TIME > now()),执行计划非常的理想。

 

案例二:

root@127.0.0.1 : test_db 16:05:15:
EXPLAIN  SELECT b.*,a.*,k.*    FROM instance b LEFT OUTER JOIN image a  ON b.image_id=a.image_id  LEFT OUTER JOIN key_pair k  ON b.key_pair_id=k.key_pair_id  LEFT OUTER JOIN region_alias r_a ON r_a.region_no=b.region_no WHERE b.STATUS  IN (1,8) AND   b.user_id = 21 AND r_a.big_region_no='regeion_xx' ORDER BY b.instance_no ASC LIMIT 37300,50;


B表的idx_uid_stat_inid的索引列包括了(user_id,status,instance_no):

我们从执行计划上分析来看,表的连接顺序为:b—>r_a—>a—>k,可以看到执行计划的第一行中需要扫描49212行的数据,同时由于status采用的是in的方式,instance_no即使在索引中也用不上,这样就导致了排序使用到了临时表,这也是导致sql执行慢的原因。我们看到sql中的最后一个排序为order by b.instance_no asc limit 37300,50,这里我们好像可以看到优化的曙光,调整数据库的索引以满足B表的排序需求:

root@127.0.0.1 : test_db 16:05:04 ALTER TABLE instance ADD INDEX ind_user_id(user_id,instance_no);
 
Query OK, 0 ROWS affected (0.56 sec)

调整索引后查看执行计划:

root@127.0.0.1 : test_db 16:09:42
EXPLAIN  SELECT b.*,a.*,k.*    FROM instance b LEFT OUTER JOIN image a  ON b.image_id=a.image_id  LEFT OUTER JOIN key_pair k  ON b.key_pair_id=k.key_pair_id  LEFT OUTER JOIN region_alias r_a ON r_a.region_no=b.region_no WHERE b.STATUS  IN (1,8) AND   b.user_id = 21 AND r_a.big_region_no='regeion_xx' ORDER BY b.instance_no ASC LIMIT 37300,50;

我们加上force index强制走我们新加的索引:

root@127.0.0.1 : test_db 16:10:24
EXPLAIN  SELECT b.*,a.*,k.*    FROM instance b  force INDEX (ind_user_id) LEFT OUTER JOIN image a  ON b.image_id=a.image_id  LEFT OUTER JOIN key_pair k  ON b.key_pair_id=k.key_pair_id  LEFT OUTER JOIN region_alias r_a ON r_a.region_no=b.region_no WHERE b.STATUS  IN (1,8) AND   b.user_id = 21 AND r_a.big_region_no='regeion_xx' ORDER BY b.instance_no ASC LIMIT 37300,50;

可以看到在加上提示符后,使用到了我们新加的索引,扫描的行数为54580行,执行时间:

root@127.0.0.1 : test_db 16:10:30
SELECT b.*,a.*,k.*    FROM instance b  force INDEX (ind_user_id) LEFT OUTER JOIN image a  ON b.image_id=a.image_id  LEFT OUTER JOIN key_pair k  ON b.key_pair_id=k.key_pair_id  LEFT OUTER JOIN region_alias r_a ON r_a.region_no=b.region_no WHERE b.STATUS IN (1,8) AND   b.user_id = 21 AND r_a.big_region_no='regeion_xx' ORDER BY b.instance_no ASC LIMIT 37300,50;
(0.49 sec)

原始的执行时间:
root@127.0.0.1 : test_db 16:10:51:

SELECT b.*,a.*,k.*    FROM instance b   LEFT OUTER JOIN image a  ON b.image_id=a.image_id  LEFT OUTER JOIN key_pair k  ON b.key_pair_id=k.key_pair_id  LEFT OUTER JOIN region_alias r_a ON r_a.region_no=b.region_no WHERE b.STATUS  IN (1,8) AND   b.user_id = 21 AND r_a.big_region_no='regeion_xx' ORDER BY b.instance_no ASC LIMIT 37300,50;
(1.28 sec)

总结:
Order by desc/asc limit的优化技术有时候在你无法建立很好索引的时候,往往会得到意想不到的优化效果,但有时候有一定的局限性,优化器可能不会按照你既定的索引路径扫描,优化器需要考虑到查询列的过滤性以及limit的长度,当查询列的选择性非常高的时候,使用sort的成本是不高的,当查询列的选择性很低的时候,那么使用order by +limit的技术是很有效的。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
14天前
|
关系型数据库 MySQL 索引
mysql 分析5语句的优化--索引添加删除
mysql 分析5语句的优化--索引添加删除
12 0
|
20天前
|
存储 关系型数据库 MySQL
轻松入门MySQL:数据库设计之范式规范,优化企业管理系统效率(21)
轻松入门MySQL:数据库设计之范式规范,优化企业管理系统效率(21)
|
20天前
|
存储 SQL 关系型数据库
轻松入门MySQL:加速进销存!利用MySQL存储过程轻松优化每日销售统计(15)
轻松入门MySQL:加速进销存!利用MySQL存储过程轻松优化每日销售统计(15)
|
20天前
|
存储 关系型数据库 MySQL
轻松入门MySQL:优化进销存管理,掌握MySQL索引,提升系统效率(11)
轻松入门MySQL:优化进销存管理,掌握MySQL索引,提升系统效率(11)
|
14天前
|
SQL 缓存 关系型数据库
mysql性能优化-慢查询分析、优化索引和配置
mysql性能优化-慢查询分析、优化索引和配置
80 1
|
20天前
|
存储 关系型数据库 MySQL
MySQL数据库性能大揭秘:表设计优化的高效策略(优化数据类型、增加冗余字段、拆分表以及使用非空约束)
MySQL数据库性能大揭秘:表设计优化的高效策略(优化数据类型、增加冗余字段、拆分表以及使用非空约束)
|
20天前
|
缓存 关系型数据库 MySQL
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
|
17小时前
|
SQL Oracle 关系型数据库
下次老板问你MySQL如何优化时,你可以这样说,老板默默给你加工资
现在进入国企或者事业单位做技术的网友越来越多了,随着去O的力度越来越大,很多国企单位都开始从Oracle向MySQL转移,相对于Oracle而言,MySQL最大的问题就是性能,所以,这个时候,在公司如果能够处理好MySQL的性能瓶颈,那么你也就很容易从人群中脱颖而出,受到老板的青睐。
8 1
|
9天前
|
SQL 关系型数据库 数据库
【后端面经】【数据库与MySQL】SQL优化:如何发现SQL中的问题?
【4月更文挑战第12天】数据库优化涉及硬件升级、操作系统调整、服务器/引擎优化和SQL优化。SQL优化目标是减少磁盘IO和内存/CPU消耗。`EXPLAIN`命令用于检查SQL执行计划,关注`type`、`possible_keys`、`key`、`rows`和`filtered`字段。设计索引时考虑外键、频繁出现在`where`、`order by`和关联查询中的列,以及区分度高的列。大数据表改结构需谨慎,可能需要停机、低峰期变更或新建表。面试中应准备SQL优化案例,如覆盖索引、优化`order by`、`count`和索引提示。优化分页查询时避免大偏移量,可利用上一批的最大ID进行限制。
33 3
|
15天前
|
SQL 存储 关系型数据库
【MySQL实战笔记】02.一条SQL更新语句是如何执行的-2
【4月更文挑战第5天】两阶段提交是为确保`redo log`和`binlog`逻辑一致,避免数据不一致。若先写`redo log`, crash后数据可能丢失,导致恢复后状态错误;若先写`binlog`,crash则可能导致重复事务,影响数据库一致性。一天一备相较于一周一备,能缩短“最长恢复时间”,但需权衡额外的存储成本。
16 1