MySQL索引概述与读书笔记(持续更新)

提示:本文毫无可读性,完全是自己的读书笔记


叶子页的分裂

主键索引一次叶子页分裂将导致其余索引上大量的磁盘I/O;对主键索引列的更新操作,会引起索引行的异动,从而导致叶子页的分裂。    

索引的选择性(与Cardinality有关)

选择性=cardinality/总行数

选择性越是接近于1越好,换言之,cardinality越大越好

以常见的反例“性别”来进行距离,cardinality=2,选择性无限趋近于0,这无疑是非常糟糕的

最差情况下的选择性比平均选择性更重要,因为如果照顾了平均选择性而忽略最差选择性的话,有可能造成某一个SQL查询特别慢

选择性的好坏,与后面提到的索引片有很大的关系;选择性越好,索引片越窄

cardinality是一个估算值(忽略同一字段建2个索引的睿智操作,不是我干的!)

相同字段下,不同时段创建的2个单一索引,Cardinality值不同

怀疑是统计没有更新,手动执行更新

(t表1500万行数据,在空闲库执行下面的语句时间338s,不要在忙时去执行这个语句)

alter table t engine=innodb;

执行后的索引信息如下

可以看到值与之前统计的值不同,而且同一字段对应的2个索引的cardinality值仍然不同

此外,在show index这里展现的cardinality估算得并不准确,甚至可能出现非常大的偏差

比如这个例子中的monthly_settlement,看起来在这个索引中,这一列的选择性是最好的,station_id的选择性看起来就没这么好

MySQL索引概述与读书笔记(持续更新)_第1张图片

然而实际情况并非如此

所以cardinality只能当做一般性的参考,如果要严格的计算选择性,还是需要用   “distinct 列名/总行数”     这种方式去计算,值越趋近于1,说明该列选择性越好


组合谓词的选择性(谓词:前面提到的3要素中的索引列)

简言之就是2个单列索引的效果可能远不如这2个字段的联合索引

优化器在评估访问路径成本时,必须先评估索引的选择性。(其实选择性的好坏也是三星索引的第一颗星)

等价的索引

where a=:a and b=:b

如果创建索引(a,b)和索引(b,a),这2个索引是等价的,因为where语句后面跟的2个都是等值条件,如果其中一个是范围条件,那么这2个索引就不等价了


回表采用的是随机读

三星索引

第一颗星:索引片的宽窄;

第二颗星:索引是否能够处理排序;(避免排序)

第三颗星:查询列是否在索引中

一般来说,第一颗星要比第二颗星重要,一个尽量窄的索引片比避免排序要重要;但这只是一般来说

下列例子说明了第一颗星的重要性

#sql语句

SELECT MAX(id)

FROM t

WHERE '2019-01-07 23:59:59' >= create_time

GROUP BY user_id;

索引1:idx_ct_uid(create_time,user_id)

索引2:idx_ct(create_time),idx_uid(user_id)

user_id是一个varchar(30)的列

全表行数为270W行,使用索引1的情况下,执行时间为6s,使用索引2的情况下,执行时间为1.5s

归结原因,条件列是一个范围查询,索引1能用到的其实只有create_time这部分,但是user_id列的加入,使得这个联合索引的索引片宽度大增,因此花了更多的时间去扫描。


宽索引

一个索引如果(至少)包含了第3颗星,那么这个索引就是一个宽索引

困难谓词(索引失效问题)

简单来说就是参照下面的口诀:

全值匹配我最爱,最左前缀要遵守

带头大哥不能死,中间兄弟不能断

索引列上少计算,范围之后全失效

like百分写最右,覆盖索引不写星

不等空值还有or,索引失效要少用

表连接的索引

关于嵌套循环

本地谓词和连接谓词

只用于访问一张表的谓词被称为本地谓词

定义了表和表之间的连接关系的谓词被称为连接谓词

抛开比较复杂的嵌套查询

只有本地谓词的那张表,将会成为外表,而另一张表自然成为了内表

外表也就是驱动表,要求是尽量的小

外表的小并不是指外表的总行数小,而是指经过外表的过滤因子之后取得的索引片比较小,这样会获得更好的性能

在嵌套查询中,内表产生大量随机读取是影响性能的一个关键因素,因此内表需要有一个比较好的宽索引,并且以连接谓词作为前导列(也就是连接谓词作为最左)

eg

select .... from A,B

where A.a=:a

and B.b=A.b;

在上面这个查询中,A.a是A表的本地谓词,所以A表很明显的就是外表,而B表就是内表,其中B.b是B表的连接谓词,为了减少内表的随机读,内表上需要有一个合适的以B.b为前导列的宽索引。

(由于这里的连接条件里只有B.b=A.b,如果B.b的选择性很差,那么这个内表的查询效率有可能很低,这种情况下,可以通过补全一些谓词去提高连接谓词的选择性(冗余字段),可以使内表尽可能快的查出结果,毕竟一般来说外表都是小表,内表相对较大,对性能的影响是比较大的。该段参考《数据库索引设计与优化》第8章的那个例子(CCTRY),该案例中有一个补全FF缺陷的技巧; 这个技巧在P178页中被描述为向下反范式,也就是内表加列;与之对应的还有向上反范式,也就是外表加列,不过需要注意所加冗余列的易维护性)

对于or的优化

可以改写成union+order by的形式

但是否能优化好,取决于一些条件是否完全匹配

有一些语句即使索引优化的很好,但union + order by仍然造成性能问题


--2019.7.25验证2年前的笔记

使用索引进行排序:(避免排序)

order by也需要满足索引最左前缀要求,才能用索引排序

比如索引为idx(b,c)

select xxx from table_a where a='x' order by b,c;

除此之外,索引列是一个常量的情况下,即使order by的部分不满足最左,也可以使用

比如索引为idx(a,b,c);索引列为常量的情况,说的就是where a='x'(如果是a>'x',或者是a in('x','y')则属于范围,不属于常量)

所以下列索引列和order by组成了a,b,c的顺序,满足了idx(a,b,c)的顺序

select xxx from table_a where a='x' order by b,c;

但是如果索引列为a>'x',这是一个范围查询,那么上面的语句无法使用idx(a,b,c)进行索引排序(下面的语句可以,因为order by满足了最左)

select xxx from table_a where a>'x' order by a,b;

关于最左前缀之前的一个理解错误的地方

idx(a,b,c)

select xxx from table_a where c='t' and b='y' and a='x'这样仍然会走索引,虽然顺序不一样,但是a,b,c都在where条件里面,并不需要把where写成a,b,c的顺序才行。

一些无法使用索引排序的例子;(索引仍然为idx(a,b,c))

1.逆序导致的方向不对

select xxx from table_a where a='x' order by b desc,c desc;


上面2张图对比可以看出desc字段没有走索引,但是从key_len来看2者没有变化,key_len记录的应该只是where后面走索引的字段,而对于排序部分是不记录的,至少5.7版本是这样

2.order by有其他列

select xxx from table_a where a='x' order by b,d;

3.中间列(b)断了

select xxx from table_a where a='x' order by c;

4.索引列有一个是范围查询

select xxx from table_a where a='x' and b in ('x','y') order by c;

5.延迟关联,本表会被当成第二张表,因此也无法使用


eg.排序走索引测试

MySQL索引概述与读书笔记(持续更新)_第2张图片

涉及的字段,type,status,create_time,update_time(重要:下文以a,b,c,d代替)

idx_0对上述4个(a,b,c,d)字段建有联合索引

idx_1对前面2个(a,b)字段建有联合索引


语句1

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by create_time;

where a=,b= order by c类型,加上查询字段的d,覆盖索引,排序走索引

语句2

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by create_time desc;

where a=,b= order by c desc类型,虽然排序是倒序,但仍然走了索引,并没有filesort

语句3

select update_time from tra_trade where type='POS_ONLINE_PAY' and status!='SUCCESS' order by create_time;

where a=,b!= order by c 类型,出现b列出现了不等于,相当于(a,b,c)中的b断了,a肯定能走索引的,从key_len来看,b也走了,后面的c走不了,c出现了排序

语句4

select update_time from tra_trade where type='POS_ONLINE_PAY' and status!='SUCCESS' order by type,status,create_time;

where a=,b!= order by a,b,c 类型,全都走了索引

语句5

select update_time from tra_trade where type='POS_ONLINE_PAY' and status!='SUCCESS' order by type,create_time;

where a=,b!= order by a,c 类型,排序中的(a,b,c)断了,c走不了索引,出现了排序

#前面5个类型属于必须要记住的分类,后面的十几个语句算是更细节的探索

语句6

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by status,update_time;

where a=,b= order by b,d 类型,排序中的(b,c,d)断了,d肯定走不了索引会出现排序;这个语句的疑问在于b不满足最左,同时也不像前面的where a,b order by c那样满足连贯,b是否能走索引?

语句6变种

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by status;

将语句6中排序的d列(update_time)去掉后,语句变成了where a,b order by b类型,没有产生排序

语句7

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by status,create_time;

where a=,b= order by b,c 类型,语句7是前面语句6变种的延伸,与6的变种一样,没有产生排序

语句8

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by status,create_time desc;

where a=,b= order by b,c desc 类型,与7一样,没有产生排序


语句9

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by create_time desc,status,type;

语句10

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by create_time,status,type;

语句11

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by create_time,type;

语句12

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by create_time,status;

语句13

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by create_time,type,update_time;

语句14

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by create_time,status,update_time;

9和10都看作是where a=,b= order by c,b,a 类型,区别只是c是否有带desc;11是where a=,b= order by c,a 类型;12是where a=,b= order by c,b 类型;13是where a=,b= order by c,a,d 类型;11是where a=,b= order by c,b,d 类型;全都没有产生排序


语句15

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by update_time;

where a=,b= order by d 类型,(a,b,d断裂),d肯定走不了索引,产生排序

语句16

select update_time from tra_trade where type='POS_ONLINE_PAY' and status='SUCCESS' order by status,payee_charge_amount;

where a=,b= order by b,e 类型,e都不在索引里,肯定产生排序,此外排序列不在索引列,using index都没了,把覆盖索引都给破坏了


语句17

select * from tra_trade force index (idx_0) where type='POS_ONLINE_PAY' and status='SUCCESS' order by create_time;

select * from where a=,b= order by c类型

语句18

select * from tra_trade force index (idx_0) where type='POS_ONLINE_PAY' and status='SUCCESS' order by create_time desc;

select * from where a=,b= order by c desc类型

17和18的排序一个正序一个倒序,但查询列从索引的d字段变成了*,首先排序肯定是没有产生的,但有意思的是执行计划里面的extra字段变了;正序走了icp,倒序就走了个using where


18个语句的测试结论:

1.order by中的倒序,不会产生排序(filesort);如果你的执行计划里面产生了filesort,起码不能简单归结于是排序中有desc

2.如果where部分是常量,且查询部分没有出现中间断裂的情况,那么order by可以不用遵循最左原则

3.如果where部分有范围(不等于相当于范围),那么order by必须遵循最左原则

4.where部分与order by部分必须避免出现中间断裂的情况,只要出现中间断裂,必然产生filesort;只要不出现中间断裂,无论order by部分字段的先后顺序如何都不会产生filesort

5.排序中的desc并不会产生filesort,但desc是否会影响数据的过滤,不得而知,至少从17和18这2句的执行计划可以发现extra有明显的不同


#第4点字面很难看明白,特别解释一下

首先说明一下怎样算断裂:断裂点通常有2个地方,一个是where和order by接壤的地方,比如where后面是ab,order by是d,中间的c没有,就断掉了;另一个地方就是order by自身,比如where是ab,order by是abd,也是漏掉了c;但如果where部分是ab,order by部分是ac,看起来order by部分好像断掉了,实际上把where和order by连起来看,abc都已经存在了,所以就不存在中间断掉的情况。 此外,前面都是在where条件为常量的情况下,如果where部分存在范围,那么order by必须满足最左,并且一旦order by中存在断裂则视为断裂

结论第4点前半句中标粗的“避免中间断裂”的意思是where部分是ab,索引部分可以是c,或者cd,或者abc,或者是ac,或者bc;总之确保where和order by连起来是一个ab,c或者ab,cd或者ab,abc或者ab,ac或者ab,bc之类的模式,但绝对不能出现ab,d这种模式(也就是说where或者order by中只有a,b,d但),一旦出现断裂,则必然出现filesort

第4点后半句中的“部分字段的先后顺序”的意思是无论是order by abc还是cba还是cab都无关紧要,都不会出现filesort

你可能感兴趣的:(MySQL索引概述与读书笔记(持续更新))