面试官:order by 怎么优化?(上)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 面试官:order by 怎么优化?

01 前言


刚换了新工作,用了两周时间准备,在 3 天之内拿了 5 个 offer,最后选择了广州某互联网行业独角兽 offer,昨天刚入职。这几天刚好整理下在面试中被问到有意思的问题,也借此机会跟大家分享下。


这家企业的面试官有点意思,一面是个同龄小哥,一起聊了两个小时(聊到我嘴都干了)。二面是个从阿里出来的架构师,视频面试,我做完自我介绍之后,他一开场就问我:


对 MySQL 熟悉吗?


我一愣,随之意识到这是个坑。他肯定想问我某方面的原理了,恰好我研究过索引。就回


答:


对索引比较熟悉。


他:


order by 是怎么实现排序的?


还好我又复习,基本上排序缓冲区、怎么优化之类的都答到点子上。今天也跟大家盘一盘 order by,我将从原理讲到最终优化,给大家聊聊 order by,希望对你有所帮助。


国际惯例,先上思维导图。PS:文末有福利


640.png

1.2 先举个栗子


现在有一张订单表,结构是这样的:


CREATE TABLE `order` (
id INT ( 11 ) NOT NULL AUTO_INCREMENT COMMENT '主键',
user_code VARCHAR ( 16 ) NOT NULL COMMENT '用户编号',
goods_name VARCHAR ( 64 ) NOT NULL COMMENT '商品名称',
order_date TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间',
city VARCHAR ( 16 ) DEFAULT NULL COMMENT '下单城市',
order_num INT ( 10 ) NOT NULL COMMENT '订单号数量',
PRIMARY KEY ( `id` ) 
) ENGINE = INNODB AUTO_INCREMENT = 100 DEFAULT CHARSET = utf8 COMMENT = '商品订单表';


造点数据:


// 第一步:创建函数
delimiter //
DROP PROCEDURE
IF
 EXISTS proc_buildata;
CREATE PROCEDURE proc_buildata ( IN loop_times INT ) BEGIN
DECLARE var INT DEFAULT 0;
WHILE
 var < loop_times DO
 SET var = var + 1;
INSERT INTO `order` ( `id`, `user_code`, `goods_name`, `order_date`, `city` , `order_num`)
VALUES
 ( var, var + 1, '有线耳机', '2021-06-20 16:46:00', '杭州', 1 );
END WHILE;
END // delimiter;
// 第二步:调用上面生成的函数,即可插入数据,建议大家造点随机的数据。比如改改城市和订单数量
CALL proc_buildata(4000);


我生成的数据是这样的:


640.png


现有需求:查出 618 期间,广州的小伙伴的订单数量和用户编号,并按照订单数量升序,只要 1000 条


根据需求可以得出以下 SQL,相信小伙伴都很熟悉了。


select city, order_num, user_code from `order` where city='广州' order by order_num limit 1000;


那这个语句是怎么执行的呢?有什么参数可以影响它的行为吗?


02 全字段排序


得到这个需求,我第一反应是先给 city 字段加上索引,避免全表扫描:


ALTER TABLE `order` ADD INDEX city_index ( `city` );


用 explain 看看执行情况


640.png


注意到最后一个 extra 字段的结果是:Using filesort,表示需要排序。 其实 MySQL 会给每个线程分配一块内存用于排序,称为 sort_buffer


为了更直观了解排序的执行流程,我粗略画了个 city 索引的图示:


640.png


可见,现在满足 sql 条件的就是 ID-3 到 ID-X 这一段数据。sql 的整个流程是这样的:


  • 1、初始化 sort_buffer,放入 city、order_num、user_code 这三个字段;
  • 2、从索引 city 找到第一个满足 city=' 广州’条件的主键 id,也就是图中的 ID_3;
  • 3、到主键 id 索引取出整行,取 city、order_num、user_code 三个字段的值,存入 sort_buffer 中;
  • 4、从索引 city 取下一个记录的主键 id;
  • 5、重复步骤 3、4 直到 city 的值不满足查询条件为止,对应的主键 id 也就是图中的 ID_X;
  • 6、对 sort_buffer 中的数据按照字段 order_num 做快速排序;
  • 7、按照排序结果取前 1000 行返回给客户端。


这个过程称之为全字段排序,画个图,长这样:


640.png


其中,按 order_num 排序这个步骤,可能在内存中完成,也可能需要使用外部排序,这取决于排序所需的内存和参数 sort_buffer_size


也就是 MySQL 为排序开辟的内存(sort_buffer)的大小。如果要排序的数据量小于 sort_buffer_size,排序就在内存中完成。但如果排序数据量太大,内存顶不住,就得磁盘临时文件辅助排序。


当然,在 MySQL5.7 以上版本可以用下面介绍的检测方法(后面都有用到),来查看一个排序语句是否使用了临时文件。PS:这里的语句直接复制到 navicat 执行即可,要一起执行(都复制进去,点下执行)


/* 打开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, order_num, user_code from `order` where city='广州' order by order_num limit 1000; 
/* 查看 OPTIMIZER_TRACE 输出 */
SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`;
/* @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;


执行完之后,可从 OPTIMIZER_TRACE 表的 TRACE 字段得到以下结果:


640.png


其中 examined_rows 表示需要排序的行数 6883;sort_buffer_size 就是排序缓冲区的大小;sort_buffer_size 就是我 MySQL 的排序缓冲区大小 256 KB。


另外,sort_mode 的值是 packed_additional_fields,它表示排序过程对数据做了优化,也就是数据占用多少就算多少内存。举个栗子:不存在数据定义长度 16,就按这个长度算,如果数据只占 2,只会按长度 2 分配内存。


number_of_tmp_files 代表的是用了几个外部文件来辅助排序。我这里是用了两个,内存放不下时,就使用外部排序,外部排序一般使用归并排序算法。可以这么简单理解, MySQL 将需要排序的数据分成 2 份,每一份单独排序后存在这些临时文件中。然后把这 2 个有序文件再合并成一个有序的大文件


最后一个查询语句,select @b-@a 的值是 6884,表示整个过程只扫描了 6883 行,为啥显示 6884?


因为查询 OPTIMIZER_TRACE 表时,需用到临时表;而 InnDB 引擎把数据从临时表取出时,Inndb_rows_read 值会加 1。


所以,把 internal_tmp_disk_storage_engine 设置为 MyISAM 可解决此问题。



相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
6月前
|
存储 SQL 关系型数据库
京东面试:mysql深度分页 严重影响性能?根本原因是什么?如何优化?
京东面试:mysql深度分页 严重影响性能?根本原因是什么?如何优化?
京东面试:mysql深度分页 严重影响性能?根本原因是什么?如何优化?
|
12月前
|
SQL 关系型数据库 MySQL
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
MySQL慢查询优化、索引优化,是必知必备,大厂面试高频,本文深入详解,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验分享。
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
|
8月前
|
机器学习/深度学习 人工智能 JSON
Resume Matcher:增加面试机会!开源AI简历优化工具,一键解析简历和职位描述并优化
Resume Matcher 是一款开源AI简历优化工具,通过解析简历和职位描述,提取关键词并计算文本相似性,帮助求职者优化简历内容,提升通过自动化筛选系统(ATS)的概率,增加面试机会。
915 18
Resume Matcher:增加面试机会!开源AI简历优化工具,一键解析简历和职位描述并优化
|
7月前
|
人工智能 算法 数据库
美团面试:LLM大模型存在哪些问题?RAG 优化有哪些方法?_
美团面试:LLM大模型存在哪些问题?RAG 优化有哪些方法?_
|
11月前
|
并行计算 算法 安全
面试必问的多线程优化技巧与实战
多线程编程是现代软件开发中不可或缺的一部分,特别是在处理高并发场景和优化程序性能时。作为Java开发者,掌握多线程优化技巧不仅能够提升程序的执行效率,还能在面试中脱颖而出。本文将从多线程基础、线程与进程的区别、多线程的优势出发,深入探讨如何避免死锁与竞态条件、线程间的通信机制、线程池的使用优势、线程优化算法与数据结构的选择,以及硬件加速技术。通过多个Java示例,我们将揭示这些技术的底层原理与实现方法。
641 3
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
198 20
Android经典面试题之图片Bitmap怎么做优化
|
SQL 关系型数据库 MySQL
面试官:limit 100w,10为什么慢?如何优化?
面试官:limit 100w,10为什么慢?如何优化?
454 2
面试官:limit 100w,10为什么慢?如何优化?
|
存储 前端开发 JavaScript
面试时让你手写一个防抖和节流优化,你能写出来吗?(二)
面试时让你手写一个防抖和节流优化,你能写出来吗?(二)
|
缓存 Prometheus 监控
Java面试题:如何监控和优化JVM的内存使用?详细讲解内存调优的几种方法
Java面试题:如何监控和优化JVM的内存使用?详细讲解内存调优的几种方法
305 3
|
运维 监控 算法
[go 面试] 优化线上故障排查与性能问题的方法
[go 面试] 优化线上故障排查与性能问题的方法