字段排序显示。市民表,城市是“杭州”,姓名排序返回前 1000 姓名、年龄。
select city,name,age from t where city='杭州' order by name limit 1000 ;
一、全字段排序
避免全表扫描, city 加上索引。explain :
Extra 的“Using filesort”表示需要排序,给每个线程分配一块内存用于排序,sort_buffer。
city='杭州’,从 ID_X 到 ID_(X+N) 。执行流程(全字段排序):
1. 初始化 sort_buffer,放入 name、city、age ;
2. 从索引 city 找满足 city='杭州’条件主键 id, ID_X;
3. 到主键 id 索引取出整行,取 name、city、age 存入 sort_buffer ;
4. 重复2,从索引 city 取下一个记录的主键 id;
5. 重复步骤 3、4 直到 不满足, ID_Y;
6. 对 sort_buffer 按照字段 name 做快速排序;
7. 取前 1000 行返回给客户端。
排序在内存/外部,取决所需的内存和参数sort_buffer_size:为排序开辟的内存(sort_buffer)的大小。内存放不下,利用磁盘临时文件辅助。
确定排序语句是否用临时文件:
1.只对本线程有效:SET optimizer_trace='enabled=on';
2.保存 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;
3.查看 OPTIMIZER_TRACE 输出:SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`\G
4.保存 Innodb_rows_read 当前值:select VARIABLE_VALUE into @b from performance_schema.session_status where variable_name = 'Innodb_rows_read';
5.计算 Innodb_rows_read 差值:select @b-@a;
OPTIMIZER_TRACE 结果来确认:
number_of_tmp_files 排序过程临时文件数。为什么需要 12 个文件?内存放不下时,放临时文件中(归并排序),将需要排序的数据分成 12 份,每一份单独排序后存在这些临时文件中。再合并成有序大文件。number_of_tmp_files =0,可直接内存中完成。
1.sort_buffer_size越小,份数越多,number_of_tmp_files 越大。
2.examined_rows=4000:参与排序行数
3.sort_mode 的packed_additional_fields:排序过程对字符串做“紧凑”处理。即使定义varchar(16),排序过程还是按实际长度分配空间。
4.select @b-@a的返回结果是 4000
避免干扰结论,internal_tmp_disk_storage_engine 设置成 MyISAM。否则为 4001。
查OPTIMIZER_TRACE 时,用临时表,默认 InnoDB。从临时表取出来的时候,会让Innodb_rows_read值加 1。
二、rowid 排序(InnoDB 不优先选择)
上面只对原表读一遍,剩下sort_buffer 和临时文件执行。问题:返回字段多,sort_buffer字段数多,内存行数少,临时文件多,排序性能差。
单行大,效率不好。MySQL 另外一种算法:
SET max_length_for_sort_data = 16;
MySQL 用于排序的行数据的长度的一个参数。单行长度超过这个值,MySQL 就认为单行太大,换个算法。
city、name、age 总长度 36,新的算法放入 sort_buffer 的字段,只要排序列( name 字段)和主键 id。
少了 city 和 age ,不能直接返回,流程:
1. 初始化 sort_buffer,放入name 和 id;
2. 索引 city 找第一个city='杭州’ 主键 id, ID_X;
3. 主键 id 索引取整行,name、id 存入 sort_buffer 中;
4. 重复2,索引 city 取下一个记录主键 id;
5. 重复3、4 直到不满足 city='杭州’, ID_Y;
6. 对 sort_buffer 中 name 排序;
7. 遍历排序结果,取前 1000 行,按照 id 的值回到原表中取出 city、name 和 age 三个字段返回给客户端。
rowid 排序多访问了一次表 t 的主键索引,步骤 7。
id到原表查到 city、name 和 age 结果,不在服务端耗费内存存储结果,直接返回给客户端。
排序数据4000 。但select @b-@a变成 5000 。除排序外,去原表取值。limit 1000,多读 1000 行。
sort_mode 变成
number_of_tmp_files 变 10 ,每行变小,总数据量小,临时文件也少。
三、全字段排序 VS rowid 排序
内存小,影响排序效率,用 rowid 排序算法,可以排序更多行,再回到原表取数据。
内存大,全字段排序,都放sort_buffer 中,不用再回到原表取数据。
如果内存够,多利用,尽量减少磁盘访问。
天然递增排序,不用再排序:
创建 city 和 name 的联合索引,对应SQL 语句:
alter table t add index city_user(city, name);
只要 city =杭州,name 一定是有序的。查询过程的流程:
1. 从索引 (city,name) 找到第一个满足 city='杭州’条件的主键 id;
2. 到主键 id 索引取出整行,取 name、city、age 三个字段的值,直接返回;
3. 重复2.从索引 (city,name) 取下一个记录主键 id;
4. 重复步骤 2、3,直到查到第 1000 条记录,或者是不满足 city='杭州’条件时循环结束。
不需要临时表和排序。
Extra 字段中没有 Using filesort 了,也就是不需要排序了。而且由于 (city,name) 这个联合索引本身有序,找到满足条件前 1000 条记录退出。
覆盖索引:索引信息满足查询,不需回到主键索引上去取数据
创建一个 city、name 和 age 联合索引:alter table t add index city_user_age(city, name, age);city 相同行,name 递增排序
1. 从索引 (city,name,age) 找到第一个满足 city='杭州’条件的记录,取出其中city、name 和 age 这三个字段的值,直接返回;
2. 从索引 (city,name,age) 取下一个记录,直接返回;
3. 重复执行步骤 2,直到查到第 1000 条记录,或者是不满足 city='杭州’条件时循环结束。
Extra里面多了“Using index”,用覆盖索引,性能快。
不是每个查询能用上覆盖索引,索引要维护。需要权衡。
小结
order by 种算法流程。
清楚每个语句排序逻辑怎么实现,分析出最坏情况下,每个语句执行对系统资源消耗,不犯低级错误。
思考题
city_name(city, name)联合索引,查两个城市中市民姓名:
mysql> select * from t where city in ('杭州'," 苏州") order by name limit 100;
1.执行有排序过程吗?
2.不需要排序方案,怎么实现呢?
3.分页需求,显示101 页, “limit 10000,100”, 实现方法?
答:
1.不需排序
2. 执行 select * from t where city=“苏州” order by name limit 100;存进内存数组 B。
3. A 和 B 两个有序数组,归并排序,name 最小前 100 值,需要结果。
select * from t where city=" 杭州" order by name limit 10100;和
select * from t where city="苏州 " order by name limit 10100。
数据量大,两个连接同时一行行读结果,归并排序拿到两个结果集,按顺序取第 10001~10100 的 name 值,就是需要的结果了。
明显损失,返回数据量大。改成:select id,name。。。
归并排序得按 name 顺序第 10001~10100 的 name、id 的值,拿这100 个 id查出所有记录。
根据性能需求、复杂度做权衡。
评论1
select * from t order by create_time desc ;优化器会根据 order by create_time 选择使用 create_time 索引么
误以为优化器根据 where 后字段条件选索引 ,没有where 时以为不会走索引 。
本地建表加20w数据 ,explain发现走全表没有走索引,analyze table重新统计 ,再次查询果走索引
where和 order共同影响,加联合索引后,语句执行逻辑
评论2
场景 ,插入时间倒序查看 ,除分页 , 没where 的条件 ,只有主键索引 。
给 create_time 创建索引,实际没用 。 本身没用到 create_time 索引,增加维护成本
1. 初始化 sort_buffer 内存
2. 没索引 ,扫出全表数据到 sort_buffer 中
2. 如果内存够则直接内存按时间排序
3. 如果内存不够则按数据量分成不同文件分别按时间排序后整合
4. 根据数量分页查询数量 回聚集索引中用 ID 查询数据
5. 返回
问题一 :无条件列,除了全表扫,建立索引办法
问题二 : 加入 group by , 数据如何走
问题三 :bigInt(20) 、 tinyint(2) 、varchar(32) 带数字与不带数字有何区别?
问题一 :
1)全表扫描,两种选择一排序。二级索引,再回表成本比全表扫描排序更高。
2)无条件查询 limit m.m值小,二级索引.
优化器根据索引有序性去回表查,得到m条数据,终止循环,比全表扫描小,走。
问题二:
group by a,a上用索引,rowid排序。
group by limit,不能用索引,堆排序
group by a,a上有索引,根据选取值不同,索引扫描方式不同
select * from t group by a 索引全扫描,为什么选择走索引全扫描
select a from t group by a 索引松散扫描,不用扫描每行值
问题三:都不影响能存储值。
ps:长连接,sql申请sort_buffer_size会话级别内存,sql成功执行完,连接sleep状态。占用的内存空间不会释放? 内存排序后free掉还给系统