Explain各参数的含义以下:html
列名 | 说明 |
---|---|
id | 执行编号,标识select所属的行。若是在语句中没有子查询或关联查询,只有惟一的select,每行都将显示1.不然,内层的select语句通常会顺序编号,对应于其在原始语句中的位置 |
select_type | 显示本行是简单或复杂select,若是查询有任何复杂的子查询,则最外层标记为PRIMARY(DERIVED、UNION、UNION RESUIT) |
table | 访问引用哪一个表(引用某个查询,如“derived3”) |
type | 数据访问/读取操做类型(All、index、range、ref、eq_ref、const/system、NULL) |
possible_key | 揭示哪一些索引可能有利于高效的查找 |
key | 显示mysql实际决定采用哪一个索引来优化查询 |
key_len | 显示mysql在索引里使用的字节数 |
ref | 显示了以前的表在key列记录的索引中查找值所用的列或常量 |
rows | 为了找到所须要的行而须要读取的行数,估算值 |
Extra | 额外信息,如using index、filesort等 |
consts
:常出如今主键或惟一索引与常量值进行比较的场景下,此时查询性能是最优的ref
:当链接使用的是前缀索引或链接条件不是 PRIMARY KEY 或 UNIQUE INDEX 时则使用它range
:使用索引进行范围扫描,常见于 between、> 、< 这样的查询条件index
:索引链接类型与 ALL 相同,只是扫描的是索引树,一般出如今索引是该查询的覆盖索引的状况阿里编码规范要求:至少要达到 range 级别,要求是 ref 级别,若是能够是 consts 最好
mysql
实际在查询中是否使用到索引的标志字段面试
Extra 列主要用于显示额外的信息,常见信息及其含义以下:sql
# 仅在服务器环境下或经过Navicat进入命令列界面 explain extended SELECT * FROM `student` where `name` = 1 and `age` = 1; # 再执行 show warnings; # 结果以下: /* select#1 */ select `mytest`.`student`.`age` AS `age`,`mytest`.`student`.`name` AS `name`,`mytest`.`student`.`year` AS `year` from `mytest`.`student` where ((`mytest`.`student`.`age` = 1) and (`mytest`.`student`.`name` = 1))
为何要作这个事呢?咱们知道Mysql有一个最左匹配原则,那么若是个人索引建的是age,name,那我以name,age这样的顺序去查询可否使用到索引呢?其实是能够的,就是由于Mysql查询优化器能够帮助咱们自动对SQL的执行顺序等进行优化,以选取代价最低的方式进行查询(注意是代价最低,不是时间最短)数据库
如表中数据须要进行深度分页,如何提升效率?在阿里出品的Java编程规范中写道:编程
利用延迟关联或者子查询优化超多分页场景
性能优化
说明:MySQL 并非跳过 offset 行,而是取 offset+N 行,而后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就很是的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写服务器
# 反例(耗时129.570s) select * from task_result LIMIT 20000000, 10; # 正例(耗时5.114s) SELECT a.* FROM task_result a, (select id from task_result LIMIT 20000000, 10) b where a.id = b.id; # 说明 task_result表为生产环境的一个表,总数据量为3400万,id为主键,偏移量达到2000万
若是数据表的状况已知,某个业务须要获取符合某个Where条件下的一条数据,注意使用Limitmysql优化
说明:在不少状况下咱们已知数据仅存在一条,此时咱们应该告知数据库只用查一条,不然将会转化为全表扫描函数
# 反例(耗时2424.612s) select * from task_result where unique_key = 'ebbf420b65d95573db7669f21fa3be3e_861414030800727_48'; # 正例(耗时1.036s) select * from task_result where unique_key = 'ebbf420b65d95573db7669f21fa3be3e_861414030800727_48' LIMIT 1; # 说明 task_result表为生产环境的一个表,总数据量为3400万,where条件非索引字段,数据所在行为第19486条记录
# 反例 INSERT into person(name,age) values('A',24) INSERT into person(name,age) values('B',24) INSERT into person(name,age) values('C',24) # 正例 INSERT into person(name,age) values('A',24),('B',24),('C',24); # 说明 比较常规,就很少作说明了
like语句通常业务要求都是 '%关键字%'
这种形式,可是依然要思考可否考虑使用右模糊的方式去替代产品的要求,其中阿里的编码规范提到:
页面搜索严禁左模糊或者全模糊,若是须要请走搜索引擎来解决
# 反例(耗时78.843s) EXPLAIN select * from task_result where taskid LIKE '%tt600e6b601677b5cbfe516a013b8e46%' LIMIT 1; # 正例(耗时0.986s) select * from task_result where taskid LIKE 'tt600e6b601677b5cbfe516a013b8e46%' LIMIT 1 ########################################################################## # 对正例的Explain 1 SIMPLE task_result range adapt_id adapt_id 98 99 100.00 Using index condition # 对反例的Explain 1 SIMPLE task_result ALL 33628554 11.11 Using where # 说明 task_result表为生产环境的一个表,总数据量为3400万,taskid是一个普通索引列,可见%%这种匹配方式彻底没法使用索引,从而进行全表扫描致使效率极低,而正例经过索引查找数据只须要扫描99条数据便可
# 反例 select * from task_result where id + 1 = 15551; # 正例 select * from task_result where id = 15550; ########################################################################## # 对正例的Explain 1 SIMPLE task_result const PRIMARY PRIMARY 8 const 1 100.00 # 对反例的Explain 1 SIMPLE task_result ALL 33631512 100.00 Using where # 说明 其实在知道了有SQL优化器以后,我我的感受这种普通的表达式转换应该能够提早进行处理再进行查询,这样一来就能够用到索引了,可是问题又来了,若是mysql优化器能够提早计算出结果,那么写sql语句的人也必定能够提早计算出结果,因此矛盾点在这个地方,致使5.7版本之前的此种状况都没法使用索引吧,将来可能会对其进行优化
说明:NULL 与任何值的直接比较都为 NULL
# 1) NULL<>NULL 的返回结果是 NULL,而不是 false。 # 2) NULL=NULL 的返回结果是 NULL,而不是 true。 # 3) NULL<>1 的返回结果是 NULL,而不是 true。
我所在的公司基本禁止了多表查询,那若是必须使用到的话,咱们能够一块儿参考一下阿里的编码规范
Eg:超过三个表禁止 join。须要 join 的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段须要有索引
以前回答一些面试问题的时候,对某一个点的理解出现了误差,即我认为只要查询的列有索引则必定会使用索引去Push数据
然而实际上不只仅是这样,真正应该是:针对查询的数据行占总数据量过多时会转化成全表查询
那么这个过多指代的是多少呢?
个人测试结果是50%,但我的认为MySQL优化器不会彻底纠结于行数区分是否全表,而是有不少其余因素综合考虑发现全表扫描的效率更高等等,因此充分认识到该问题便可
阿里的Java编码规范中有如下内容:
【强制】不要使用 count(列名) 或 count(常量) 来替代 count(*)
count(*) 是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行
阿里的Java编码规范中有如下内容:
【推荐】防止因字段类型不一样形成的隐式转换,致使索引失效
实际上数据库在查询的时候会做一层隐式的转换,好比 varchar 类型字段经过 数字去查询
# 正例 EXPLAIN SELECT * FROM `user_coll` where pid = '1'; type:ref ref:const rows:1 Extra:Using index condition # 反例 EXPLAIN SELECT * FROM `user_coll` where pid = 1; type:index ref:NULL rows:3(总记录数) Extra:Using where; Using index # 说明 pid字段有相应索引,且格式为varchar
感谢如下博文及其做者:
自建数据表进行测试
CREATE TABLE `student` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(255) NOT NULL, `class` varchar(255) DEFAULT NULL, `page` bigint(20) DEFAULT NULL, `status` tinyint(3) unsigned NOT NULL COMMENT '状态:0 正常,1 冻结,2 删除', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4
插入数据
DELIMITER ;; CREATE PROCEDURE insertData() BEGIN declare i int; set i = 1 ; WHILE (i < 1000000) DO INSERT INTO student(`name`,class,`page`,`status`) VALUES(CONCAT('class_', i), CONCAT('class_', i), i, (SELECT FLOOR(RAND() * 2))); set i = i + 1; END WHILE; commit; END;; CALL insertData();