MYSQL面试--explain优化(心法+详细案例)(五)

-- 8.explain优化
-- 资料:https://www.bilibili.com/video/av21334868/?p=32    -- mysql优化视频
-- 资料:https://www.bilibili.com/video/av21334868/?p=44    -- explain例子视频解析
-- 资料:https://www.cnblogs.com/gdwkong/articles/8505125.html    -- explain超详尽例子讲解:

/*
join语句的优化:
1.尽可能减少join语句中的Nestedloop嵌套循环的循环总次数:"永远用小结果集驱动大的结果集"。
2.优先优化NestedLoop的内层循环;
3.保证join语句中被驱动表上Join条件字段已经被索引
4.当无法保证被驱动表额Join条件字段被索引且内存资源充足的前提下,可以调高JoinBuffer设置
*/

/*
面试坑点:
定值、范围还是排序,一般order by 是给个范围
group by 基本需要进行排序,会有临时表产生
*/

/*
优化心法口诀(重点)
1.全值匹配我最爱
2.最佳左前缀法则:如果索引了多列,查询要从最左前列开始且不跳过索引中的列
3.不在索引列上做任何操作(计算、函数、(自动or手动)转化),会导致索引失效而转向全表扫描
4.储存引擎不能使用索引中范围条件右边的列
5.尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select*
6.mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
7.is null ,is not null也无法使用索引
8.like以通配符开头('%abc...')mysql索引失效会变成全表扫描的操作
9.字符串不加单引号索引失效
10.少用or,用它来连接时索引会失效
*/

/*
【优化总结口诀】
全值匹配我最爱,最左前缀要遵守
带头大哥不能死,中间兄弟不能断
索引列上少计算,范围之后全是效
Like百分写最右,覆盖索引不写星
不等空值还有or,索引失效要少用
VAR引号不可丢 ,SQL高级也不难
*/

/*
一般性建议
1.对于单值索引,尽量选择针对当前query过滤性更好的索引
2.在选择组合索引的时候,当前query中过滤性更好的索引在字段顺序中,越靠前越好
3.在选择组合索引的时候,尽量选择能够包含当前query中的where字句中更多的索引
4.尽可能通过分析统计信息和调整query的写法来到达选择合适索引的目的
*/



-- 建表
CREATE TABLE t1(
  id INT PRIMARY KEY,
  NAME VARCHAR(20),
  col1 VARCHAR(20),
  col2 VARCHAR(20),
  col3 VARCHAR(20)
);
CREATE TABLE t2(
  id INT PRIMARY KEY,
  NAME VARCHAR(20),
   col1 VARCHAR(20),
   col2 VARCHAR(20),
  col3 VARCHAR(20)
);
CREATE TABLE t3(
  id INT PRIMARY KEY,
  NAME VARCHAR(20),
    col1 VARCHAR(20),
   col2 VARCHAR(20),
  col3 VARCHAR(20)
);
INSERT INTO t1 VALUES(1,'zs1','col1','col2','col3');
INSERT INTO t2 VALUES(1,'zs2','col2','col2','col3');
INSERT INTO t3 VALUES(1,'zs3','col3','col2','col3');

CREATE INDEX ind_t1_c1 ON t1(col1);
CREATE INDEX ind_t2_c1 ON t2(col1);
CREATE INDEX ind_t3_c1 ON t3(col1);

CREATE INDEX ind_t1_c12 ON t1(col1,col2);
CREATE INDEX ind_t2_c12 ON t2(col1,col2);
CREATE INDEX ind_t3_c12 ON t3(col1,col2);

-- id列
-- 1.id值相同,执行顺序由上而下
EXPLAIN SELECT t2.* FROM t1,t2,t3 WHERE t1.id = t2.id AND t1.id= t3.id AND t1.name = 'zs';
-- 2.id值不同,id值越大优先级越高,越先被执行(嵌套查询括号越深,越先执行)
EXPLAIN SELECT t2.* FROM t2 WHERE id = (SELECT id FROM t1 WHERE id = (SELECT t3.id FROM t3 WHERE t3.name='zs3'));

-- select_type列:
	-- simple:简单查询(不含子查询和union)
	EXPLAIN SELECT * FROM t1;
	-- primary:查询中包含子查询,则最外层标记primary
	EXPLAIN SELECT * FROM t1 WHERE id = (SELECT id FROM t2 WHERE NAME = 'zs2');
	-- SUBQUERY:select或where里的子查询
	EXPLAIN SELECT * FROM t1 WHERE id = (SELECT id FROM t2 WHERE NAME = 'zs2');
	-- DERIVED:from里的子查询
	EXPLAIN SELECT k.id FROM (SELECT * FROM t1 WHERE id = 1 UNION SELECT * FROM t2)k;
	-- UNION:出现在union后面的select,若from里的子查询出现union,外层select标记derived
	EXPLAIN SELECT col1,col2 FROM t1 UNION SELECT col1,col2 FROM t2;
	-- UNION RESULT: 从union表获取结果的select
	EXPLAIN SELECT col1,col2 FROM t1 UNION SELECT col1,col2 FROM t2;	

-- table列:跟那个表相关

-- partitions列:暂无资料

-- type列:
	-- all:全表扫描 ,将遍历全表以找到匹配的行
	EXPLAIN SELECT * FROM t1;
	-- index: 扫描整个索引表, index 和all的区别为index类型只遍历索引树. 这通常比all快,因为索引文件通常比数据文件小,虽然index和all都是读全表,但是index是从索引中读取,而all是从硬盘中读取数据
	EXPLAIN SELECT id FROM t1;
	-- range:只检索给定范围的行, 使用一个索引来选择行
	EXPLAIN SELECT * FROM t1 WHERE id BETWEEN 1 AND 10;
	-- ref:非唯一性索引扫描,返回匹配某个单独值的所有行
	EXPLAIN SELECT * FROM t1 WHERE col1='zs1';
	-- eq_ref:对于每个索引键,表中只有一条记录与之匹配, 常见于主键或者唯一索引扫描
	EXPLAIN SELECT * FROM t1,t2 WHERE t1.id = t2.id;
	-- const:通过索引一次就找到了
	EXPLAIN SELECT * FROM t1 WHERE id = 1;
	-- system:表中只有一行数据(难以实现)
	-- null

	-- 结论;最好到最差依次是: system > const > eq_ref>ref >range > index > all , 最好能优化到range级别或则ref级别

-- possible_keys列:sql可能用到的索引,实际不一定用到

-- key列:查询中真正使用到的索引,如果为null,则没使用
	-- 查询中使用覆盖索引 
	EXPLAIN SELECT t2.* FROM t1,t2,t3 WHERE t1.col1 = ' ' AND t1.id = t2.id AND t1.id= t3.id;

-- key_len列:越短越好,索引中使用的字节数,可通过该列计算查询中使用的索引的最大可能长度
	EXPLAIN SELECT * FROM t1 WHERE col1='col1' AND col2 = 'col2';
	ALTER TABLE t1 DROP INDEX ind_t1_c1;-- 注意: 为了演示这个结果,我们删除了c1上面的索引
	CREATE INDEX ind_t1_c1 ON t1(col1);-- 执行完成之后,再次创建索引;增删索引后,长度无法恢复63!!
	
-- ref列:显示索引的哪一列被使用了,如果可能的话,是一个常数
EXPLAIN SELECT * FROM t1,t2 WHERE t1.col1 = t2.col1 AND t1.col2 = 'col2';

-- rows列:根据表统计信息及索引选用的情况,估算找出所需记录要读取的行数 (有多少行记录被优化器读取)
	-- 越少越快,重要的优化指标

-- Extra:包含其它一些非常重要的额外信息

	-- Using filesort:排序与index冲突,性能下降
	EXPLAIN SELECT col1 FROM t1 WHERE col1='col1' ORDER BY col3;
	-- 解决方法:Using filesort 是Mysql里一种速度比较慢的外部排序,如果能避免是最好的了,很多时候,我们可以通过优化索引来尽量避免出现Using filesort,从而提高速度
	-- 思路1:select查询是先col1再col3,而我们没有建立col3索引,导致'额外的一次排序',所以我们要建立index(col1,col3)
	-- 思路2:索引的建立要跟select查询的顺序一致才能发挥功效,否则解决不了filesore问题
	CREATE INDEX ind_t1_c13 ON t1(col1,col3);
	CREATE INDEX ind_t1_c31 ON t1(col3,col1);
	DROP INDEX ind_t1_c13 ON t1;
	DROP INDEX ind_t1_c31 ON t1;
	EXPLAIN SELECT col1 FROM t1 WHERE col1='col1' ORDER BY col3;
	
	-- Using temporary:使用了临时表保存中间结果,Mysql在对查询结果排序时使用了临时表,常见于order by 和分组查询
	EXPLAIN SELECT col1 FROM t1 WHERE col1 >'col1' GROUP BY col2;
	-- 解决方法:
	EXPLAIN SELECT col1 FROM t1 WHERE col1 >'col1' GROUP BY col1,col2;

	-- Using index:查询操作中使用了覆盖索引(查询的列和索引列一致),避免访问了表的数据行,效率好
		-- 如果同时出现了using where, 表明索引被用来执行索引键值的查找
		-- 如果没有同时出现using where, 表明索引用来读取数据而非执行查找动作
		
	-- Coving index:覆盖索引,取的列跟索引一样或在索引内,很优秀的所查询!
	
	-- Using where:表明使用了where条件过滤

	-- using join buffer : 表明使用了连接缓存, join次数太多了可能会出现
	
	-- impossible where : where子句中的值'总是'false(多次false),不能用来获取任何数据	
	EXPLAIN SELECT * FROM t1 WHERE col1='zs' AND col1='ls';
	
	-- distinct : 优化distinct操作,在找到第一个匹配的数据后即停止查找同样的值的动作
	


-- 心法口诀练习

	-- 1.全值匹配我最爱
	-- 索引全都用到最好
	
	-- 2.最佳左前缀法则(使用到下一章储存过程的表,大家可以先执行储存过程的代码先)
	EXPLAIN SELECT * FROM test;
	-- 建立索引,注意顺序!!
	CREATE INDEX t_idname ON test(id,test_name);
	-- 删除本表的主键索引,呃,不知道为什么,主键索引导致了我下文的测试Extra项,没有using index,删了
	ALTER TABLE test DROP PRIMARY KEY;
	-- 建了复合索引,但因为该索引开头是name,但是查询并没有name,所以索引失效
	EXPLAIN SELECT * FROM test WHERE test_name = 'zhangsan25' AND test_num = 58;
	-- 按顺序用索引,效果拔群(两个索引顺序互换也不影响,因为mysql底层有自动优化器,会帮你调整顺序,但最好怎么建怎么用)
	EXPLAIN SELECT * FROM test WHERE  test_name = 'zhangsan25' AND id = 25 AND test_num = 58;
	EXPLAIN SELECT * FROM test WHERE  id = 25 AND test_name = 'zhangsan25' AND test_num = 58;
		
	-- 3.不在索引做操作	
	-- 额外知识:left(col,int)函数,相当于like,下面这句意思是查询test_name字段,从左开始3位等于'zhangsan100'的行,int参数要跟等号右边长度一致	
	EXPLAIN SELECT * FROM test WHERE LEFT(id,3) = '100';-- 对索引使用了方法操作,使得索引失效
	EXPLAIN SELECT * FROM test WHERE id = 100;-- 无操作索引,走索引,nice!!
	
	-- 4.索引中范围条件右边的列不走索引
	-- ref是null明显没有走索引
	/*
	拓展知识:ref是null,不一定说明没有走索引,还要看key_len,key_len的长度随走的索引数增多
	key_len一个索引长度是4,两个是67(在当前数据下)
	*/
	EXPLAIN SELECT * FROM test WHERE id > 100 AND test_name = 'zhangsan100' AND test_num = 208 ;
	
	-- 5.尽量使用覆盖索引
	/*
	覆盖索引百度百科:
	覆盖索引是select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖。
	理解方式一:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了(或覆盖了)满足查询结果的数据就叫做覆盖索引。 [1] 
	理解方式二:是非聚集复合索引的一种形式,它包括在查询里的Select、Join和Where子句用到的所有列(即建索引的字段正好是覆盖查询条件中所涉及的字段,也即,索引包含了查询正在查找的数据)。
	*/
	-- 两条语句对比发现,第二条覆盖索引Extra有using index,好东西,所以不用第一个
	EXPLAIN SELECT * FROM test WHERE id = 1;
	EXPLAIN SELECT id,test_name FROM test WHERE  id = 1;
	
	-- 6.mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描(mysql5.7后此条失效)
	EXPLAIN SELECT id FROM test WHERE id <> 3;
	
	-- 7.is null ,is not null也无法使用索引
	
	-- 8.%like加右边(emm..5.7也修复了这条的type=all部分,但没有修复key部分)
	-- 百分号加右边索引生效,其他不生效;或者'kk%kk%'这种索引可以生效,即只要是知道开头就可以用到索引
	EXPLAIN SELECT id,test_name FROM test WHERE  id LIKE '%100%';
	EXPLAIN SELECT id,test_name FROM test WHERE  id LIKE '%100';
	EXPLAIN SELECT id,test_name FROM test WHERE  id LIKE '100%';
	
	-- 8.1 解决like'%xxx%'时索引不被使用的方法
	-- 使用覆盖索引--select后面查询的内容是索引key,但不能包括非索引key否则就不会走索引
	
	-- 9.字符串不加单引号索引失效
	-- 字符串列如果里面的值是数字,是可以通过整型查询出结果的,因为底层会帮你做类型转换,但是不走索引
	-- 如果这条语句加锁了,会从行锁编程表锁,严重影响性能
	
	-- 10.少用or,用它来连接时索引会失效(mysql5.7也没有失效,但是union和or谁的效率高呢)
	EXPLAIN SELECT id FROM test WHERE id = 1 UNION SELECT id FROM test WHERE id = 2;
	EXPLAIN SELECT id FROM test WHERE id = 1 OR id = 2;
	
-- explain超详尽例子讲解:链接https://www.cnblogs.com/gdwkong/articles/8505125.html

	-- 案例1:单表explain优化案例
	-- 建表,抖音新浪类型的文章表
	CREATE TABLE IF NOT EXISTS article(
	id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
	author_id INT(10) UNSIGNED NOT NULL,
	category_id INT(10) UNSIGNED NOT NULL,
	views INT(10) UNSIGNED NOT NULL,
	comments INT(10) UNSIGNED NOT NULL,
	title VARBINARY(255) NOT NULL,
	content TEXT NOT NULL
	);
	-- 插入数据
	INSERT INTO article(author_id ,category_id,views,comments,title, content) VALUES
	(1,1,1,1,'1',1),
	(2,2,2,2,'2',2),
	(1,1,3,3,'3',3);

	-- 需求:查询id=1,comments大于一的情况下,views最多的atricle_id

	-- 第一次实现:简单实现任务要求,先实现,再优化
	-- 发现缺点:没有索引,all全表扫描,出现filesore,必须优化
	EXPLAIN SELECT id,author_id FROM article
	WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1; 
	
	-- 第二次优化:建立id,comments,view索引,因为查询用到了这三个字段
	-- 发现缺点:filesore没有解决,type是range可以接受,有用到索引
	/* 结论:根据B-Tree索引工作原理,先排序category_id,如果遇到相同id在排序comments,
	遇到相同comments再排序views;但是现在comments在中间位置>1(range范围查询),
	导致mysql无法利用索引再对后面的views检索,即range类型查询字段后面的索引无效*/
	-- 建立索引
	CREATE INDEX idx_article_ccv ON article(category_id,comments,views);
	-- 查看索引
	SHOW INDEX FROM article;
	-- 删除索引
	DROP INDEX idx_article_ccv ON article;
	
	-- 第三次优化:跳过comments,只建立category_id和views索引
	-- 结论:完美优化,为了不被comments打断后面额索引,放弃它建索引跟快
	CREATE INDEX idx_article_cv ON article(category_id,views);
	DROP INDEX idx_article_cv ON article;
	
	-- 案例2:双表explain优化案例
	-- 建表:class表是分类:武侠类,诗歌类,漫画类..;book表是书本;都有card属性可以连接,一本书可以属于很多类别,一个类别有很多本书
	CREATE TABLE IF NOT EXISTS class(
	id INT(10) NOT NULL AUTO_INCREMENT,
	card INT(10) UNSIGNED NOT NULL,
	PRIMARY KEY(id)
	);

	CREATE TABLE IF NOT EXISTS book(
	bookid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	card INT(10) UNSIGNED NOT NULL,
	PRIMARY KEY(bookid)
	);

	INSERT INTO class(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO class(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO class(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO class(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO class(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO class(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO class(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO class(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO class(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO class(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO class(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO class(card) VALUES(FLOOR(1+RAND()*20));


	INSERT INTO book(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO book(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO book(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO book(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO book(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO book(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO book(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO book(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO book(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO book(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO book(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO book(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO book(card) VALUES(FLOOR(1+RAND()*20));

	-- 需求:查看所有书本和其类别的信息
	
	-- 第一次实现:满足需求
	-- 缺点:type是all全表扫描,需要优化
	EXPLAIN SELECT *FROM class LEFT JOIN book ON book.`card` = class.`card`;
	
	
	-- 第二次优化:给book添加索引
	-- 从ref列、type列、rows列看,的确有优化了
	ALTER TABLE book ADD INDEX b_c(card);
	DROP INDEX b_c ON book;
	
	-- 第三次优化:给class添加索引,测试同一个sql用不同索引
	-- type是index和rows比上一个优化要差
	/* 总结:由做左连接特性决定left join一定把左表的都显示,所以优化左表收益不大;
	所以右表是我们的关键点,一定需要建索引 -- 左连接加右表,右连接加左表 
	*/
	ALTER TABLE class ADD INDEX c_c(card);
	DROP INDEX c_c ON class;
	
	
	-- 案例3:三表explain优化案例
	-- 建表,phone表,跟book和class组成三表,就是为了测试,没什么现实意义~
	CREATE TABLE IF NOT EXISTS phone(
	phoneid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	card INT(10) UNSIGNED NOT NULL,
	PRIMARY KEY(phoneid)
	);

	INSERT INTO phone(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO phone(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO phone(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO phone(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO phone(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO phone(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO phone(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO phone(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO phone(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO phone(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO phone(card) VALUES(FLOOR(1+RAND()*20));
	INSERT INTO phone(card) VALUES(FLOOR(1+RAND()*20));	
	
	-- 需求:优化如下语句
	EXPLAIN SELECT * FROM class LEFT JOIN book ON class.`card` = book.`card` LEFT JOIN phone ON book.`card` = phone.`card`;
	
	-- 优化:根据二表的案例结论,索引相反建,即可最优
	/*
	 有人问:为什么不给class建索引,三个索引不美滋滋,答案是class建了rows不会减少,
	 唯一好处就是class.type这行从all变index,优化程度不明显,而且,维护索引带来的
	 坏处更高
	 */
	ALTER TABLE book ADD INDEX b_c(card);
	ALTER TABLE phone ADD INDEX p_c(card);

猜你喜欢

转载自blog.csdn.net/qq_38056704/article/details/82807167