有人曾经说过:“普通程序员的技术尽头就是在研究数据库”。架构设计都围饶着数据库来设计。又有人统计过“对数据库的查询操作要远胜更新操作”。所以了解数据查询还是很有必要的。
MySQL 客户端和服务端的通信是半双工的,这意味着同一个时刻内,客户端和服务端只有一方在发送数据。一旦一方开始发送数据,另外一端必须接受完整个消息才能进行响应。
注:通信协议限制了数据交互,应该方要经常要搞个连接池
防止查询语句过长浪费资源用 max_allowed_packet 限制。
防止结果集过大需要添加 LIMIT 限制。
查询各有进程状态命令为SHOW FULL PROCESSLIST
如果查询缓存是打开的,那么MySQL会优先检查这个查询是否命中查询缓存中的数据。
查询缓存命令:show variables like ‘%query_cache%’;
关于语法规则参考《编译原理》
现在语法树被认为合法的了,并且由优化器将其转化为执行计划。一条查询可以由很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。
将语法树->执行计划的过程,可理解成《编译原理-语义分析》。mysql优化器在翻译过程中会结合相关表的实际情况进行基于成本估算。最终选择成本最小的一个。
在解析和优化阶段,MySQL将生成查询对应的执行计划,MySQL的查询执行引擎则根据这个执行计划来完成整个查询。相对于查询优化阶段,查询执行阶段不是那么复杂:MySQL只是简单的根据执行计划给出的指令逐步执行。在根据执行计划逐步执行的过程中,有大量的操作需要通过调用存储引擎实现的接口来完成,这些接口就是我们称为“handler API”的接口。实际上,MySQL在优化阶段就为每个表创建了一个handler实例,优化器根据这些实例的接口可以获取表的相关信息,包括表的所有列名、索引统计信息等。
查询执行的最后一个阶段是将结果返回给客户端。
结果集中的每一行都会以一个满足MySQL客户端/服务器通信协议的封包发送,再通过TCP协议进行传输,在TCP传输过程中,可能对MySQL的封包进行缓存然后批量传输。
想要了解优化器如何工解,基核心思想是先要读懂sql是如何执行的。
sql是按照固定的顺序解析的,主要的作用就是从上一个阶段的执行返回结果来提供给下一阶段使用,sql在执行的过程中会有不同的临时中间表,一般是按照如下顺序:
此只是逻辑过程,mysql会对其中的操作优化使其等价。
表连接的方式多种多样,主要有以下三种。
hash join 算法先选一个小表,放入内存的 hash table,然后扫描另一个表,与 hash table 匹配出结果数据。当表太大,无法一次放入内存时,就分而治之,写入块文件,再对每个块文件走一遍正常时的流程。
## 例:查询人和国家信息
select
given_name, country_name
from
persons a join countries b
on a.country_id = b.id
这样就完成了整个 join 操作,每个表只扫描一次就可以了,扫描匹配时间也是恒定的,非常高效.
有比较大的表,无法全部加载到内存中?
- 把表中剩余数据分成多个块文件写到磁盘上,并保证每个块文件的大小都是适合可用内存的。
- 通过hash(countries.country_id)定位到哪个块,则调取块到内存
排序合并连接则要求两个排序了的数据集。它的优点是,不需要每一次匹配都从头读到尾。但它有些特别的场景:
## 例:查询人和国家信息
select
given_name, country_name
from
persons a join countries b
on a.country_id = b.id
行号 | persons表(a) | countries表(b) |
---|---|---|
1 | 10 | 10 |
2 | 20 | 10 |
3 | 20 | 20 |
4 | 40 | 20 |
5 | 50 | 30 |
对persons,countries表,进行merge操作
合理使用索引对于排序合并连接算法,可减少排序的步骤。
NLJ是通过两层循环,用第一张表做Outter Loop,第二张表做Inner Loop,Outter Loop的每一条记录跟Inner Loop的记录作比较,符合条件的就输出。而NLJ又有3种细分的算法:
SNLJ就是两层循环全量扫描连接的两张表,得到符合条件的两条记录则输出,这也就是让两张表做笛卡尔积,比较次数是R * S,是比较暴力的算法,会比较耗时。
// 伪代码
for (r in R) {//R为驱动表
for (s in S) {//直接扫表遍历
if (r satisfy condition s) {
output <r, s>;
}
}
}
INLJ是在SNLJ的基础上做了优化,通过连接条件确定可用的索引,在Inner Loop中扫描索引而不去扫描数据本身,从而提高Inner Loop的效率。
而INLJ也有缺点,就是如果扫描的索引是非聚簇索引,并且需要访问非索引的数据,会产生一个回表读取数据的操作,这就多了一次随机的I/O操作。
// 伪代码
for (r in R) {//R为驱动表
for (si in SIndex) {//在索引中取数据
if (r satisfy condition si) {
output <r, s>;//满足条件回表查
}
}
}
在MySQL5.6中,对INLJ的回表操作进行了优化,增加了Batched Key Access Join(批量索引访问的表关联方式)和Multi Range Read(mrr,多范围读取)特性,在join操作中缓存所需要的数据的rowid,再批量去获取其数据,把I/O从多次零散的操作优化为更少次数批量的操作,提高效率。
一般情况下,MySQL优化器在索引可用的情况下,会优先选择使用INLJ算法,但是在无索引可用,或者判断full scan可能比使用索引更快的情况下,还是不会选择使用过于粗暴的SNLJ算法。
这里就出现了BNLJ算法了,BNLJ在SNLJ的基础上使用了join buffer,会提前读取Inner Loop所需要的记录到buffer中,以提高Inner Loop的效率。
// 伪代码
for (r in R) {//R为驱动表
for (sbu in SBuffer) {//在缓存中取数据
if (r satisfy condition sbu) {
output <r, s>;
}
}
}
通过多表关联算法得知2个重要的信息。如何选择驱动表,如何选择索引是查询优化的关键的。
何谓驱动表?
指多表关联查询时,第一个被处理的表,亦可称之为基表,然后再使用此表的记录去关联其他表。
驱动表的选择遵循一个原则:在对最终结果集没影响的前提下,优先选择结果集最少的那张表作为驱动表。何为索引
简单的说就是新华字典的检字表,,可根据它快速定义数据页,关于Mysql索引具体信息查看《InnoDB存储引擎索引概述》。
所以MySQL生成查询计划的时候,需要向存储引擎获取相应的统计信息。
选择驱动表,MySQL主要采用Nested Loop Join算法进行表关联。
每行查询字节数 * 预估的行数 = 预估结果集
预估的行数可根据EXPLAIN查询
EXPLAIN 结果中,第一行出现的表就是驱动表
驱动表对于Nested Loop Join是外层表,并不关注连接条件on的变化
结合《MySQL Explain详解》分析以下案例
create table a(a1 int primary key, a2 int ,index(a2)); --双字段都有索引
create table c(c1 int primary key, c2 int ,index(c2), c3 int); --双字段都有索引
create table b(b1 int primary key, b2 int); --有主键索引
insert into a values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);
insert into b values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);
insert into c values(1,1,1),(2,4,4),(3,6,6),(4,5,5),(5,3,3),(6,3,3),(7,2,2),(8,8,8),(9,5,5),(10,3,3);
EXPLAIN EXTENDED
select a1,b2,c2,c3 from a
left join b on a.a1=b.b2
left join c on a.a1=c.c2 and c.c2 < 6
where a.a1 > 1
- 关联表的数据都是汇入到基表,因此后面 order by,group by 如果要用到索引,只可以用基表的索引
- on 后面语句发生在2表关联后执行,即使是和 a.a1 > 1 和基表相关的语句
- 在关系过程中一线表只能选择一个索引进行辅助
#给c表加个c2,c3的联合索引
EXPLAIN EXTENDED select c2,c3,count(1) from c
LEFT JOIN
(select a2 from a where a2 > 3) t
on c.c2 = t.a2
GROUP BY c2,c3
order by c2 asc
limit 3
完美的直接利用索引干完事情,没有回表的操作。如果GROUP BY ,
order by, on三者选择的索引不一致,就会出来回表查,文件排序的状
《mysql 优化 explain 和show warnings 用法》
《MySQL8 的 Hash join 算法》
《SQL调优之六:排序合并连接(Sort Merge Joins)》
《Nested Loop Join》
《Mysql多表连接查询的执行细节一》
《Mysql多表连接查询的执行细节(二)》