• Index-06 order by


    TOC


    一、排序实现

    SELECT city,
           name,
           age
    FROM t
    WHERE city='杭州'
    ORDER BY name LIMIT 1000 ;

    1.全字段排序

    1.1排序过程

    1. 初始化sort_buffer, 确定放入city,name,age 字段
    2. 找到第一个city='杭州' 的主键
    3. 从主键找到整行,取name,city,age 三个字段,放入sort_buffer
    4. 从索引city中取下一个记录
    5. 重复3-4, 直到city取值不满足查询条件
    6. sort_buffer 中数据对name字段排序
    7. 取前1000行返回客户端

    1.2临时文件

    对name排序, 可以在内存中完成, 也可能需要使用外部排序, 取决于排序需要的内存和sort_buffer_size
    sort_buffer_size 就是为排序开辟的空间,
    1. 排序数据量< sort_buffer_size, 就在内存排序
    2. 排序数据量>sort_buffer_size, 内存放不下,不得不利用磁盘临时文件辅助排序

    如何确定一个排序语句是否使用到了临时文件

    /* 打开 optimizer_trace,只对本线程有效 */
    SET optimizer_trace='enabled=on'; 
    
    /* @a 保存 Innodb_rows_read 的初始值 */
    select VARIABLE_VALUE into @a from performance_schema.session_status where variable_name = 'Innodb_rows_read';
    
    /* 执行语句 */
    select city, name,age from t where city='杭州' order by name limit 1000; 
    
    /* 查看 OPTIMIZER_TRACE 输出 */
    SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`G
    
    /* @b 保存 Innodb_rows_read 的当前值 */
    select VARIABLE_VALUE into @b from performance_schema.session_status where variable_name = 'Innodb_rows_read';
    
    /* 计算 Innodb_rows_read 差值 */
    select @b-@a;

    number_of_tmp_files 表示的是, 排序过程中,使用的临时文件数.
    MySQL将排序数据分为了12份,每一份单独存储后存在这些临时文件中,然后把这12个有序文件再合并成一个有序的大文件


    2. rowid 排序

    上面的算法中, 对原表数据读了一遍, 剩下的操作都是在sort_buffer 和临时文件中.
    存在一个问题, 查询需要返回的字段很多时, sort_buffer 中放的字段比较多,所以内存中同时放下的行数比较少,要分成很多个临时文件.
    所以单行很大,效率不好, 影响算法的参数SET max_length_for_sort_data = 16;
    控制排序行数据长度的参数, 单行长度超过这个值, 需要换个算法

    city,name,age 三个字段的总长度是36, 将max_length_for_sort_data 设置为16, 放入sort_buffer 中的字段只有拍序列name 和主键id

    流程:

    1. 初始化sort_buffer , 确定放入两个字段name,id
    2. 从索引city找到第一个满足city='杭州' 主键id
    3. 从主键访问,取name, ,将name id 放入sort_buffer
    4. 从索引city取下一个记录
    5. 重复3-4
    6. sort_buffer 对name排序
    7. 遍历排序结果,取前1000行, 回表访问city,name,age 返回客户端


    过程中有两次回表访问, examined_rows 是4000, 但是select @b-@a = 5000 ,访问成本4000+1000
    sort_mode 变成 sort_key,rowid
    number_of_tmp_file 变成10, 因为排序字段少了


    3. 对比总结

    MySQL 内存够用, 就多利用内存, 尽量减少磁盘访问.
    排序成本高,甚至发生磁盘排序, 这是因为原来的数据都是无需的
    alter table t add index city_user(city, name);
    如果建立这样的索引, city='杭州'后 ,name还是有序的,查询过程:

    1. 从 city,name 取满足第一个city='杭州'主键id
    2. 从主键取name,city,age
    3. 从city索引取下一个
    4. 重复2-3 , 直到查询到第1000行结束, 或者不满足city='杭州'

    这个过程中, 不需要临时表,也不需要排序


    二、一个索引优化的case

    语句和表结构

    ## SQL
    SELECT *  
    FROM t_follow_timeline_099  force index (idx_uid_status)
    WHERE UID = 1833901412800099
      AND status = 1
    ORDER BY create_time DESC
    LIMIT 10
    
    CREATE TABLE `t_follow_timeline_099` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT ,
      `uid` bigint(20) NOT NULL DEFAULT '0' ,
      `timeline_id` varchar(32) NOT NULL ,
      `timeline_owner` bigint(20) NOT NULL DEFAULT '0' ,
      `status` tinyint(4) NOT NULL DEFAULT '1' ,
      `timeline_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ,
      `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ,
      `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ,
      PRIMARY KEY (`id`),
      KEY `idx_uid` (`uid`),
      KEY `idx_create_time` (`create_time`),
      KEY `idx_uid_timeline_owner` (`uid`,`timeline_owner`),
      KEY `idx_uid_timeline_id` (`uid`,`timeline_id`),
      KEY `idx_uid_create_time` (`uid`,`create_time`),
      KEY `idx_uid_status` (`uid`,`status`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1091861159388237825 DEFAULT CHARSET=utf8 

    第一页优化

    ## 第一页查询
    mysql> explain SELECT *
        -> FROM t_follow_timeline_099
        -> force index (idx_uid_status)
        -> WHERE UID = 1833901412800099
        -> AND status = 1
        -> ORDER BY create_time DESC
        -> LIMIT 10;
    +----+-------------+-----------------------+------------+------+----------------+----------------+---------+-------------+------+----------+---------------------------------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-----------------------+------------+------+----------------+----------------+---------+-------------+------+----------+---------------------------------------+
    | 1 | SIMPLE | t_follow_timeline_099 | NULL | ref | idx_uid_status | idx_uid_status | 9 | const,const | 2875 | 100.00 | Using index condition; Using filesort |
    +----+-------------+-----------------------+------------+------+----------------+----------------+---------+-------------+------+----------+---------------------------------------+
    1 row in set, 1 warning (0.00 sec)
    
    # 执行时间
    10 rows in set (0.02 sec)

    修改排序为order by id, 因为二级索引天然带主键,根据id排序直接返回

    #修改成 order by id 
    SELECT *
    FROM t_follow_timeline_099
    force index (idx_uid_status)
    WHERE UID = 1833901412800099
      AND status = 1
    ORDER BY id DESC
    LIMIT 10
    #查询耗时: 0.001135 秒

    去掉force index,出现与主键的merge

    调整索引, 二级索引带上主键

    mysql> alter table t_follow_timeline_099 add index idx_uid_status_id(`uid`,`status`,`id`) , drop index idx_uid_status;
    Query OK, 0 rows affected (1 min 7.67 sec)
    Records: 0 Duplicates: 0 Warnings: 0

    翻页查询 通过id实现

    mysql> explain SELECT * FROM t_follow_timeline_099 WHERE UID = 1833901412800099 AND status = 1 and id < 1090943061166186580 ORDER BY id DESC LIMIT 10;
    +----+-------------+-----------------------+------------+-------+--------------------------------------------------------------------------------------------------+-------------------+---------+------+------+----------+-----------------------+
    ----+------+----------+-----------------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-----------------------+------------+-------+--------------------------------------------------------------------------------------------------+-------------------+---------+--
    
    | 1 | SIMPLE | t_follow_timeline_099 | NULL | range | PRIMARY,idx_uid,idx_uid_timeline_owner,idx_uid_timeline_id,idx_uid_create_time,idx_uid_status_id | idx_uid_status_id | 17 | NULL | 2774 | 100.00 | Using index condition |
    +----+-------------+-----------------------+------------+-------+--------------------------------------------------------------------------------------------------+-------------------+---------+------+------+----------+-----------------------+
    
    
    查询耗时: 0.00155 秒

    优化总结

    mysql 关于order by的语句优化

    1. where条件后面的字段加索引,最好是order by id
    2. 为了保证order by id 不会走到主键索引,需要在组合索引最后一列,加上id
    3. 组合索引最后一列加上id,是为了让server层识别,避免走到了主键的执行计划,在存储引擎层面是一模一样的
    4. 这个实现的方法最优雅,比order by id+0好很多,因为这个绝对走不到主键的索引,而上面的方法还有可能
    5. order by id limit 10 , 目前不能体现在执行计划的row中。eg: idx_uid_status_id , 条件uid = 123 and status =1 order by id limit 10 , key_len 中依然是uid_status的长度,但是没有排序快速返回
  • 相关阅读:
    linux下测试web访问及网络相关的命令
    linux下的数据备份工具rsync讲解
    Centos安装PHP PS:LAMP环境时,为少出错误,先安装一下编译环境
    Centos6.6安装MySQL5.6.24
    Centos6.6安装apache2.4
    bash_profile和bashrc区别
    Centos安装 Apache2.4提示 APR not found的解决办法
    ERROR 1010 (HY000): Error dropping database (can't rmdir './test/', errno: 17)
    MySQL ibdata1文件迁移
    Linux启动/停止/重启Mysql数据库的方法
  • 原文地址:https://www.cnblogs.com/jesper/p/2c1ee93e1f9947d0b2e41cf9f9009665.html
Copyright © 2020-2023  润新知