一条sql查询的语句执行过程
MySQL 可以分为 Server 层和存储引擎层两部分。
缓存 show variables可以查看我们mysql的许多配置,我们查一些需要的参数可以使用类似于模糊匹配的方式如下:
show variables
show variables like '%cache%';
我们可以指定查询缓存的开关
set global query_cache_size=0 set global query_cache_type=0
query_cache_type=0 quey_cache_size=0
mysql> select SQL_CACHE(sql_no_cache) * from T where ID=10;
MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了。
语法分析举例: 根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法,如果我们输入的不满足就会报错。如下我们selecr少输入一个s
这里着重说一下优化器 经过了分析器,MySQL 就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。
优化器负责选择执行计划,比如在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。
举个例子你执行下面这样的语句,这个语句是执行两个表的 join:
select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;
这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。如果你还有一些疑问,比如优化器是怎么选择索引的,有没有可能选择错等等
对于查询效率影响至关重要的索引,想必大家都知道这个词。但是可能还不是非常了解。 索引的出现其实就是为了提高数据查询的效率,就像书的目录一样。
索引的常见模型:哈希表、有序数组和搜索树。
哈希表
是一种以键 - 值(key-value)存储数据的结构,我们只要输入待查找的键即 key,就可以找到其对应的值即 Value。哈希的思路很简单,把值放在数组里,用一个哈希函数把 key 换算成一个确定的位置,然后把 value 放在数组的这个位置。不可避免地,多个 key 值经过哈希函数的换算,会出现同一个值的情况。处理这种情况的一种方法是,拉出一个链表。需要注意的是,图中四个 ID_card_n 的值并不是递增的,这样做的好处是增加新的 User 时速度会很快,只需要往后追加。但缺点是,因为不是有序的
,所以哈希索引做区间查询的速度是很慢的。你可以设想下,如果你现在要找身份证号在[ID_card_X, ID_card_Y]这个区间的所有用户,就必须全部扫描一遍了。所以,哈希表这种结构适用于只有等值查询
的场景,比如 Memcached 及其他一些 NoSQL 引擎。 有序数组
在等值查询和范围查询场景中的性能就都非常优秀。还是上面这个根据身份证号查名字的例子,如果我们使用有序数组来实现的话,示意图如下所示:
我们假设身份证号没有重复,这个数组就是按照身份证号递增的顺序保存的。这时候如果你要查 ID_card_n2 对应的名字,用二分法就可以快速得到。 如果仅仅看查询效率,有序数组就是最好的数据结构了。但是,在需要更新数据的时候就麻烦了,你往中间插入一个记录就必须得挪动后面所有的记录,成本太高。
平衡二叉树
,(普通二叉搜索树容易链化就不说了)
缺点: 太高了 数据处的(高)深度决定着他的IO操作次数,IO操作耗时大 太小了 每一个磁盘块(节点/页)保存的数据量太小了 没有很好的利用操作磁盘IO的数据交换特性(我们可以一次读4K数据的,现在只读了1个结点进去,而且这个结点只存了一个数据,非常浪费操作系统资源) 也没有利用好磁盘IO的预读能力(空间局部性原理)(预读;每一次IO时,不仅仅把当前磁盘地址的数据加载到内存,同时也把相邻数据也加载到内存缓冲区中。) 从而带来频繁的IO操作 操作系统方面具体细节可以百度,百度百科比我说的好...
B+树
B树和B+树类似,区别于其他树的最大区别是:B树和B+树,每个结点中不再只有左右两个孩子了,而是我们可以定义为任意个孩子,其中m个孩子就是m阶树。m阶B树定义
这是因为,我们mysql一般把一个结点数据定义为一页,一页数据是16K=16*1024byte,如果我们用的平衡二叉树,假如定义的索引为int型id,一个id 4byte,加上其他数据一个id索引可能页就8byte左右,这才占了一次io的多少?这何等的浪费资源??而我们用B树后,B树是多路平衡查找树,我们可以定义 16*1024/8 大概两千多路(索引或者孩子结点),这样效率提升了何止一点半点呢!
这其实也就是为啥我们一般慎用uuid做主键,因为它长度太长了,如果用uuid,太占用空间,我们索引的路数会变少,层数变多,效率会有所下降.
B+树是B树的变体,其结构定义基本与B树相同,除了:
B+Tree更适合用来做存储索引
这先要从InnoDB的索引实现说起,InnoDB有两大类索引:
InnoDB聚集索引和普通索引有什么差异?
聚集索引的叶子节点存储行记录
,因此, InnoDB必须要有,且只有一个聚集索引
普通索引的叶子节点存储主键值。
(1)如果表定义了PK,则PK就是聚集索引; (2)如果表没有定义PK,则第一个not NULL unique列是聚集索引; (3)否则,InnoDB会创建一个隐藏的row-id作为聚集索引;
举个栗子具体说明一下索引的建立结构,不妨设有表: t(id PK, name KEY, sex, flag); id是主键(聚集索引),name是普通索引。
表中有四条记录: 1, shenjian, m, A 3, zhangsan, m, A 5, lisi, m, A 9, wangwu, f, B
该表两个索引结构如上图所示
如果我们要根据普通索引name查一条数据,如select * from t where name='lisi';
,那么过程应该是咋样的呢?
(1)先通过普通索引定位到主键值id=5;
(2)在通过聚集索引定位到行记录;
这就是所谓的回表查询,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低。因此我们平常应该多用主键进行sql操作。
用不用的到索引其实不一定是用就是用不到啊,比如我们select * from user where id=3
如果id是主键我们就可以用用到索引,怎么证明呢? 可以用explain查看执行计划
*,直接在sql语句之前加explain就行,如EXPLAIN SELECT * FROM user WHERE id =3
可以看到这里是用了主键索引的,而且只扫描了一行就搞定了,但是我们还是不建议使用select * 这是因为我们往往需要的数据并没有那么多,但是我们平常为了追求开发速度好多查询功能都复用了以前的sql,增加返回字段,这给别人的业务加大了相应速度,也增加了自己业务的相应时间;另外也建议我们如果在别人代码里建新流程,要把自己的业务提成方法,业务功能划分要清晰,如果开发流程不是非常一致建议尽量别在别人业务里改动。
比如:
上述兴业的类型的我们就可以单独写个方法去处理,这样更清晰点
下面我们看下执行计划输出参数的释义。
expain出来的信息概要描述:
msql执行计划输出参数详细解析中文文档地址https://www.docs4dev.com/docs/zh/mysql/5.7/reference/explain-output.html,其他技术中文文档https://www.docs4dev.com/docs/zh# type结果值从好到坏依次是: system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
system:查询对象表只有一行数据,且只能用于MYISAM和Memary引擎的表,这是最好的情况
const:基于主键或唯一索引查询,最多返回一条结果
eq_ref:表连接时基于主键或非null的唯一索引完成扫描
ref:基于普通索引的等值查询,最多返回一条结果
fulltext:全文检索
ref_or_null:表连接类型时ref,但进行扫描的索引列中可能包含null值
index_merge:利用多个索引
index_subquery:子查询中使用唯一索引
range:利用索引进行范围搜索
index:全索引扫描
all:全表扫描
一般来说,得保证查询至少达到range级别,最好能达到ref,type出现index和all时,表示走的是全表扫描没有走索引,效率低下,这时需要对sql进行调优。 这里有点疑问,我们在查询列表接口通常要根据新旧进行排序返回,比如快讯列表根据更新时间排序每次分页取20条,如果我们用自增id代替更新时间,是不是效率会高许多呢?看下面
图1
,图一是咱们一个有自增主键的表,我们真实使用explain来分析一下两种查询的执行计划
图1
其实所有索引失效的原因,我们明白索引的结构就已经知道了,即本质来说,索引失效原因就是 搜索条件模糊,搜索范围广,搜索顺序不能按照索引走
为了描述的更清晰点,咱们直接对着图说好了
1.前导模糊查询不能利用索引(like '%XX'或者like '%XX%')
比如我们上面的name的值为表'ls','sj','ww','zs' ,如果where name like '%s'条件,由于前面是
模糊的,我们不知道从哪里查,所以不能利用索引的顺序,必须一个个去找,,看是否满足条件。这样会导致全索引扫描或者全表扫
描。
如果是这样的条件where name like 'l% ',就可以查找name中l开头的name的位置,当碰到s开头的
数据时,就可以停止查找了,因为后面的数据一定不满足要求。这样就可以利用索引了
2.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
3.如果是组合索引的话,如果不按照索引的顺序进行查找,比如直接使用第三个位置上的索引而忽略第一二个位置上的索引时,则会进行全表查询
索引为c1,c2,c3,c4
如果我们直接where x=c3则是全表查询,无法使用该索引的,因为c3字段使用索引的前提是c1,c2两字段均使用了索引。建议大家下来看下最左匹配原则
4.条件中使用or
我们不要感觉用or就一定不走索引,许多情况索引没有必定用到或者不用到,这跟我们条件信息有比较大的关系,比如如下图2所示的一个or,咱们实时说话
图3
5. 应尽量避免在where子句中对 字段
进行函数操作或者表达式操作,这将导致引擎放弃使用索引而进行全表扫描。
例子如下图4
一个对主键绝对值进行查询的sql
图4
如果我们对值进行操作实际上是不会影响索引的如explain SELECT * from r_notice_info b where id =(1+1)
,但是不建议这样做,java中的处理要更快些