7 mysql对order by group by join limit count的实现

排序 order by

索引是天然有序的,如果对索引排序,mysql只需要选择升序查询或降序查询即可,不需要专门的排序,效率非常高。

如果排序字段不是索引,mysql就需要将数据查出来再进行排序。

sort_buffer_size

在内存中排序,就肯定会消耗内存空间,mysql的sort_buffer_size就是设置这个空间的大小,sort_buffer_size并非全局共享,是每个线程独占,如果sort_buffer过大,而并发线程又多,就会内存不足。

当sort_buffer满了之后,就会写入临时文件。

调用方法

filesort()

估算排序总行数

estimate_rows_upper_bound()

总大小 = 总叶子节点数 * 页大小

行数 = 2 * (总大小 / 行大小)

总叶子节点数是stat值非实时数据

行大小是字段元数据设置的大小,非实际大小。

所以varcahr设置的值大小, 直接影响到排序的策略。

判断优先级队列是否可用

check_if_pq_applicable()

如果优先级队列可用,查出一条,就放到优先级队列里面排序,然后再查,不用全部查出来,然后排序,效率比较高。

如果不可用,那就用传统的排序方式,通常都是走的这个方式。

查出数据,存放到sort_buffer,直到查询结束或sort_buffer满了之后,save_index()对数据进行排序。

find_all_keys

参数:max_sort_length 默认1024

这个参数是限制每个记录最大排序值,超过这个记录,mysql就只取id和排序字段放到sort_buffer里面,排好序后,再回表查询。

没超过时,就整行放进去排序,排好序就返回,不用回表了。

varchar会按最大计算,并不是按实际大小计算

因为varchar的实际长度是存放在行记录中的,mysql不可能提前查询出行数据,然后在计算实际长度;

而是通过字段的元数据,直接计算大小。

group by

内存分组 ha_heap

通过HashMap数据结构分组

max_heap_table_size 内存表最大值 默认16M

tmp_table_size 临时表大小 默认 16M

取两个变量的最小值为内存临时的大小。

table->file ha_heap

ha_write_row()

写入行,写入到了ha_heap中,并非innodb

write_row ha_heap::write_row()

ha_heap 是一个内存临时表,一个hashMap结构

临时文件分组, innodb临时表

创建一个临时表,分组字段作为主键,主键具有唯一性,当插入重复主键时,就会发送数据合并。

最终临时表的数据就是一份不重复的数据。

mysql会优先使用内存表分组

如果数据量太大,超出最大值,就会在磁盘的临时文件进行分组。

这时候就会利用innodb进行临时表管理。

将内存中已插入的转移到磁盘,后续的直接插入到磁盘临时表。

create_innodb_tmp_table()

create_table()

排序

group by 默认会进行排序

分组后的数据,会经过排序逻辑

统计

在做分组插入临时表时,

会先查询一下临时表是否已有数据,

如果有,就取出来,和要插入的数据做个计算,然后更新(update_row)已有的数据。

如果没有,直接插入

相当于不断在做reduce操作

效率

内存临时表的效率要远高于磁盘临时表

如果某个sql group by 的时间很长, 就要考虑参与计算的数据是否超过了默认的16M.

如果超过了,要么优化sql,减少数据量, 要么增加内存表的大小。

join

join过程

join_buffer

默认256kb,每个线程独占

join过程会面临先查哪个表,再查哪个表,这个是mysql优化器决定的,没有固定。

可以通过执行计划explain看到先查询的哪个表。

通常mysql会选择先查询记录较少的表,这样可以先把它放到join buffer里面,太大可能就放不进去。

nested-loop

通过两层嵌套循环实现

不会使用到join_buffer,通过执行计划explain,如果没有using join buffer 那么就肯定是用的这个。

过程:

外层循环称之为驱动表,内层循环为被驱动表

当驱动表查询出一条后,被驱动表针对外层结果搜索一次。

被驱动表进行查询的条件也就是on的部分,如果所关联的内层表字段不是索引,那么每一次驱动表查询,就会进行一次被驱动表的全表扫描。若驱动表查询数量为n,那么被驱动表被全表扫描的次数为n,所以关联查询一定要建立索引。

如果两表没有on绑定,那么会产生卡迪尔乘积 n*m条结果

如果有外键关联,那么结果数量取决于驱动表。

block nested-loop

针对nested-loop的优化操作, 执行计划有显示using join buffer

上面nested-loop是逐个循环遍历的(for(i=0;i<n;i++),在无索引的情况下,有n次循环就有n次全表扫描,代价太大。

block nested-loop就进行了批量遍历(for(i=0;i<n;i+=m),驱动表每次循环,查询出m条数据,载入到内存(join buffer),然后进行一次被驱动表全表扫描,再进行数据关联。 那么被驱动表的总全表扫描次数为n/m次(m取决于BNL的内存设定大小,内存越大m就越大,次数也越少)。

子查询

简单子查询会被优化成join查询

select * from t1 where id in (select sid from t2 where type=1)

select * from t1 left join t2 on t2.sid=t1.id where t2.type=1

from 部分的子查询,需要建立临时表

内存临时表

磁盘临时表

limit

对innodb来说,还是全表扫描,但是在mysql层面会有一个计数器,用来计算limit,只返回满足次序的记录。

sql_class.cc :: send_data()

send_records, select_limit_cnt

mysql这么处理,会造成无论是取第几页,前面的记录都会被遍历,这就产生了深分页问题。

当页数非常大的时候,mysql就会很慢,mysql层面是无法处理的,只能在其他方面解决。

1 业务上避免深分页,只提供前100页数据,隐藏跳页操作,如电商商品的浏览

2 如果有递增的id或其他索引,可以利用递增索引,提前计算好目标页的索引,然后通过in查询

3 用一个较小的表记录次序,再使用2的方法。

count

count() = count(1) ≈ count(id) < count(字段)
count(
) count(1) 默认是会利用主键索引扫描所有记录,计算总数。

但如果该表有辅助索引,mysql将会扫描辅助索引计算总数。

由于辅助索引的叶子节点只有id,而且辅助索引的叶子数量必定和聚集索引的一样,所以扫描辅助索引的速度要更快。

count(id) 会遍历全表,取出id, 然后判断id是否为空,再count++

由于id是聚集索引,比较快

count(字段) 也是遍历全表,但字段是非索引,需要额外取出记录,消耗更多。

相关推荐
SelectDB5 小时前
更高效的数据处理解决方案:基于 MinIO 部署 Apache Doris 存算分离版本实践
数据库·数据分析·apache
寒月霜华6 小时前
JavaWeb后端-MySQL
数据库·mysql
Fuly10246 小时前
大模型的记忆与管理及长期记忆实现方式
数据库·人工智能·rag
weixin_307779136 小时前
C#程序实现将Teradata的存储过程转换为Azure Synapse Dedicated SQL pool的存储过程
数据库·数据分析·c#·云计算·azure
折翼的恶魔7 小时前
SQL 189 统计有未完成状态的试卷的未完成数和未完成率
数据库·sql
yangmf20407 小时前
如何使用 INFINI Gateway 增量迁移 ES 数据
大数据·数据库·elasticsearch·搜索引擎·gateway
运维李哥不背锅8 小时前
Ansible 的条件语句与循环详解
数据库·ansible
曾凡宇先生9 小时前
OpenEuler中mysql这是在执行 MySQL 密码重置操作时出现的 “找不到mysqld_safe命令” 的错误场景。
数据库·mysql
方二华9 小时前
6 mysql源码中的查询逻辑
数据库·mysql