表结构
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 = '商品订单表';
全字段排序
将所有的字段写入buffer中排序后返回
select city, order_num, user_code from `order` where city='广州'
order by order_num limit 1000;
- explain 这个语句 , extra 会出现 Using filesort, 这个 filesort 并不是说通过磁盘文件进行排序,而只是告诉我们进行了一个排序操作而已
出现order by关键字 MySQL 会给每个线程分配一块内存用于排序,称为 sort_buffer。
- 执行过程
- 初始化 sort_buffer,放入 city、order_num、user_code 这三个字段;
- 从索引 city 找到第一个满足 city=' 广州’条件的主键 id,也就是图中的 ID_3;
- 到主键 id 索引取出整行,取 city、order_num、user_code 三个字段的值,存入 sort_buffer 中;
- 从索引 city 取下一个记录的主键 id;
- 重复步骤 3、4 直到 city 的值不满足查询条件为止
- 对 sort_buffer 中的数据按照字段 order_num 做快速排序;
- 按照排序结果取前 1000 行返回给客户端。
排序的操作(第6步)可能在内存中完成, 也可能在文件中完成(外部排序),取决于参数
sort_buffer_size //也就是mysql开辟的sort_buffer大小
MySQL 为排序开辟的内存(sort_buffer)的大小。如果要排序的数据量小于 sort_buffer_size,排序就在内存中完成。但如果排序数据量太大,内存顶不住,就得磁盘临时文件辅助排序。
sort_buffer_size尽量调大,尽量减少磁盘临时文件排序可能
rowid 排序
将主键排序后,回表取数据返回
我们需要查询的字段都要放到 sort_buffer 中,如果查询的字段多了起来,内存占用升高,就会很容易打满 sort_buffer 。这时,就要用很多的临时文件辅助排序,导致性能降低。
我们思考的方向应该是降低排序的 单行长度,哪有没有方法能做到呢?
MySQL 之所以走全字段排序是由 max_length_for_sort_data 控制的,它的 默认值是 1024。
show variables like 'max_length_for_sort_data';
max_length_for_sort_data 尽量调大,减少回表次数,rowid排序需要额外回表查询数据
- 进行rowid排序 本文示例中 city,order_num,user_code 长度 = 16+4+16 =36 < 1024, 所以走的是全字段排序。我们来改下这个参数,改小一点,
SET max_length_for_sort_data = 16;
当单行的长度超过这个值,MySQL 就认为单行太大,要换一个算法。原来 city、user_code、order_num 占用的长度是 36,显然放不下全部查询字段了。
这时就要换算法:sort_buffer 只存 order_num 和 id 字段。
- 初始化 sort_buffer,确定放入两个字段,即 order_num 和 id;
- 从索引 city 找到第一个满足 city=' 广州’条件的主键 id,也就是图中的 ID_3
- 回表,取 order_num、id 这两个字段,存入 sort_buffer 中;
- 从索引 city 取下一个记录的主键 id;
- 重复步骤 3、4 直到不满足 city=' 广州’条件为止,也就是图中的 ID_X;
- 对 sort_buffer 中的数据按照字段 order_num 进行排序;
- 遍历排序结果,取前 1000 行,再次回表取出 city、order_num 和 user_code
- 三个字段返回给客户端。
可以发现:比全字段排序多了1次回表操作,但是sort_buffer不大,不用进行临时文件排序
全字段, rowid排序对比
- rowid 排序中,排序过程一次可以排序更多行,但是需要回表取数据。
- 对于 InnoDB 表来说,rowid 排序会要求回表多造成磁盘读,因此不会被优先选择。
索引优化排序
增加符合索引
alter table `order` add index city_order_num_index(city, order_num);
- 从索引 (city,order_num) 找到第一个满足 city=' 广州’条件的主键 id;
- 回表,取 city、order_num、user_code 三个字段的值,作为结果集的一部分直接返回
- 从索引 (city,order_num) 取下一个记录主键 id;
- 重复步骤 2、3,直到查到第 1000 条记录,或者是不满足 city='广州'条件时循环结束。
因为order_num字段本身就是有序的,所以不需要排序,只需要一次回表:
继续优化
查询字段,加到组合索引中呀,对应到这张表,就是把 user_code 也加到组合索引中:
alter table `order` add index city_order_num_user_code_index(city, order_num, user_code);
因为索引覆盖,不用回表取值,速度最快
没有 where 条件,order by 字段需要加索引吗?
select * from student order by create_time;
无条件查询的话,即使 create_time 上有索引,也不会使用到。因为 MySQL 优化器认为走普通二级索引,再去回表成本比全表扫描排序更高。所以选择走全表扫描,然后根据全字段排序或者 rowid 排序来进行。