更好理解ORDER BY内部排序和性能优化-mysql

sql 复制代码
create table a(
    city varchar(16) not null,
    name varchar(16) not null,
    id int(11) not null,
    primary key(id)
)

一、全字段排序

比如在执行一个

vbnet 复制代码
select city, name, ID from T 
where city='广州' 
order by name limit 200;
  • city 字段有索引(用来快速过滤出 "广州" 的记录)
  • name 字段没有索引(无法直接按 name 有序返回)

    触发全字段排序,mysql就会把需要返回的字段(city,name,UID)放到一个 sort-buffer的内存,按这个内存块的name排序。

流程

1、先利用索引过滤

先通过索引 city,找到city= 杭州对应的主键ID。

2、根据主键回表取返回字段

根据主键ID,读取整行数据,只取需要返回的字段city,name,id,

放入内存块sort-buffer

3、在sort-buffer排序

sort_buffer 是 MySQL 为每个线程分配的一块排序专用内存

如果 sort_buffer 能装下所有待排序数据就直接排序。

如果数据太多,就会把数据分成多份,每份排序后存到临时磁盘文件中,把这些有序文件合并成一个大的有序结果

4、返回结果

二、rowid排序

触发条件:当单行数据长度超过 max_length_for_sort_data 参数时,MySQL 会认为单行太大,不适合把所有返回字段都塞进 sort_buffer,而改用 rowid 排序:

注:max_length_for_sort_data是用户自己设置。

ini 复制代码
set max_length_for_sort_data = 20;

流程

把需要排序的字段(如name)和主键ID放入sort-buffer

排序完成后,再根据排序后的ID回表提取其他需要返回的字段(city,name,id)。

三、为什么需要排序?

在只有 city 索引的情况下:

  • city 索引只保证了 city 字段的有序性,但满足 city='杭州' 的行,name 字段是无序的。
  • 因此 order by name 必须通过排序(全字段或 rowid)来实现,

四、全字段排序 vs rowid 排序

全字段排序

原理(简):把需要返回的字段在sort-buffer排序。

pre:无需回表,一次读取

pro: 单行数据大时,sort_buffer 很快占满,需依赖磁盘临时文件,性能差

rowid排序

原理(简):只把order by的字段和主键ID在sort-buffer排序,再回表提取其他返回字段。

pre:内存占用小,减少临时文件数量

pro: 多一次回表操作,增加 IO 开销

需要采用全字段排序和rowid排序是因为原数据是无序的。

五、联合索引

如果在city索引取的行是按照name递增排序,就不用做排序了(全字段排序ORrowid排序)。

所以可以在创建表后,表外添加索引,创建一个city和name的联合索引,

sql 复制代码
alter table a add index city_user(city,name);

当我们创建 (city, name) 联合索引后: 索引的结构是先按 city 排序,再按 name 排序。

------------ city='广州' 的行,因为name 天然是有序的。 所以此时 order by name 不再需要排序,直接按索引顺序读取即可,性能大幅提升。

流程

从 (city, name) 索引找到第一个 city='广州' 的行,获取主键 id。

回表到主键索引,取出 id 字段,与索引中的 city, name 一起返回。

按索引顺序取下一行,重复步骤 2,直到取够 1000 行。

六、覆盖索引

如果我们进一步创建 (city, name, di) 覆盖索引:

这样的话,索引中就包含了查询的所有需要返回的字段,就不用回表操作,直接读取索引数据。

输出explain 结果中的 Extra 字段显示 Using index,是覆盖索引的标志,表示性能达到最优。

sql 复制代码
alter table a add index city_user(city,name,id);

流程

  1. 从 (city, name, age) 索引找到第一个 city='杭州' 的行,直接取出 city, name, age 返回。
  2. 按索引顺序取下一行,重复步骤 1,直到取够 1000 行。

结论

排序是成本很高的操作,所以能通过索引避免就尽量避免。

覆盖索引是性能优化的最优手段,但需要权衡索引的维护成本,和查询收益,不是所有场景都适合。

相关推荐
王中阳Go6 小时前
两个入口答案不一致先别调模型:6 行入口口径对账表(附脚本)
人工智能·后端·python·大语言模型·1024程序员节
码事漫谈7 小时前
消除AI幻觉,普通人能做的五件事
后端
程序员塔塔喵9 小时前
【MySQL篇|第一篇】索引失效的情况
数据库·mysql·索引失效
paopaokaka_luck9 小时前
宠物门店经营管理系统(销售与库存联动、进货自动记账、 退换与退款关联、员工个人业绩、ECharts 经营分析、滞销识别与客户分层、宠物档案年龄计算)
前端·javascript·spring boot·mysql·echarts·mybatis·1024程序员节
金銀銅鐵9 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(下)
java·后端·ai编程
Mk5987811 小时前
给 Agent 装上对话框 —— AgentScope-Java 接入 Spring Boot 与 SSE 流式实战
后端
付威202311 小时前
从 Loop 到 Runtime:rpi Agent Loop 与 Cordis 的边界、取舍与组合
后端
IT_陈寒11 小时前
Redis的LRU淘汰策略坑了我一整天
前端·人工智能·后端
paopaokaka_luck11 小时前
心理咨询管理系统(WebSocket实时沟通、基于用户的协同过滤、 ECharts多维分析、 预约业务流程、预约校验与定时处理)
java·vue.js·spring boot·mysql·spring·echarts·1024程序员节
2301_322414280411 小时前
springboot农产品交易平台的设计与实现08055-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·spring boot·redis·mysql·tomcat·maven·mybatis