第10章 条条大路通罗马-单表的访问方法
查询时使用的查询SQL语句首先需要经过MySQL查询优化器进行优化,得到一个执行计划,然后通过查询计划调用存储引擎中的方法来执行真正的查询
但是那种是比较复杂的情况,先看看最简单的查询过程------单表查询:就是FROM子句后面只有一个表
访问方法
平时写的查询语法只是一种声明的过程,告知MySQL我们想获取的数据符合哪些规则,或者说需要哪些要求。至于怎么获得到的就是MySQL自己的事情
这里就像是平时我们的地图软件,我们只负责输入目的地,至于中间的路程都是地图软件解决的问题。
那么我们告知MySQL的只是一种结果(可以被其他语句修饰),中间过程也是MySQL自己弄出来的------查询执行
执行查询的方式有两种
(1)使用全盘扫描
就是最纯粹的遍历查询方法,最原始的手段
(2)使用索引
也就是之前说过的二分法快速查询的办法,内节点 -》 叶节点 -〉 具体的页 -》 页中的槽 -〉定位到页中的组 -》 遍历该组的到具体的数据
执行查询语句的方式称之为查询方法,一个查询语句可以使用多种方法来查询,但是最终的结果都是一样的:这就是条条大路通罗马咯!
具体的访问方法:
Const
通过主键列来定位一条记录
SELECT * FROM single_table WHERE id = 1438
于是MySQL回直接通过主键值在主键B+树,也就是聚簇索引中查询记录
还可以使用二级索引来定位一条记录
SELECT * FROM single_table WHERE key2 = 3841
这样就会先到这个辅助的二级索引中先获取到主键值,然后通过这个主键值回表到聚簇索引中获取到完整的记录
这种使用索引的定位方式是很快的,于是定义成const级别,也就是常数级别的查询。意思就是代价基本上就是可以忽略不计的
但是只能访问主键列或者唯一的二级索引列和一个常数进行等值比较才有效。因为索引中的全部列都是直接进行值的等值比较的
如果不唯一的话:
SELECT * FROM single_table WHERE key2 IS NULL
二级索引并不限制Null的数量,于是可以访问到多条记录,因此就不可以使用const方法来查询了
总结:只要是唯一的确定值的记录,通过索引访问的话都是const级别,因为只能通过等值查询到一条记录。
也就是索引值等值唯一查询的情况
ref
使用普通的二级索引来和常数值进行等值比较
SELECT * FROM single_table WHERE key1 = 'abc';
此时可以查询到多条记录,因此这种方式和const还是差了一点点。如果重复记录比较少的话,那么效率还是可以的。太多的话回表的成本就太大了
注意:
(1)二级索引列值为NULL的情况,不管是普通的二级索引还是唯一二级索引,都不限制NULL的记录的数量,因此查询NULL记录最多是ref级别的访问方法
(2)对于包含多个列值的二级索引来说,只要使用等值比较,都有可能采用ref的查询方式
但是如果使用范围查询的话,就不是ref级别的访问方式了:
SELECT * FROM single_table WHERE key-part1 = 'god like' AND key_part2 > 'legendary';
总结:也就是索引值不唯一的等值情况
ref-of-null
有时候不仅想找出某个常数值的记录,还想获得NULL值的记录:
SELECT * FROM single_demo WHERE key1 = 'abc' OR key1 = NULL;
如果使用索引来执行该查询的话,那么就是ref-or-null级别的查询
先得到两个范围的记录中的主键id值,然后再回表得到完整记录
总结:也就是在索引列值不唯一的等值查询的情况下多一个null的查询就是 ref on null
range
前面基本上都是等值查询,如果查询条件更加复杂一点呢?
SELECT * FROM single-table WHERE key2 IN (1438,6328) OR (key2 >= 38 AND key2 <= 79);
可以使用全盘扫描,也可以使用二级索引 + 回表
利用索引进行范围匹配的查询称之为range查询
索引可以是主键值的聚簇索引也可以是二级索引
总结:range很好理解就是范围查询的意思
index
SELECT key_part1, key_part2 ,key_part3 FROM single_table WHERE key_part2 = 'abc';
这里keypart2不是联合索引中满足大推小顺序的索引列,于是不可以使用ref或者range来进行查询
不过查询列表包含三个列,并且索引也包含这三个列。并且搜索条件中的part2也包含在索引列中
因此可以直接遍历二级索引的叶节点记录来比较这个搜索条件,然后将匹配成功的记录直接放到结果集即可
这是根据结果为导向进行的查询,用户想要的数据以及搜索条件直接在二级索引中的叶节点中就有,因此直接遍历即可。不用回表也不用查询聚簇索引
这种遍历二级索引记录的方式称之为index
总结:遍历二级索引树的叶节点方法就是index
all
最直接的查询方式------全表扫描
注意事项
重新复习二级索引 + 回表
一般的时候是只使用一个二级索引进行查询:
SELECT * FROM single_table WHERE key1 = 'abc' AND key2 > 1000
查询优化器会根据查询的搜索条件来进行提前预估判断记录的数量来决定到底是使用全盘扫描还是索引查询
流程如下:
(1)先根据 key1 = abc 从二级索引树中查询到对应的一堆主键值,然后回表得到一些完整的记录
(2)再根据key2 的要求筛选刚刚得到的完整的记录,就是最终的结果集了
注意:key2的要求在第一步中是使用不到的,只有完成回表的操作以后才能使用到,key2是针对完整记录的过滤操作
明确range访问方法使用的范围区间
索引列和搜索条件的常数使用 = 、 IN、NOT IN、IS NOT NULL、
等符号或者使用LIKE操作符连接起来,就可以形成一个区间
而且一个WHERE子句可以有很多个小的查询条件,使用AND 或者OR来查询
(1)AND:两个条件都为TRUE的时候整个表达式才为TRUE
(2)OR:两者其一为TRUE即可
使用range范围查询方法的时候,关键就是找出可用索引的范围区间
所有搜索条件都可以使用某个索引的情况
也就是说一个索引可以有多个范围匹配的要求:
SELECT * FROM single_table WHERE key2 > 100 AND key2 > 200;
这里的查询语句每个条件都使用到了key2索引列值,并且AND连接就是取两个区间的交集
当然也可以使用OR来进行:
SELECT * FROM single_table WHERE key2 > 100 OR key2 > 200;
这样就是取各个范围的并集
有的搜索条件无法使用索引的情况
SELECT * FROM single_table WHERE key2 > 100 AND common_field = 'abc';
这里的查询条件中只能使用一个key2索引,但是这个二级索引的记录中又没有common-field字段,这个字段只能是回表以后从完整的用户记录中得到才行
因此后面的这个common条件就不需要了,因为一开始的筛选中就和这个条件无关,因此将其设定为TRUE即可:
SELECT * FROM single_table WHERE key2 > 100 (AND TRUE) ;
注意这里的AND TRUE是可以省略的
如果一开始使用OR呢?
SELECT * FROM single_table WHERE key2 > 100 OR common_filed = 'abc';
那么经过TRUE的转换以后:
SELECT * FROM single_table WEHRE key2 > 100 OR TURE;
那么这个逻辑判断将一直显示为TRUE,于是查询的记录为所有记录了
所以你使用索引列中没有的值作为查询条件的话,使用OR就无法使用该索引了
个人总结:核心在于索引列中没有后面那种搜索条件的值,因此直接默认为TRUE了
复杂搜索条件下找出范围匹配的区间
SELECT * FROM single_table WHERE
(key1 > 'xyz' AND key2 = 748 ) OR
(key1 < 'abc' AND key1 > 'lmn') OR
(key1 LIKE '%suf' AND key1 > 'zzz' AND (key2 < 8000 OR common_field = 'abc')) ;
非常复杂,但是实际上查询优化器是会对这语句做如刚刚所示的化简处理的
先看看用到了哪些列:key1,key2,common-field。
然后有哪些索引呢:key1的普通二级索引和key2的唯一二级索引
如果我们先使用key1的索引进行查询:
化简步骤:
(1)先将用不到索引的搜索条件先移除掉:转换为TRUE:
(key1 > 'xyz' AND TRUE ) OR
(key1 < 'abc' AND key1 > 'lmn') OR
(TRUE AND key1 > 'zzz' AND (TRUE OR TRUE))
然后再化简一下形式:
(key1 > 'xyz') OR
(key1 < 'abc' AND key1 > 'lmn') OR
(key1 > 'zzz')
(2)逻辑判断一下哪些条件永远为TRUE 还是 FALSE:
比如 key1的条件,key1 < 'abc' AND key1 > 'lmn'
显然不合理,直接为FALSE,因此可以直接删除这个条件:
(key1 > 'xyz') OR (key1 > 'zzz')
(3)然后继续化简区间:
现在只剩下OR语句了,直接合并区间:key1 > 'xyz';
于是这就是最终的筛选条件。那么如果使用key1索引进行筛选的话,首先是从二级索引中取出key1大于xyz的值,得到id以后再回表取出完整的用户记录,接着使用其他搜索条件进行过滤即可
如果使用key2进行筛选呢?那么就是针对key2条件进行查询优化:
(1)还是一样,将用不到的筛选条件换成TRUE:
(TRUE AND key2 = 748 ) OR
(TRUE AND TRUE) OR
(TRUE AND TRUE AND (key2 < 8000 OR TRUE))
这里很多OR和TRUE的组合,那么可以进一步化简:
key2 = 748 OR TRUE
然后就是:
TRUE
也就是说使用key2进行先一步的筛选的话,需要进行全盘扫描才行,所以这种情况下就不会使用key2了
那么这就是优化器差不多需要做的工作,正式开始查询之前先化简一下查询的条件,做一个预处理的工作
索引合并
一般执行查询最多用到一个二级索引,但是也可以使用多个二级索引,这种方式叫做index merge。
于是我们需要将多个索引进行合并来查询
Intersection合并
英文名字是交集的意思,查询的时候使用多个二级索引,那么就从多个二级索引中查询到的结果去交集
比如:SELECT * FROM single_table WHERE key1 = 'a' AND key3 = 'b';
如果这个查询使用交集索引合并的模式的话:
(1)从key1二级索引树中取出key1 = a的记录
(2)再从key3索引树中取出key3 = b 的记录
(3)由于是AND,于是将两个结果集中的id值取交集
(4)然后根据得到的id进行回表即可
如果只使用一个索引列值进行回表,得到的记录再使用另一个索引列值进行筛选不可以吗?
当然可以,使用索引是顺序I/O,但是回表的时候是随机I/O,如果读取一个二级索引需要回很多表的话,这样性能损耗比较大
同时使用两个索引进行查找则都是顺序I/O,得到的结果集再取交集,这样得到的主键值就不需要多少回表了
也就是使用两个二级索引的成本比使用一个二级索引 + 回表的成本要低
使用Intersection索引合并的情况:
(1)等值匹配:二级索引必须是等值匹配的查询,如果是联合索引那么每个列值都必须等值匹配才行
(2)主键列可以是范围匹配:
SELECT * FROM single_table WHERE id > 100 AND key1 = 'a';
为什么这样又可以呢?
交集索引合并的核心就是从两个结果集中去取主键值的交集。如果一开始就给出了一个主键值的范围,那么就相当于已经给出了一个结果集中的主键值了,于是另一个搜索条件查询到结果集以后再和这个主键集取主键的交集即可
如果顺序换一下呢:
SELECT * FROM single_table WHERE key1 = 'a' AND id > 100;
这样更加简单,直接取出结果集,因为结果集中带有主键值,索引直接根据后面的id 大于100进行筛选即可。就不用重新取交集了。也就是后面的id条件就是一个过滤的作用了
但是(1)(2)条件也不是优化器一定会采用交集索引合并的条件,这个得看优化器提前预估取出二级索引记录数量多不多,回表性能损耗大不大才行
Union 合并
刚刚的是交集,那么从英文名字来看这里的Union自然就是并集的合并了,对应的就是OR语句
比如这样:SELECT * FROM single_table WHERE key1 = 'a' OR key3 = 'b'
那么直接从得到的结果集中将主键值id合并查询回表即可
使用Union索引合并的情况:
(1)二级索引必须是等值匹配的情况,如果是联合索引,那么每一个列值也需要进行等值匹配
(2)主键列可以是范围匹配
(3)使用了交集索引合并的搜索条件
比如这样:SELECT * FROM single_table WHERE key_part1 = 'a' AND key_part2 = 'b' AND key_part3 = 'c' OR (key1 = 'a' AND key3 = 'b');
这里可以拆分成两个几个部分:
先看括号,是一个交集索引合并,得到一个主键值的集合
然后再按照从左到右的顺序得到另一个主键集合
最后通过OR的并集索引合并的方式将两个主键值集合去并集,然后再回表操作得到结果集
当然,最终是否使用并集索引合并还是取决于优化器的事先判断
Sort-Union 合并
使用并集索引合并的条件必须是等值匹配才行,比较苛刻:
SELECT * FROM single_table WHERE key1 < 'a' OR key3 > 'z'
像这样看着可以并集的查询,还是不可以使用Union合并索引。
因为得到的两个主键值的集合都不是顺序排列的
于是可以分别将两个主键值集合排序以后,再进行合并,此时就退化成了主键范围查询的Union情况了。
这种方式就可以使用Union合并,称之为Sort-Union,先获取主键值集合,排序好了以后再合并查询
索引合并的注意事项
联合索引代替Intersection交集索引合并
之所以需要使用交集索引合并,是因为之前的两个索引列都不是同一个B+树上的,因此才需要"合并"
如果一开始就是将它们设置到一棵树上,那么直接使用联合索引查询就好了,就不需要合并它们了
总结:本章就是介绍了一些查询的方法和优化器的基本工作,以及查询的时候索引的一些优化方式
复习:
查询方法:使用索引那么就有索引对应的不同的方法,比如const,ref ,ref-on-null,等等方法
优化器的基本工作就是不停对查询语句作化简处理,如果是单个索引列值的情况直接化简,如果是多个索引列的情况那么还需要确定先使用的索引。如果预估的回表记录太多的话那么还是使用全盘扫描比较合适
查询索引的时候可以将索引合并,交集合并和并集合并,本质还是通过二级索引得到的结果集中对id主键值进行的交集和并集的操作,然后再回表,节约性能