基础优化-索引及执行计划(七)

1. 执行计划分析

1.1 执行计划

select * from t1 where name='zs';
执行计划分析:分析的是优化器按照内置的cost计算算法(模型),最终选择后的执行计划。

优化器毕竟是一个代码,有可能会出先偏差的时候,这个时候就需要管理员读懂优化器,最终得出的结论合不合理。需要有一些评估的标准

对于计算机来讲,代价(cost)是资源,消耗的资源越多,代价越高(IO,CPU,MEM)

1.2 查看执行计划

(1) 查看执行计划

-- 查看执行计划,explain、desc这两种方式都可以
mysql> explain select * from world.city;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | city  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4188 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+

mysql> desc select * from world.city;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | city  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4188 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+

(2)explain(desc)使用场景(面试题)

题目意思:  我们公司业务慢,请你从数据库的角度分析原因
1.mysql出现性能问题,我总结有两种情况:
(1)应急性的慢:突然夯住
应急情况:数据库hang(卡了,资源耗尽)
处理过程:
1.show processlist;  获取到导致数据库hang的语句
2. explain 分析SQL的执行计划,有没有走索引,索引的类型情况
3. 建索引,改语句
(2)一段时间慢(持续性的):
(1)记录慢日志slowlog,分析slowlog
(2)explain 分析SQL的执行计划,有没有走索引,索引的类型情况
(3)建索引,改语句

1.3 执行计划显示结果的认识

table: 此次查询涉及到的表
type: 查询类型,全表扫描或者索引扫描
possible_keys:可能用到的索引
key:最后选择的索引
key_len:索引覆盖长度
rows:此次查询需要扫描的行数
extra:额外的信息

1.4 输出信息介绍

1.4.1 table

此次查询涉及到的表,针对一个查询中多个表时,精确到问题表。
desc select country.name ,city.name from city join country on city.countrycode=country.code where city.population='CHN';

1.4.2 type查询类型

(1)全表扫描:不用任何的索引。ALL。
查询结果是需要把整个索引树扫描一遍才能得到的这种情况,成为全索引扫描,代价高
例如:

mysql> desc select * from city;
mysql> desc select * from city where 1=1;
mysql> desc select * from city where countrycode like '%ch%';
mysql> desc select * from city where countrycode not in ('CHN','USA');
mysql> desc select * from city where countrycode != 'CHN';

(2)索引扫描
index < range < ref < eq_ref < const(system)
索引扫描从左到右索引效率增高

index: 全索引扫描

mysql> desc select conuntrycode from world.city;

range: 索引范围查询(> < >= <= like in or between and)

mysql> desc select * from city where id<10;
mysql> desc select * from city where coutrycode like 'CH%';
mysql> desc select * from city where countrycode in ('CHN','USA');
--> 如果效率不高可以改写成 union all 
select * from city where countrycode='CHN'
union all
select * from city where countrycode='USA';

-- 特殊情况:查询条件为主键时
mysql> desc select * from city where id != 10;
mysql> desc select * from city where id not in (10,20);

ref: 辅助索引等值查询

desc  select * from city where countrycode='CHN';

eq_ref: 多表连接中,非驱动表连接条件是主键或唯一键。

-- 下面的B就是非驱动表,B的yy列如果是主键列或唯一键,那么索引类型就是ref
A join B on A.xx=B.yy
-- 进行测试
desc select country.name,city.name
from city join country
on city.countrycode=country.code
where city.popualtion='CHN';

const(system): 聚簇索引等值查询(只有一行数据的时候是system,不常见)

mysql> desc select * from city where id=10;
1.4.3 possible_keys和key

possible_keys:可能会走的索引,所有和此次查询有关的索引
key:此次查询选择的索引。

1.4.4 key_len

key_len:联合索引覆盖长度
对于联合索引index(a,b,c),我们希望将来的查询语句,对于联合索引应用越充分越好
key_len可以帮助我们判断,此次查询走了联合索引的几部分。
比如下面的这些结论,并不是随便说的,咱们也可以用key_len来验证下面的结论的正确性。

-- 创建idx(a,b,c)联合索引,等价于a  ab  abc这三个索引都被创建了
idx(a,b,c) ---> a  ab  abc 

-- 全部覆盖(也可以是and or in等作为条件),99%的情况下走索引
select * from t1 where  a=  and  b=  and  c=
select * from t1 where  a in  and b in and c in
select * from t1 where  b=  and c=  and  a= # 虽然a放在最后面,但是优化器会自动把a放到前面,来满足联合索引的查询条件

-- 部分覆盖(也可以是and or in等作为条件),部分索引只要查询的条件只包含a  ab  abc就走索引,其他的就不走索引了
select * from t1 where a= and b=  # 走索引
select * from t1 where a=         # 走索引
select * from t1 where a= and c=  # 不走索引
select * from t1 where a= and b > < >= <= like and c= # 遇到不等值,不等值后面的索引就不生效了。索引整体不走索引
select xxx from t1 where a order by b  # 走a、b的索引,要看执行循序
select xxx from t1 where c order by a  # 不能走索引
-- 不覆盖
bc  b  c

key_len的计算: idx(a,b,c)
假设,某条件查询可以完全覆盖三列联合索引。例如:
select * from t1 where a= and b= and c=
key_len= a长度 + b长度 + c长度

长度:列的最大存储值字节长度
长度受到:数据类型,字符集影响

数字:
				not null	没有not null(会专门占用一个字节记录空还是非空) 

	tinyint		1				1+1
	int			4				4+1
	bigint      8				8+1
key_len:
a int not null ----> 4
a int		   ----> 5


字符: utf8 ----> 一个字符最大占3个字节
     (utf8mb4 ---> 一个字符最大占4个字节)
				not null	没有not null(会专门占用一个字节记录空还是非空) 
	char(103*10 		3*10+1
	varchar(10) 3*10+2		3*10+2+1
	
小提示:varchar会留一个或两个字节存储字符串长度,key_len算的最大预留长度,所以是在基础上加2	

b() char(10) not null   30
b char(10) 				  31
c varchar(10) not null	  32
c varchar(10) 			  33


举个栗子:
create table t1(
a int not null,
b int,
c char(10) not null,
d varchar(10)
)charset = utf8mb4

alter table t1 add index idx(a,b,c,d);

问:查询中完全覆盖到4列索引,key_len是多少?(92create table t1(
a int not null,       4
b int,				  5
c char(10) not null,  40
d varchar(10)		  43
)charset = utf8mb4
-- 验证查看key_len的值
msyql> desc select * from t1 where a=1 and b=1 and c='a' and d='a';
mysql> desc select * from t1 where a=1 and b=1 and c='a';
mysql> desc select * from t1 where a=1 and b=1;
mysql> desc select * from t1 where a=1;
 

小提示:
(1)ch% 走的是最左前缀索引,%ch% 这样不走索引的原因是,最左边的%是不能进行排序
(2)not in != 这样不走索引的原因:不是确定的值,无法走索引树进行匹配
但是主键的!=可以走索引,自动转化成大于小于这种形式,走的range类型索引
(3)utf8 一个字符最大占3个字节,utf8mb4 一个字符最大占4个字节

1.4.5 extra

using filesort(不正常的现象): 表示此次查询使用到了文件排序,说明在查询中的排序操作 order by group by distinc...

解决方法:order by group by distinc...这样也走索引,可以创建联合索引,使其生效。(与where条件一块做联合索引,这样才有效果)

结论: 
1.当我们看到执行计划extra位置出现filesort,说明由文件排序出现
2.观察需要排序(ORDER BY,GROUP BY ,DISTINCT )的条件,有没有索引
3. 根据子句的执行顺序,去创建联合索引

Using index condition(正常的现象): 索引覆盖

举个栗子:

-- extra: using filesort(不正常的现象)
mysql> desc select * from city where countrycode='CHN' order by population;
-- 增加联合索引,使其order by的索引也进行生效
mysql> alter table city add index idx_c_p(countrycode,population);
-- 再次查询执行计划,就不会再有extra: using filesort
mysql> desc select * from city where countrycode='CHN' order by population;

2. 索引应用规范

业务
1.产品的功能
2.用户的行为
"热"查询语句 --->较慢--->slowlog
"热"数据

2.1 建立索引的原则(DBA运维规范)

-- 2.1.0 说明
为了使索引的使用效率更高,在创建索引时,必须考虑在哪些字段上
创建索引和创建什么类型的索引。那么索引设计原则又是怎样的?

-- 2.1.1 (必须的) 建表时一定要有主键,一般是个无关列
(必须的) 建表时一定要有主键,一般是个无关列

-- 2.1.2 选择唯一性索引
唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。
如果使用姓名的话,可能存在同名现象,从而降低查询速度。

优化方案:
(1) 如果非得使用重复值较多的列作为查询条件(例如:男女),可以将表逻辑拆分
(2) 可以将此列和其他的查询类,做联和索引
select count(*) from world.city;
select count(distinct countrycode) from world.city;
select count(distinct countrycode,population ) from world.city;

-- 2.1.3 (必须的) 为经常需要where 、ORDER BY、GROUP BY,join on等操作的字段
排序操作会浪费很多时间。
where  A B C      ----》 A  B  C
in 
where A   group by B  order by C
A,B,C

如果为其建立索引,优化查询
注:如果经常作为条件的列,重复值特别多,可以建立联合索引。

-- 2.1.4 尽量使用前缀来索引
如果索引字段的值很长,最好使用值的前缀来索引。

-- 2.1.5 限制索引的数目
索引的数目不是越多越好。
可能会产生的问题:
(1) 每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。
(2) 修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。
(3) 优化器的负担会很重,有可能会影响到优化器的选择.
percona-toolkit中有个工具,专门分析索引是否有用

-- 2.1.6  删除不再使用或者很少使用的索引(percona toolkit)
pt-duplicate-key-checker

表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。

-- 2.1.7 大表加索引,要在业务不繁忙期间操作

-- 2.1.8 尽量少在经常更新值的列上建索引

-- 2.1.9 建索引原则
(1) 必须要有主键,如果没有可以做为主键条件的列,创建无关列
(2) 经常做为where条件列  order by  group by  join on, distinct 的条件(业务:产品功能+用户行为)
(3) 最好使用唯一值多的列作为索引,如果索引列重复值较多,可以考虑使用联合索引
(4) 列值长度较长的索引列,我们建议使用前缀索引.
(5) 降低索引条目,一方面不要创建没用索引,不常使用的索引清理,percona toolkit(xxxxx)
(6) 索引维护要避开业务繁忙期

2.2 不走索引的情况(开发规范)

-- 2.2.1 没有查询条件,或者查询条件没有建立索引
select * from tab;       全表扫描。
select  * from tab where 1=1;
在业务数据库中,特别是数据量比较大的表。
是没有全表扫描这种需求。
1、对用户查看是非常痛苦的。
2、对服务器来讲毁灭性的。
(1select * from tab;
SQL改写成以下语句:
select  * from  tab  order by  price  limit 10 ;    需要在price列上建立索引
(2select  * from  tab where name='zhangsan'          name列没有索引
改:
1、换成有索引的列作为查询条件
2、将name列建立索引

-- 2.2.2 查询结果集是原表中的大部分数据,应该是25%以上。
查询的结果集,超过了总数行数25%,优化器觉得就没有必要走索引了。

假如:tab表 id,name    id:1-100w  ,id列有(辅助)索引
select * from tab  where id>500000;
如果业务允许,可以使用limit控制。
怎么改写 ?
结合业务判断,有没有更好的方式。如果没有更好的改写方案
尽量不要在mysql存放这个数据了。放到redis里面。

-- 2.2.3 索引本身失效,统计数据不真实
索引有自我维护的能力。
对于表内容变化比较频繁的情况下,有可能会出现索引失效。
一般是删除重建

现象:
有一条select语句平常查询时很快,突然有一天很慢,会是什么原因
select?  --->索引失效,,统计数据不真实
DML ?   --->锁冲突


-- 2.2.4 查询条件使用函数在索引列上,或者对索引列进行运算,运算包括(+,-,*,/,! 等)
例子:
错误的例子:select * from test where id-1=9;
正确的例子:select * from test where id=10;
算术运算
函数运算
子查询

-- 2.2.5 隐式转换导致索引失效.这一点应当引起重视.也是开发中经常会犯的错误.
这样会导致索引失效. 错误的例子:
mysql> alter table tab add index inx_tel(telnum);
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql>
mysql> desc tab;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id    | int(11)    | YES  |    | NULL    |      |
| name  | varchar(20) | YES  |    | NULL    |      |
| telnum | varchar(20) | YES  | MUL | NULL    |      |
+--------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
mysql> select * from tab where telnum='1333333';
+------+------+---------+
| id  | name | telnum  |
+------+------+---------+
|    1 | a    | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
mysql> select * from tab where telnum=1333333;
+------+------+---------+
| id  | name | telnum  |
+------+------+---------+
|    1 | a    | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
mysql> explain  select * from tab where telnum='1333333';   # 走索引
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key    | key_len | ref  | rows | Extra                |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+

|  1 | SIMPLE      | tab  | ref  | inx_tel      | inx_tel | 63      | const |    1 | Using index condition |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)
mysql> explain  select * from tab where telnum=1333333;  # 不走索引
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra      |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | tab  | ALL  | inx_tel      | NULL | NULL    | NULL |    2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

-- 2.2.6 <> ,not in 不走辅助索引(特殊情况:可以走聚簇索引)
EXPLAIN  SELECT * FROM teltab WHERE telnum  <> '110';
EXPLAIN  SELECT * FROM teltab WHERE telnum  NOT IN ('110','119');

mysql> select * from tab where telnum <> '1555555';
+------+------+---------+
| id  | name | telnum  |
+------+------+---------+
|    1 | a    | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
mysql> explain select * from tab where telnum <> '1555555';

单独的>,<,in 有可能走,也有可能不走,和结果集有关,尽量结合业务添加limit
orin  尽量改成union
EXPLAIN  SELECT * FROM teltab WHERE telnum  IN ('110','119');
改写成:
EXPLAIN SELECT * FROM teltab WHERE telnum='110'
UNION ALL
SELECT * FROM teltab WHERE telnum='119'

-- 2.2.7 like "%_" 百分号在最前面不走
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '31%'  走range索引扫描
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '%110'  不走索引
%linux%类的搜索需求,可以使用elasticsearch+mongodb 专门做搜索服务的数据库产品

小提示:(对上面的内容进行总结)
(1)5.7以前可以查看mysql.innodb_index_stats简单的评估一下哪些索引用的频率比较高。5.7中可以查看视图sys.schema_unused_indexes可以看到哪些索引没有被使用过。也可以使用第三方工具percona-toolkit可以收集不怎么常用的索引,然后区删除这些索引。

(2)统计信息: mysql.innodb_index_stats mysql.table_stats存放的是表和索引的统计信息
这两个表不是实时更新的,统计信息表和真实的数据不准确很有可能造成索引的失效
办法: 删除重建索引或者 optimize table city;

(3)隐式转换:int类型转换为char类型—>应用了函数进行转换,索引不走索引
telnum列是字符串类型,并且telnum设置了索引
select * from a where telnum=110 # int类型转换为char类型
select * from a where telnum=‘110’
上面两个查询结果一样,但是第一个不走索引(应用了函数进行转换)

(4)可以使用sql_yog来查找重复的索引,并且删除它

在这里插入图片描述
在这里插入图片描述

3. 优化器针对索引的算法

扩展内容: 优化器针对索引的算法
AHI和Chage buffer默认自己就优化
优化器算法介绍:

-- 查看优化器算法
mysql> select @@optimizer_switch;
index_merge=on,
index_merge_union=on,
index_merge_sort_union=on,
index_merge_intersection=on,
engine_condition_pushdown=on,
-- 索引下推
index_condition_pushdown=on,
mrr=on,mrr_cost_based=on,
block_nested_loop=on,
batched_key_access=off,
materialization=on,
semijoin=on,
loosescan=on,
firstmatch=on,
duplicateweedout=on,
subquery_materialization_cost_based=on,
use_index_extensions=on,
condition_fanout_filter=on,
derived_merge=on

如何使算法生效?
(1)my.cnf 配置文件中设置
(2)set global optimizer_switch='batched_key_access=off'
(3) hints 使单条的sql语句生效(了解即可)

3.1 MySQL索引自优化-AHI

MySQL的InnoDB引擎,能够创建只有Btree。
AHI(Adaptive Hash Index):自适应的hash索引表

AHI作用:
自动评估"热"的内存索引page,生成HASH索引表
帮助InnoDB快速读取索引页,加快索引读取的速度。
相当于索引的索引。

在这里插入图片描述
小提示:
(1)索引页在内存中是没有没有规律的比较杂乱,不便于内存读取数据的,不清楚哪些经常使用和不经常使用的。mysql会在内存中经常使用的索引页生成一个内存的hash表(AHI),使得我们访问热的数据页的时候更加迅速。索引AHI可以理解索引页的索引,作用就是快速找到内存中的索引页
(2)自适应hash索引是innodb独有的,其他引擎是没有的

3.2 MySQL索引自优化-Chage buffer

第四部分:索引问题总结中有详细说明

比如insertupdatedelete数据
对于聚簇索引会立即更新的
对于辅助索引,不是实时更新的
在innodb内存机构中,加入了insert buffer(会话),现在版本叫change。
change buffer功能是临时缓冲辅助索引需要的数据更新。
当我们需要查询新insert的数据,会在内存中进行merge(合并)操作,此时辅助索引就是最新的。

小提示:
(1)change buffer不能调太大,浪费内存,因为一个用户连接就会分配一个。

3.3 ICP索引下推算法

ICP:索引下推

作用: 解决了联合索引只能部分应用情况。为了使减少没必要的数据页被扫描。
将不走索引的条件,在engine层取数据之前先做c的二次过滤。
index(a,b,c)
select * from t1 where a=xx and c=xx
在server-sql层先做a列过滤条件的索引优化
在将c列过滤下推到engine层在做过滤

举个栗子:

正常的执行逻辑:选择B方案,走索引查询
在这里插入图片描述
下图中select * from t1 where a=xx and c=yy有联合索引index(a,b,c)根据key_leng的结论,只走了a的索引,这时sql层分析的结果是遍历a列所有的数据页,然后告知引擎层。又因为有了IPC,索引取数据的需求直接下推到引擎层,引擎层发现c也有索引,索引拿着c的条件在进行一次过滤,最终得结果需要读取哪些数据页,加载到内存,最后从这些数据也中得出最终结果。 就比如下图中如果没有ICP就要从磁盘中读10个数据页到内存,如果有了数据页就从磁盘中读两个数据页加载到内存中。

作用: 减少无关数据页的扫描,如果没有IPC会读取有关a的所有数据页加载到内存中
结论: 就是在sql层和存储引擎曾经历了两次过滤,最终得出读取的数据页,在加载到内存中。
注意: 跟联合索引不一样,只是经历了两次过滤。索引的应用在SQL层,解析优化的时候会应用到,引擎层就是把索引页加载起来,不做过滤。但是有了ICP之后,即在sql层做过滤又在引擎层做过滤(如果不走索引,引擎拿着sql层给出的方案去找数据页)

没有ICP的时候:select * from t1 where a= and c= 先把基于a条件的所有数据页全过滤出来,然后在c的条件在遍历一遍
有ICP的时候:select * from t1 where a= and c= SQL层虽然说需要把提取a条件的数据页出来 ,先不着急取出来,因为有了索引下推,发现c列也是有索引的,引擎层在拿c列的条件,根据综合条件,最后再从磁盘上提取出来。

在这里插入图片描述
有了ICP之后:
在这里插入图片描述

官方图:
在这里插入图片描述
在这里插入图片描述

3.4 MRR

设置使用MRR(multi-range read)
mysql> set global optimizer_switch='mrr=on,mrr_cost_based=off'

辅助索引 - - - 回表 - - -> 聚簇索引
有了MRR就转换为:
辅助索引 - - - sort id - - 回表 - -> 聚簇索引

小提示:
(1) 默认mrr_cost_based=on是开启的,如果使用mrr,需要把这关闭mrr_cost_based=off,这样大概率才走mrr。
(2) 没有mrr之前,辅助索引查询有多个name=‘zs’ 但是id不同,这时需要进行聚簇索引,一个id一个id的进行回表,导致回表次数增多会产生随机IO
(3)有个mrr之后,回表查询之前,先把ID值在缓冲区里排个序,在同一个数据页中的一块查询,这样可以检查随机IO(直接简洁理解在缓冲区的id可以一次性的把数据拿出来,减少回表)
(4)辅助索引拿到一个id值就到聚簇索引中回表一次。辅助索引查到的id放到缓冲区,排个序然后一次性的到聚簇索引中回表

官方图:
(1)没有mrr的模型图。左侧是辅助索引,右侧是聚簇索引。

在这里插入图片描述
(2)有mrr的模型图,中间多了个缓冲区。

在这里插入图片描述

3.5 SNLJ

NLJ(Nested-Loop Join Algorithms)
SNLJ(Simple NLJ) 没有任何索引的情况下走这个算法,双循环嵌套查询匹配。

直接举例子:

-- 多表连接查询
A join B on A.xx = B.yy where ...
-- 伪代码(嵌套for循环)
for each row in A matching range{
	for each row in B{
		A.xx = B.yy, send to client
	}
}
-- 以上栗子,可以通过left join 强制驱动表(把行数少的表做为驱动表)

3.6 BNLJ

BNLJ(Block Nested-Loop Join)MySQL 5.7版本默认是开启的

在A和B关联条件匹配时,不在一次一次进行循环。而是采用一次性驱动表的关联值和非驱动表匹配,一次性返回结果。主要优化:减少了CUP的消耗,减少IO的次数。

外层循环(A表)是驱动表,where 没有过滤条件或者where没有索引的话,也会对A表进行全表扫描,然后和B表进行关联。如果有过滤条件,先把A表过滤出来的条件放到join buffer中,然后在一次性的去查询。

如果有索引的情况,在辅助索引读取的值放到join buffer中,然后在一次性去另一张表进行去匹配,尽可能减少循环的次数。不是逐行进行匹配了,而是放到一堆,然后一次性的进行匹配,减少的关联的次数,循环的次数。(缺点还是会重复读取数据页,有随机io产生,索引出现了下面的BKA算法)
在这里插入图片描述

3.7 BKA

BKA(Batched Key Access joins)
使用BAK的前提:非驱动表(上面栗子中的B表就是非驱动表)的关联列有辅助索引(主键唯一键不需要这个算法,因为查出来的数据就是唯一,本身就是顺序的,不需要额外的排序)

BKA这种算法会依赖于mrr的算法(BNLJ+NRR)
BNLJ 在连表时有个join buffer缓冲区,一堆一堆的去查询。
NRR(辅助索引和聚簇索引之间的关系),在聚簇索引的时候做了个排序

开启方式:

mysql> set global optimizer_switch='mrr=on,mrr_cost_based=off';
mysql> set global optimizer_switch='batched_key_access=on';

如果两条数据在一个数据页上,那么就读一次数据页就可以读取到两条数据了
可以减少io的次数(如下图如果没有sort,可能读取最大次数六次,有了sort就只读四次)

在这里插入图片描述

4. 索引问题总结

问题一: 回表查询详细解释

-- 1. 数据库中的表长成什么样?
mysql用来存储数据行的逻辑结构,表的数据行最终存储到了很多的page上。
innodb存储引擎,会按照聚簇索引,有序的组织存储表数据到各个区的连续的页上。
这些连续的数据页,成为了聚簇索引的叶子节点。你可以认为聚簇索引就是原表数据。
所以回表即是,回聚簇索引

-- 2. 什么时候回表
辅助索引: 将辅助索引列值+ID主键值,构建辅助索引B树结构
用户使用辅助索引列作为查询条件查询时,首先扫描辅助索引的b树。
(1) 如果辅助索引能够完全覆盖我们的查询结果时,就不需要回表了。
(2) 如果不能完全覆盖到,只能通过得出的ID主键值,回到聚簇索引(回表)扫描

-- 3. 回表带来什么影响
(1) IO量级变大(查询的次数变多,IO也就变大了)
(2) IOPS会增大(每秒的IO次数会变大,每块磁盘瓶颈的点,有上限的,如果达到上线就要等待)
(3) 随机IO会增大(一个数据页跳到另一个数据页里面,有可能不在一个连续的区里面,不在连续的区里面,可能在磁盘读的时候就是随机性的)

-- 4. 怎么减少回表
(1) 将查询尽可能用ID主键查询
(2) 设计合理的联合索引
	 完全覆盖: select name,age,gender from t1 where name='zs' # name,age,gender可以创建一个联合索引
(3) 更精确的查询条件+联合索引
	 select * from t1 where name='zs' and addr='bj' # 精确的条件,减少回表的次数
(4) 优化器算法: MRR(扩展内容)

问题二: 更新数据时,会对索引有影响吗?数据的变化会使索引实时更新吗?

比如insertupdatedelete数据
对于聚簇索引会立即更新的
对于辅助索引,不是实时更新的
在innodb内存机构中,加入了insert buffer(会话),现在版本叫change。
change buffer功能是临时缓冲辅助索引需要的数据更新。
当我们需要查询新insert的数据,会在内存中进行merge(合并)操作,此时辅助索引就是最新的。下面有详细解释。

详细解释:
ibd文件就是表空间文件(段),有很多数据页,有存数据的,有存聚簇索引、辅助索引的。下图中C代表聚簇索引(可以理解为表的数据行,叶子节点),S代表辅助索引(部分数据)

在这里插入图片描述

正常的读取流程:select * from t1 where name='zs'
首先会判断有没有针对这个列的索引,如果有的话就会把S数据页调到内存中去。如何需要回表,然后再把聚簇索引加载到内存中。(数据库静止的状态下,没有更新也没有删除)

在这里插入图片描述

如果新录入新数据,首先会更新聚簇索引的值,聚簇索引会被立即更新的。辅助索引的信息不想立即让他更新,因为将来对辅助索引列值更新的请求会更多一些。因为像主键这些无非加一些值,很快就完成了,在内存中然后写入磁盘。但是对辅助索引来说不是立即完成的,而是先把这些数据放到change buffer里面(独立区域)。S1: 更新的辅助索引的值

在这里插入图片描述

如果更新的辅助索引(此时在change buffer中)要读取怎么办那?先把idb中的S加载到内存中,然后S1于S做merge,然后在辅助索引,最后在聚簇索引(回表查询)。有了change buffer这个内存结构,使得我们呢的增删改这种操作,对于辅助列的值来讲,没有立即更新到磁盘的S区域里面去,索引可以达到减轻索引更新的频次。如果查询完成后,也会把S1存到磁盘的S区。

change buffer会话级别的,每个连接进来的都会分配一个change buffer,会有很多change buffer,每个用户都有 ,用完了就会回收。这个大小是可以控制的,如果大小不够的话,会立即更新索引树。可以通过增大change buffer可以优化insert
,大批量的insert,要插入100w行大事务,可以临时调大change buffer加快插入的过程。还有大批量的update、delete

在这里插入图片描述

问题三: 如何知道用户走了我们设置的索引?遇到双11那种大量访问时,索引不能及时设置,大厂如何解决这种问题的?

(1)slowlog日志可以收集,不走索引的语句
(2)双十一的时候,并发度太高,提前1-2周将热点商品数据,灌入到Tair(类似于redis,memcache)集群中。

问题四: msyql后台查询表数据,中文数据乱码,在sqlyog中显示正常

(1) Linux没有中文语言支持包
(2) 字符集设置有问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值