zoukankan      html  css  js  c++  java
  • Mysql 排序优化与索引使用(转)

      为了优化SQL语句的排序性能,最好的情况是避免排序,合理利用索引是一个不错的方法。因为索引本身也是有序的,如果在需要排序的字段上面建立了合适的索引,那么就可以跳过排序的过程,提高SQL的查询速度。下面我通过一些典型的SQL来说明哪些SQL可以利用索引减少排序,哪些SQL不能。假设t1表存在索引key1(key_part1,key_part2),key2(key2)

    a.可以利用索引避免排序的SQL

    1
    2
    3
    4
    SELECT FROM t1 ORDER BY key_part1,key_part2;
    SELECT FROM t1 WHERE key_part1 = constant ORDER BY key_part2;
    SELECT FROM t1 WHERE key_part1 > constant ORDER BY key_part1 ASC;
    SELECT FROM t1 WHERE key_part1 = constant1 AND key_part2 > constant2 ORDER BY key_part2;

    b.不能利用索引避免排序的SQL

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //排序字段在多个索引中,无法使用索引排序
    SELECT FROM t1 ORDER BY key_part1,key_part2, key2;
     
    //排序键顺序与索引中列顺序不一致,无法使用索引排序
    SELECT FROM t1 ORDER BY key_part2, key_part1;
     
    //升降序不一致,无法使用索引排序
    SELECT FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;
     
    //key_part1是范围查询,key_part2无法使用索引排序
    SELECT FROM t1 WHERE key_part1> constant ORDER BY key_part2;

    2.排序实现的算法
          对于不能利用索引避免排序的SQL,数据库不得不自己实现排序功能以满足用户需求,此时SQL的执行计划中会出现“Using filesort”,这里需要注意的是filesort并不意味着就是文件排序,其实也有可能是内存排序,这个主要由sort_buffer_size参数与结果集大小确定MySQL内部实现排序主要有3种方式,常规排序,优化排序和优先队列排序,主要涉及3种排序算法:快速排序、归并排序和堆排序

    假设表结构和SQL语句如下:

    CREATE TABLE t1(id int, col1 varchar(64), col2 varchar(64), col3 varchar(64), PRIMARY KEY(id),key(col1,col2));
    SELECT col1,col2,col3 FROM t1 WHERE col1>100 ORDER BY col2;

    a.常规排序,双路排序
    (1).从表t1中获取满足WHERE条件的记录
    (2).对于每条记录,将记录的主键+排序键(id,col2)取出放入sort buffer
    (3).如果sort buffer可以存放所有满足条件的(id,col2)对,则进行排序;否则sort buffer满后,进行排序并写到临时文件中。(排序算法采用的是快速排序算法)
    (4).若排序中产生了临时文件,需要利用归并排序算法,保证临时文件中记录是有序的
    (5).循环执行上述过程,直到所有满足条件的记录全部参与排序
    (6).扫描排好序的(id,col2)队,即sort buffer,并利用主键id去取SELECT需要返回的其他列(col1,col2,col3)
    (7).将获取的结果集返回给用户。
          从上述流程来看,是否使用文件排序主要看sort buffer是否能容下需要排序的(id,col2)的结果集,这个buffer的大小由sort_buffer_size参数控制。此外一次排序还需要两次IO一次是取排序字段(id,col2)到sort buffer中,第二次是通过上面取出的主键id再来取其他所需要返回列(col1,col2,col3),由于返回的结果集是按col2排序,因此id是乱序的,通过乱序的id取(col1,col2,col3)时会产生大量的随机IO。对于第二次IO取MySQL本身会优化,即在取之前先将主键id排序,并放入缓冲区,这个缓存区大小由参数read_rnd_buffer_size控制,然后有序去取记录,将随机IO转为顺序IO
    b.优化排序,单路排序,max_length_for_sort_data
         常规排序方式除了排序本身,还需要额外两次IO。优化排序方式相对于常规排序,减少了第二次IO主要区别在于,一次性取出sql中出现的所有字段放入sort buffer中而不是只取排序需要的字段(id,col2)。由于sort buffer中包含了查询需要的所有字段,因此排序完成后可以直接返回,无需二次取数据。这种方式的代价在于,同样大小的sort buffer,能存放的(col1,col2,col3)数目要小于(id,col2),如果sort buffer不够大,可能导致需要写临时文件,造成额外的IO。当然MySQL提供了参数max_length_for_sort_data,只有当排序sql里出现的所有字段小于max_length_for_sort_data时,才能利用优化排序方式,否则只能用常规排序方式。
    c.优先队列排序
         为了得到最终的排序结果,我们都需要将所有满足条件的记录进行排序才能返回。那么相对于优化排序方式,是否还有优化空间呢?5.6版本针对Order by limit M,N语句,在空间层面做了优化,加入了一种新的排序方式--优先队列,这种方式采用堆排序实现。堆排序算法特征正好可以解limit M,N 这类排序的问题,虽然仍然需要所有字段参与排序,但是只需要M+N个元组的sort buffer空间即可,对于M,N很小的场景,基本不会因为sort buffer不够而导致需要临时文件进行归并排序的问题。对于升序,采用大顶堆,最终堆中的元素组成了最小的N个元素,对于降序,采用小顶堆,最终堆中的元素组成了最大的N的元素。

    3.排序不一致问题

    案例1:order by no_index limit n在MySQL5.5和5.6中的不一致

    MySQL从5.5迁移到5.6以后,发现分页出现了重复值(排序字段没有用索引,或则直接是全表扫描),MariaDB已经是优化后的方案,和5.6一致。

    问题源头:https://bbs.aliyun.com/read/248026.html,解决办法:http://mysql.taobao.org/monthly/2015/06/04/

    测试表与数据:

    复制代码
    复制代码
    create table t1(id int primary key, c1 int, c2 varchar(128));
    insert into t1 values(1,1,'a');
    insert into t1 values(2,2,'b');
    insert into t1 values(3,2,'c');
    insert into t1 values(4,2,'d');
    insert into t1 values(5,3,'e');
    insert into t1 values(6,4,'f');
    insert into t1 values(7,5,'g');
    复制代码
    复制代码

    假设每页3条记录,第一页limit 0,3和第二页limit 3,3查询结果如下:

    我们可以看到 id为4的这条记录居然同时出现在两次查询中,这明显是不符合预期的,而且在5.5版本中没有这个问题。

    使用优先队列排序的目的就是在不能使用索引有序性的时候,如果要排序,并且使用了limit n,那么只需要在排序的过程中,保留n条记录即可,这样虽然不能解决所有记录都需要排序的开销,但是只需要 sort buffer 少量的内存就可以完成排序,上面已经说明。

    之所以MySQL5.6出现了第二页数据重复的问题,是因为使用了优先队列排序,其使用了堆排序的排序方法,而堆排序是一个不稳定的排序方法,也就是相同的值(例子中的值2)可能排序出来的数据和读出来的数据顺序不一致,无法保证排序前后数据位置的一致,所以导致分页重复的现象

    为了避免这个问题,有几种方法:

    ①:索引排序字段

    利用索引的有序性,在字段添加上索引,就直接按照索引的有序性进行读取并分页,从而可以规避遇到的这个问题。

    ②:利用多列索引,对于单列相同无法排序的,利用其主键进行排序:

    select * from t1 order by c1,id asc limit 0,3;
    select * from t1 order by c1,id asc limit 3,3;

    案例2:单路排序和双路排序返回结果不一样

    两个类似的查询语句,除了返回列不同,其它都相同,但排序的结果不一致

    测试表与数据:

    复制代码
    复制代码
    create table t2(id int primary key, status int, c1 varchar(255),c2 varchar(255),c3 varchar(255),key(c1));
    insert into t2 values(7,1,'a',repeat('a',255),repeat('a',255));
    insert into t2 values(6,2,'b',repeat('a',255),repeat('a',255));
    insert into t2 values(5,2,'c',repeat('a',255),repeat('a',255));
    insert into t2 values(4,2,'a',repeat('a',255),repeat('a',255));
    insert into t2 values(3,3,'b',repeat('a',255),repeat('a',255));
    insert into t2 values(2,4,'c',repeat('a',255),repeat('a',255));
    insert into t2 values(1,5,'a',repeat('a',255),repeat('a',255));
    复制代码
    复制代码

    分别执行SQL语句:

    select id,status,c1,c2 from t2 force index(c1) where c1>='b' order by status;
    select id,status from t2 force index(c1) where c1>='b' order by status;

    执行结果如下:

    看看两者的执行计划是否相同

        为了说明问题,因为测试数据不多,确保能走上c1列索引,加了force index的hint。语句通过c1列索引取id,然后去表中捞取返回的列。根据c1列值的大小,记录在c1索引中的相对位置如下:

    (c1,id)<===>(b,6),(b,3),(c,5),(c,2),

    对应的status值分别为2,3,2,4。从表中取数据并按status排序,则相对位置变为(6,2,b),(5,2,c),(3,3,b),(2,4,c),这就是第二条语句查询返回的结果,那么为什么第一条查询语句(6,2,b),(5,2,c)是调换顺序的呢?

    这里说明下:
    1. Query 语句所取出的字段类型大小总和小于max_length_for_sort_data 。
    2. 排序的字段不包含TEXT和BLOB类型。

    之前提到的优化排序就可以明白了:由于第一条查询返回的列的字节数超过了max_length_for_sort_data,导致排序采用常规排序,而在这种情况下第二次IO时,MYSQL本身优化会对id排序,将随机IO转为顺序IO,所以返回的先是5,后是6;而第二条查询采用的是优化排序,没有第二次取数据的过程,保持了排序后记录的相对位置,直接在sort buffer里取出。对于第一条语句,若想采用优化排序,我们将max_length_for_sort_data设置调大即可,比如2048。

  • 相关阅读:
    在Ubuntu中通过update-alternatives切换软件版本
    SCons: 替代 make 和 makefile 及 javac 的极好用的c、c++、java 构建工具
    mongodb 的使用
    利用grub从ubuntu找回windows启动项
    How to Repair GRUB2 When Ubuntu Won’t Boot
    Redis vs Mongo vs mysql
    java script 的工具
    python 的弹框
    how to use greendao in android studio
    python yield的终极解释
  • 原文地址:https://www.cnblogs.com/moss_tan_jun/p/6021822.html
Copyright © 2011-2022 走看看