【深度长文】MySQL排序内部原理探秘(3)

简介: 【深度长文】MySQL排序内部原理探秘

5.2.2 sort_merge_passes

MySQL手册中对Sort_merge_passes的描述只有一句话

Sort_merge_passes
The number of merge passes that the sort algorithm has had to do. If this value is large, you should consider increasing the value of the sort_buffer_size system variable.

这段话并没有把sort_merge_passes到底是什么,该值比较大时说明了什么,通过什么方式可以缓解这个问题。

我们把上面MySQL的外部排序算法搞清楚了,这个问题就清楚了。

其实sort_merge_passes对应的就是MySQL做归并排序的次数,也就是说,如果sort_merge_passes值比较大,说明sort_buffer和要排序的数据差距越大,我们可以通过增大sort_buffer_size或者让填入sort_buffer_size的键值对更小来缓解sort_merge_passes归并排序的次数。

对应的,我们可以在源码中看到证据。

上述MySQL外部排序的算法中第5到第7步,是通过sql/filesort.cc文件中merge_many_buff()函数来实现,第5步单次归并使用merge_buffers()实现,源码摘录如下:

int merge_many_buff(Sort_param *param, Sort_buffer sort_buffer,
                    Merge_chunk_array chunk_array,
                    size_t *p_num_chunks, IO_CACHE *t_file)
{
...
    for (i=0 ; i < num_chunks - MERGEBUFF * 3 / 2 ; i+= MERGEBUFF)
    {
      if (merge_buffers(param,                  // param
                        from_file,              // from_file
                        to_file,                // to_file
                        sort_buffer,            // sort_buffer
                        last_chunk++,           // last_chunk [out]
                        Merge_chunk_array(&chunk_array[i], MERGEBUFF),
                        0))                     // flag
      goto cleanup;
    }
    if (merge_buffers(param,
                      from_file,
                      to_file,
                      sort_buffer,
                      last_chunk++,
                      Merge_chunk_array(&chunk_array[i], num_chunks - i),
                      0))
      break;                                    /* purecov: inspected */
...
}

截取部分merge_buffers()的代码如下,

int merge_buffers(Sort_param *param, IO_CACHE *from_file,
                  IO_CACHE *to_file, Sort_buffer sort_buffer,
                  Merge_chunk *last_chunk,
                  Merge_chunk_array chunk_array,
                  int flag)
{
...
  current_thd->inc_status_sort_merge_passes();
...
}

可以看到:每个merge_buffers()都会增加sort_merge_passes,也就是说每一次对MERGEBUFF (7) 个block归并排序都会让sort_merge_passes加一,sort_merge_passes越多表示排序的数据太多,需要多次merge pass。解决的方案无非就是缩减要排序数据的大小或者增加sort_buffer_size。

打个小广告,在我们的qmonitor中就有sort_merge_pass的性能指标和参数值过大的报警设置。

六、trace 结果解释

说明白了三种排序模式和外部排序的方法,我们回过头来看一下trace的结果。

6.1 是否存在磁盘外部排序

"number_of_tmp_files": 0,

number_of_tmp_files表示有多少个分片,如果number_of_tmp_files不等于0,表示一个sort_buffer_size大小的内存无法保存所有的键值对,也就是说,MySQL在排序中使用到了磁盘来排序。

6.2 是否存在优先队列优化排序

由于我们的这个SQL里面没有对数据进行分页限制,所以filesort_priority_queue_optimization并没有启用

"filesort_priority_queue_optimization": {
              "usable": false,
              "cause": "not applicable (no LIMIT)"
            },

而正常情况下,使用了Limit会启用优先队列的优化。优先队列类似于FIFO先进先出队列。

算法稍微有点改变,以回表排序模式为例。

  • sort_buffer_size足够大

如果LIMIT 限制返回N条数据,并且N条数据比sort_buffer_size小,那么MySQL会把sort buffer作为priority queue,在第二步插入priority queue时会按序插入队列;在第三步,队列满了以后,并不会写入外部磁盘文件,而是直接淘汰最尾端的一条数据,直到所有的数据都正常读取完成。

算法如下:

  1. 根据索引或者全表扫描,按照过滤条件获得需要查询的数据
  2. 将要排序的列值和row ID组成键值对,按序存入中priority queue中
  3. 如果priority queue满了,直接淘汰最尾端记录。
  4. 重复上述步骤,直到所有的行数据都正常读取了完成
  5. 最后一轮循环,仅将row ID写入到结果文件中
  6. 根据结果文件中的row ID按序读取用户需要返回的数据。为了进一步优化性能,MySQL会读一批row ID,并将读到的数据按排序字段要求插入缓存区中(内存大小read_rnd_buffer_size)。
  • sort_buffer_size不够大

N条数据比sort_buffer_size大的情况下,MySQL无法直接利用sort buffer作为priority queue,正常的文件外部排序还是一样的,只是在最后返回结果时,只根据N个row ID将数据返回出来。具体的算法我们就不列举了。


这里MySQL到底是否选择priority queue是在sql/filesort.cc的check_if_pq_applicable()函数中确定的,具体的代码细节这里就不展开了。

另外,我们也没有讨论limit m,n的情况,如果是Limit m,n, 上面对应的“N个row ID”就是“M+N个row ID”了,MySQL的limit m,n 其实是取m+n行数据,最后把M条数据丢掉。


从上面我们也可以看到sort_buffer_size足够大对limit数据比较小的情况,优化效果是很明显的。

七、MySQL其他相关排序参数

7.1 max_sort_length

这里需要区别max_sort_length 和max_length_for_sort_data。

max_length_for_sort_data是为了让MySQL选择”< sort_key, rowid >”还是”< sort_key, additional_fields >”的模式。

而max_sort_length是键值对的大小无法确定时(比如用户要查询的数据包含了 SUBSTRING_INDEX(col1, ‘.’,2))MySQL会对每个键值对分配max_sort_length个字节的内存,这样导致内存空间浪费,磁盘外部排序次数过多。

7.2 innodb_disable_sort_file_cache

innodb_disable_sort_file_cache设置为ON的话,表示在排序中生成的临时文件不会用到文件系统的缓存,类似于O_DIRECT打开文件。

7.3 innodb_sort_buffer_size

这个参数其实跟我们这里讨论的SQL排序没有什么关系。innodb_sort_buffer_size设置的是在创建InnoDB 索引时,使用到的sort buffer的大小。

以前写死为1M,现在开放出来,允许用户自定义设置这个参数了。

八、MySQL排序优化总结

最后整理一下优化MySQL排序的手段

  1. 排序和查询的字段尽量少。只查询你用到的字段,不要使用select * ;使用limit查询必要的行数据;
  2. 要排序或者查询的字段,尽量不要用不确定字符函数,避免MySQL直接分配max_sort_length,导致sort buffer空间不足;
  3. 使用索引来优化或者避免排序;
  4. 增加sort_buffer_size大小,避免磁盘排序;
  5. 不得不使用original 排序算法时,增加read_rnd_buffer_size;
  6. 字段长度定义合适就好(避免过长);
  7. tmpdir建议独立存放,放在高速存储设备上。

写到这里,大家可以回顾一下文章开头的那八个问题,如果回答不了这些问题,说明其实你没有真正的理解透MySQL的排序,或者说我们的这篇文章写的太乱了~

九、参考文献

https://dev.mysql.com/doc/refman/5.7/en/order-by-optimization.html

http://coding-geek.com/how-databases-work/

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
关系型数据库 MySQL 数据库
MySQL Innodb Purge简介
前言 为什么MySQL InnoDB需要Purge操作?明确这个问题的答案,首先还得从InnoDB的并发机制开始。为了更好的支持并发,InnoDB的多版本一致性读是采用了基于回滚段的的方式。另外,对于更新和删除操作,InnoDB并不是真正的删除原来的记录,而是设置记录的delete mark为1。
9201 1
|
Oracle JavaScript 关系型数据库
MySQL8 OCP可以个人报名了,内卷时代考个证提升自己
MySQL8 OCP可以个人报名了,内卷时代考个证提升自己
2873 0
MySQL8 OCP可以个人报名了,内卷时代考个证提升自己
|
SQL 关系型数据库 MySQL
(十七)SQL优化篇:如何成为一位写优质SQL语句的绝顶高手!
(Structured Query Language)标准结构化查询语言简称SQL,编写SQL语句是每位后端开发日常职责中,接触最多的一项工作,SQL是关系型数据库诞生的产物,无论是什么数据库,MySQL、Oracle、SQL Server、DB2、PgSQL....,只要还处于关系型数据库这个范畴,都会遵循统一的SQL标准,这句话简单来说也就是:无论什么关系型数据库,本质上SQL的语法都是相同的,因为它们都实现了相同的SQL标准,不同数据库之间仅支持的特性不同而已。
687 8
|
11月前
|
存储 人工智能 搜索推荐
详解MySQL字符集和Collation
MySQL支持了很多Charset与Collation,并且允许用户在连接、Server、库、表、列、字面量多个层次上进行精细化配置,这有时会让用户眼花缭乱。本文对相关概念、语法、系统变量、影响范围都进行了详细介绍,并且列举了有可能让字符串发生字符集转换的情况,以及来自不同字符集的字符串进行比较等操作时遵循的规则。对于最常用的基于Unicode的字符集,本文介绍了Unicode标准与MySQL中各个字符集的关系,尤其详细介绍了当前版本(8.0.34)默认字符集utf8mb4。
2634 82
|
NoSQL Redis 监控
redis-shake数据同步&迁移&备份导入导出工具使用介绍
redis-shake是阿里云Redis&MongoDB团队开源的用于redis数据同步的工具。
72599 4
redis-shake数据同步&迁移&备份导入导出工具使用介绍
|
缓存 关系型数据库 MySQL
【MySQL】read_rnd_buffer_size=4M,是干什么的?底层原理是什么?
【MySQL】read_rnd_buffer_size=4M,是干什么的?底层原理是什么?
1471 0
|
存储 关系型数据库 MySQL
(十五)MySQL命令大全:以后再也不用担心忘记SQL该怎么写啦~
相信大家在编写SQL时一定有一个困扰,就是明明记得数据库中有个命令/函数,可以实现自己需要的功能,但偏偏不记得哪个命令该怎么写了,这时只能靠盲目的去百度,以此来寻找自己需要的命令。
551 28
|
SQL 算法 关系型数据库
(十)全解MySQL之死锁问题分析、事务隔离与锁机制的底层原理剖析
经过《MySQL锁机制》、《MySQL-MVCC机制》两篇后,咱们已经大致了解MySQL中处理并发事务的手段,不过对于锁机制、MVCC机制都并未与之前说到的《MySQL事务机制》产生关联关系,同时对于MySQL锁机制的实现原理也未曾剖析,因此本篇作为事务、锁、MVCC这三者的汇总篇,会在本章中补全之前空缺的一些细节,同时也会将锁、MVCC机制与事务机制之间的关系彻底理清楚。
454 2
|
SQL 算法 关系型数据库
(二十)MySQL特性篇:2022年的我们,必须要懂的那些数据库新技术!
 MySQL数据库从1995年诞生至今,已经过去了二十多个年头了,到2022.04.26日为止,MySQL8.0.29正式发行了GA版本,在此之前版本也发生了多次迭代,发行了大大小小N多个版本,其中每个版本中都有各自的新特性,所有版本的特性加起来,用一本书的篇幅也无法完全阐述清楚,因此本章主要会挑重点特性来讲,具体各版本的特性可参考MySQL官网的开发手册。
537 1
|
存储 SQL 关系型数据库
(十四)全解MySQL之各方位事无巨细的剖析存储过程与触发器!
前面的MySQL系列章节中,一直在反复讲述MySQL一些偏理论、底层的知识,很少有涉及到实用技巧的分享,而在本章中则会阐述MySQL一个特别实用的功能,即MySQL的存储过程和触发器。
372 0