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

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 WHERE; USING 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的技术是很有效的。

时间: 2024-11-03 22:11:27

MySql sql优化之order by desc/asc limit M的相关文章

mysql sql优化实例

mysql sql优化实例 优化前: pt-query-degist分析结果: # Query 3: 0.00 QPS, 0.00x concurrency, ID 0xDC6E62FA021C85B5 at byte 628331 # This item is included in the report because it matches --limit. # Scores: V/M = 0.19 # Time range: 2016-09-24T15:14:24 to 2016-10-0

MySQL SQL优化之覆盖索引

前些天,有个同事跟我说:"我写了个SQL,SQL很简单,但是查询速度很慢,并且针对查询条件创建了索引,然而索引却不起作用,你帮我看看有没有办法优化?". 我对他提供的case进行了优化,并将优化过程整理了下来. 优化前的表结构.数据量.SQL.执行计划.执行时间 表结构 CREATE TABLE `t_order` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `order_code` char(12) NOT NULL, `o

mysql sql优化之straight_join

在oracle中可以指定的表连接的hint有很多:ordered hint 指示oracle按照from关键字后的表顺序来进行连接:leading hint 指示查询优化器使用指定的表作为连接的首表,即驱动表:use_nl hint指示查询优化器使用nested loops方式连接指定表和其他行源,并且将强制指定表作为inner表. 在mysql中就有之对应的straight_join,由于mysql只支持nested loops的连接方式,所以这里的straight_join类似oracle中

MySQL索引优化的实际案例分析_Mysql

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

性能优化案例-SQL优化

最近刚做了系统迁移,需要将老系统的数据迁移到新的系统中,老系统中是分了100张表,表的数据量分配不均匀,有些表有40G左右的数据,而有些表的数据又比较少. 刚开始处理的方式是,先获取该表数据的总量,然后进行分页处理,迁移的sql如下: 第一步: select count(1) from some_table where type_id = #type_id#; 第二步: select * from some_table where type_id = #type_id# limit #offse

MySQL优化案例系列-mysql分页优化_Mysql

通常,我们会采用ORDER BY LIMIT start, offset 的方式来进行分页查询.例如下面这个SQL: SELECT * FROM `t1` WHERE ftype=1 ORDER BY id DESC LIMIT 100, 10; 或者像下面这个不带任何条件的分页SQL: SELECT * FROM `t1` ORDER BY id DESC LIMIT 100, 10; 一般而言,分页SQL的耗时随着 start 值的增加而急剧增加,我们来看下面这2个不同起始值的分页SQL执行

MySQL怎样优化WHERE子句

mysql|优化     where优化主要是在SELECT中,因为他们最主要是在那里使用,但是同样的优化也可被用于DELETE和UPDATE语句. 但请注意,下面的优化并不是完全的.MYSQL实施了许多优化,但我没时间全部测试. MySQL的一些优化列在下面: 删除不必要的括号: ((a AND b) AND c OR (((a AND b) AND (c AND d)))) -> (a AND b AND c) OR (a AND b AND c AND d) 常数调入: (a -> b&

SQL优化技巧指南_MsSql

对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描 select id from t where num is null 最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库. 备注.描述.评论之类的可以设置为 NULL,其他的,最好不要使用NULL. 不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立

分析MySQL中优化distinct的技巧_Mysql

有这样的一个需求:select count(distinct nick) from user_access_xx_xx; 这条sql用于统计用户访问的uv,由于单表的数据量在10G以上,即使在user_access_xx_xx上加上nick的索引, 通过查看执行计划,也为全索引扫描,sql在执行的时候,会对整个服务器带来抖动: root@db 09:00:12>select count(distinct nick) from user_access; +--------+ | count(dis