Mysql中由专门负责优化SELECT语句的优化器,主要功能就是通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划(他认为最优的,但**不一定是DBA觉得最优的,这部分最耗时间**)。
当客户端向MySQL请求一条Query的时候,命令解析器模块完成请求分类,区别出是SELECT并转发给MySQL Query Optimizer时,MQO会对整条Query进行优化,处理掉一些常量表达式的预算,将其换算成常量值并对,Query中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等,然后分析Query中的Hint信息(如果有的话),看显示Hint信息是否可以完全确定该Query的执行计划。如果没有Hint或Hint信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据Query进行相应的计算分析,让后再得出最后的执行计划。
CPU
IO
服务器硬件
使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从知道MySQL是如何处理你的SQL语句的,分析你查询语句或者是表结构的性能瓶颈。
在 MySQL 5.7
中,会对衍生表进行合并优化,如果要直观的查看 select_type
的值,需要临时关闭该功能(默认是打开的)
-- 关闭衍生表的合并优化
set global optimizer_switch='derived_merge=off';
-- 关闭衍生表的合并优化(只对该会话有效)
set session optimizer_switch='derived_merge=off';
-- 打开衍生表的合并优化(只对该会话有效)
set session optimizer_switch='derived_merge=on';
Explain + SQL语句即可。
测试案例的sql
CREATE TABLE t1(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t2(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t3(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t4(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
INSERT INTO t1(content) VALUES(CONCAT('t1_',FLOOR(1+RAND()*1000)));
INSERT INTO t2(content) VALUES(CONCAT('t2_',FLOOR(1+RAND()*1000)));
INSERT INTO t3(content) VALUES(CONCAT('t3_',FLOOR(1+RAND()*1000)));
INSERT INTO t4(content) VALUES(CONCAT('t4_',FLOOR(1+RAND()*1000)));
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序。
id相同,执行顺序由上至下
EXPLAIN SELECT *
FROM t1, t2, t3
WHERE
t1.id = t2.id
AND t2.id = t3.id;
此例中,执行了 where 后的第一条语句 t1.id = t2.id 通过 t1.id 关联 t2.id,而 t2.id 的结果是建立在 t2.id = t3.id 的基础上。
也可能会出现执行顺序为 t1 -> t3 -> t2 的情况。
EXPLAIN SELECT t2.*
FROM t2
WHERE id = (SELECT t1.id
FROM t1
WHERE id = (
SELECT t3.id
FROM t3));
由结果我们可以看出,如果是子查询,id的序号会递增,id值越大,优先级越高,越先被执行。
可以理解为,括号的优先级。
id递增 -> id值越大 -> 优先级越高 -> 越先被执行
EXPLAIN SELECT t2.*
FROM (SELECT t3.id
FROM t3) AS s1, t2
WHERE s1.id = t2.id;
id如果相同,可以认为是一组,组内从上往下执行,所以在所有组中,id值越大优先级越高,优先级越高的那一组,越先执行。
结果一度让我震惊,因为没有出现衍生表。我去查了一下,出现派生表并不是一个好的查询方案,派生表本质也就是临时表,如果过大也许不得不在磁盘上创建而非内存中,这样创建和访问都是要消耗IO资源的,可能在MySQL5.7中的优化器,会自动帮忙优化派生表,将派生表合并到外层查询中。
比如:
-- 优化前
SELECT * FROM (SELECT * FROM t1) AS s1;
-- 优化后
SELECT * FROM t1;
查询的类型,主要用于区别普通查询,联合查询,子查询等的复杂查询。
下面我们来逐个的看一下。
简单的SELECT查询,查询中不包含子查询或者UNION
PRIMARY
查询中若包含任何复杂的子部分,最外层查询则被标记为PRIMARY,最后执行的那个。
5.7版本的优化器直接优化了,这里用别人的图。
在FROM列表中包含的子查询被标记为DERIVERD(派生)MySQL会递归执行这些子查询,把结果放在临时表中。
在SELECT或WHERE列表中包含了子查询。
在SELECT或WHERE列表中包含了子查询,子查询基于最外层
DEPENDENT SUBQUERY 与 SUBQUERY 的区别
依赖子查询:子查询结果为多值 | 子查询:查询结果为单值。
无法被缓存的子查询,估计就是临时表太大了。
若第二个SELECT出现在UNION之后,则被标记为UNION;
若UNION包含在FROM子句的子查询中,外层的SELECT将会被标记为DERIVED
使用 union
关键词后会生成一个临时表,对于这个临时表的 select
。
对应行查询的表。
from
子句中如果有子查询的话,table 列为是 deriverN 的格式,表示这一行执行的是 id = N 行的查询。union
时,table
的数据为
的格式, M 和 N 表示参与 union
的 select
行 id。type表示这行查询的关联类型(访问类型,或查询类型),通过该值可以了解该行查询数据记录的大概范围。
他的值与我们是否优化过SQL息息相关。
常见的值依次从最优到最差分别为:system > const > eq_fef > ref > range > index > all
;
一般情况下,我们要保证效率的话,要优化我们的语句至少使其达到 range
级别,如果可能的话最好优化到 ref
; range
级别一般用于范围查找,所以换句话说,除了范围查找,我们其它查询语句最好是优化到 ref
级别。
常见的就只有这些:
下面我们就来逐一看一下总共会出现多少种情况:
NULL
system / const
show warnings
查看优化的结果),主要是查询主键(primary key
)或唯一索引(Unique Key
)对应的记录,因为不存在重复,所以最多只能查询出一条记录,所以速度比较快。system 是 const的特例,当临时表只有一条记录时为system。eq_ref
ref
range
key
列显示使用了哪个索引一般就是在你的where
语句中出现了 between、<>、in
等查询范围。index
all
Full Table Scan
,将遍历全表以找到匹配的行。index_merge
or
关键字的 sql
中。ref_or_null
null
值的情况下,查询优化器会选择用ref_or_null
连接查询。index_subquery
包含不适合在其他列中显示但是十分重要的额外信息。
Using filesort
Using temporary
MySQL
在对查询结果排序时使用临时表。order by
和分组查询 group by
。Using index
表示相应的select操作中使用了覆盖索引(Covering Index),避免了表的数据行,效率不错!
如果同时出现using where,表明索引只是用来读取数据而非利用索引执行查找。
覆盖索引
SELECT id , name FROM t_xxx WHERE age = 18;
idx_id_name_age_xxx
包含了 id,name,age
三个字段。查询时直接将建立了索引的列取出来了,而不用再去查找所在行的其他数据,效率变高了。select列表
中只取出需要的列,并且列涵括在覆盖索引中。不可以select *
,如果将所有字段一起做索引会导致索引文件过大,查询性能下降。Using where
using join buffer
impossible where
select tables optimized away
GROUP BY
子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化 COUNT(*) 操作,不必等到执行阶段在进行计算,查询执行计划生成的阶段即完成优化。运行顺序?
t2 -> t1 -> t3 -> ->
性能下降SQL慢,执行时间长,等待时间长的原因一般有如下几点:
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');
SELECT * FROM article;
-- 查询 category_id 为 1 且 comments 大于 1 的情况下,views 最多的 article_id
SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
EXPLAIN SELECT `id`,`author_id` FROM `article` WHERE `category_id` = 1 AND `comments` > 1 ORDER BY `views` DESC LIMIT 1;
我们可以看到,此时我们type是ALL(全表扫描),并且是简单单表查询,但是Using filesort了,综合下来,这已经是最坏的情况了。
我们可以通过建立索引来将查询类型往 range 方向靠;
-- 建立复合索引
ALTER TABLE `article` ADD `index` idx_article_ccv(`category_id`,`comments`,`views`);
我们可以看到还是没有消除 Using filesort 但是 range 已经属于可以接受的范围了。
为什么我们都已经建了索引了,可是还是文件排序,没有用到索引排序呢?
分析完之后我们来尝试越过需要范围查询的字段建立复合索引。
-- 删除第一次建的索引
DROP INDEX idx_article_ccv ON article;
-- 建立新索引
CREATE INDEX idx_article_cv ON article(`category_id`,`views`);
最后一次分析:
可以看出这就是我们最想要的结果,type = ref,ref = const,Extra 中的 Using filesort 也消失了。
CREATE TABLE IF NOT EXISTS `class` (
`id` INT(10) UNSIGNED 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`)
);
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 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 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)));
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 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)));
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)));
-- 双表
SELECT * FROM class LEFT JOIN book ON class.card = book.card;
-- 分析下列语句
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
直接出现了 ALL 和 Using join buffer,这都是我们不希望的,我们朝着 ref | range 以及 const 去优化。
尝试添加索引来消除 ALL。
ALTER TABLE `book` ADD INDEX idx_c(`card`);
我们可以看到此时第二行已经是 ref 和 消除了 Using join buffer 了,但是第一行还是 ALL。这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要建立索引。
我们看看有没有办法除第一行的 ALL,尝试在左表的 card 字段创建索引。
ALTER TABLE `class` ADD INDEX idx_c(`card`);
效果拔群,成功优化第一行的 ALL 为 index ,能接受。
但是一般我们只用优化到第三步那种就可以了,多的索引可能还会占空间,尽量避免多的消耗空间资源,因为 rows 指标都一样,我们不如多省点空间。
-- 先删除多余索引
DROP INDEX idx_c ON class;
DROP INDEX idx_c ON book;
-- 本案例要分析的SQL
SELECT * FROM class LEFT JOIN book ON class.card = book.card LEFT JOIN phone ON book.card = phone.card;
先分析这条语句
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card LEFT JOIN phone ON book.card = phone.card;
可以看出,这就是正常的没有优化过的语句,可以像双表那样,按照那套规则去优化。
我们给两个分别参与对应左连接的右表开始加入索引
-- 添加新索引
ALTER TABLE `book` ADD INDEX B (`card`);
ALTER TABLE `phone` ADD INDEX P (`card`);
优化到了能接受地步。
从**磁盘读取查询所需要的所有列**,按照ORDER BY列在 buffer 对他们进行排序,然后扫描排序后的列表进行输出,它的效率快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是他会使用更多的空间,因为它把每一行都保存在内存中了。
MySQL 4.1 之前是使用的双路排序,字面意思就是两次扫描磁盘,最终得到数据。读取行指针和 ORDER BY列,对他们进行排序,然后扫描已经排好序的列表,按照列表中的值重新从列表中读取对应的数据输出。
从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。
单路排序是后出的,所以总的来说性能是要优于双路排序的。
但是在 sort_buffer 中,方法B 比 方法A 要多占用很多空间,因为方法B是把所有字段都取出,,所以**有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据**,进行排序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O。
本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。
所以我们此时就需要运维和DBA出现根据业务来调整MySQL数据库配置文件中的 sort_buffer 的大小。
MySQL支持两种方式的排序,FileSort和Index,Index效率高,它指MySQL扫描索引本身完成排序。FileSort方式效率较低。
我们在开始优化order by子句之前,我们得了解我们要向着什么阶段去优化,对于 ORDER BY 排序,尽量优化到 INDEX 方式排序就最好了,避免 FILESORT。
CREATE TABLE tblA(
id int primary key not null auto_increment,
age INT,
birth TIMESTAMP NOT NULL,
name varchar(200)
);
INSERT INTO tblA(age,birth,name) VALUES(22,NOW(),'abc');
INSERT INTO tblA(age,birth,name) VALUES(23,NOW(),'bcd');
INSERT INTO tblA(age,birth,name) VALUES(24,NOW(),'def');
CREATE INDEX idx_A_ageBirth ON tblA(age,birth,name);
SELECT * FROM tblA;
第一条语句:带头大哥在,但是排序按照小弟在前,大哥在后,顺序打乱了,出现 filesort。
第二条语句:带头大哥在,并且将大哥和第二个小弟。
第三条语句:带头大哥在,并且使用已经索引出来的大哥排序。
第四条语句:带头大哥在,但是使用小弟排序,不在 where 子句中出现的索引字段在 order by 子句中出现会导致索引失效,出现 filesort。
第一条语句:索引排序失效,因为不是大哥开头。
第二条语句:索引排序失效,因为不是大哥开头。
第三条语句:索引排序成功,因为 WHERE 子句按索引字段顺序来并且范围之后才会失效,所以没有全表扫描,并且,ORDER BY 子句是以大哥开头。
第四条语句:索引排序失效,因为索引默认排好序了,然后你强行反转顺序,会导致索引失效。
先来看一个例子,GROUP BY 分组,分组之前必排序,会有临时表的产生
案例SQL
CREATE TABLE staffs (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR (24) NULL DEFAULT '' COMMENT '姓名',
age INT NOT NULL DEFAULT 0 COMMENT '年龄',
pos VARCHAR (20) NOT NULL DEFAULT '' COMMENT '职位',
add_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间'
) CHARSET utf8 COMMENT '员工记录表' ;
INSERT INTO staffs(NAME,age,pos,add_time) VALUES('z3',22,'manager',NOW());
INSERT INTO staffs(NAME,age,pos,add_time) VALUES('July',23,'dev',NOW());
INSERT INTO staffs(NAME,age,pos,add_time) VALUES('2000',23,'dev',NOW());
INSERT INTO staffs(NAME,age,pos,add_time) VALUES(null,23,'dev',NOW());
ALTER TABLE staffs ADD INDEX idx_staffs_nameAgePos(name, age, pos);
如果索引了多个列,要遵守最左前缀法则。最左前缀法则指的是**查询从索引的最左前列开始并且不跳过索引中的列。**
从最左边的第一个条件开始,如果用到索引了,那么就得从用到的这个索引的最左的字段开始,不能丢失,不能省略,就好比楼房,不能没有一楼和二楼直接盖三楼。
中间兄弟不能断,不可能盖完一楼直接盖三楼。
索引 idx_staffs_nameAgePos 建立索引时,以 name,age ,pos 的顺序建立的。全值匹配表示 按顺序匹配的查询。
所以按最左前缀法则,会使得索引失效的概率变低。
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July';
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25;
-- 全值匹配我最爱
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July'AND age = 25 AND pos = 'dev';
不在索引列上做任何操作,比如计算、函数、自动|手动类型转换,因为这样做会使索引失效进而导致转向全表扫描。
-- 错误操作运用left函数操作索引字段
EXPLAIN SELECT * FROM staffs WHERE left(NAME,4) = 'July';
存储引擎不能使用索引中范围条件右边的列
两张图对比下来,我们发现,第二种只用到了两个索引字段,第三个字段的索引失效,范围条件字段之后全失效,所以我们得尽可能的优化这种范围查询。
减少使用 SELECT *
,如题。
MySQL使用不等于(!= | <>)的适合也会无法使用索引,导致全表扫描,注意是被拿来**!=操作**的索引字段无法使用,索引全部失效。
前者不能用索引,后者可以用索引。
**like以通配符开头(‘%xxxx’)**这样的条件,MySQL索引会失效变成全表扫描。
like ‘abc%’
type 类型为 range ,算是范围,可以使用索引。
但是出现那种不可避免得使用 %xxx% 的场景怎么办?
CREATE TABLE `tbl_user` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`NAME` VARCHAR(20) DEFAULT NULL,
`age` INT(11) DEFAULT NULL,
email VARCHAR(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO tbl_user(NAME,age,email) VALUES('1aa1',21,'[email protected]');
INSERT INTO tbl_user(NAME,age,email) VALUES('2aa2',222,'[email protected]');
INSERT INTO tbl_user(NAME,age,email) VALUES('3aa3',265,'[email protected]');
INSERT INTO tbl_user(NAME,age,email) VALUES('4aa4',21,'[email protected]');
INSERT INTO tbl_user(NAME,age,email) VALUES('aa',121,'[email protected]');
-- before index
-- 没有建立索引,所以以下语句怎么来都无所谓
EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';
EXPLAIN SELECT id FROM tbl_user WHERE NAME LIKE '%aa%';
EXPLAIN SELECT NAME FROM tbl_user WHERE NAME LIKE '%aa%';
EXPLAIN SELECT age FROM tbl_user WHERE NAME LIKE '%aa%';
EXPLAIN SELECT id,NAME FROM tbl_user WHERE NAME LIKE '%aa%';
EXPLAIN SELECT id,NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';
EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';
EXPLAIN SELECT * FROM tbl_user WHERE NAME LIKE '%aa%';
EXPLAIN SELECT id,NAME,age,email FROM tbl_user WHERE NAME LIKE '%aa%';
-- create index 创建复合索引了
CREATE INDEX idx_user_nameAge ON tbl_user(NAME,age);
-- DROP INDEX idx_user_nameAge ON tbl_user
-- after index
EXPLAIN SELECT * FROM tbl_user WHERE NAME =800 AND age = 33;
总结下来要想解决这个问题,我们得建立复合索引,然后在索引字段范围内做查询,比如复合索引了 a,b,c
三个字段,你除了用到 d 字段,或者 a,b,c,d
四个字段(多了一个字段出来)以外索引都不会失效。
类型转化会导致索引失效,比如字符串类型==不加单引号==会自动类型转换为别的类型。
or 也得少用,用 or 关键字来连接时也会使索引失效。
保证被驱动表的 join 字段已经被索引(被驱动表:join 后的表为被驱动表 (需要被查询))。
MySQL自动选择小表作为驱动表。因为驱动表无论如何都会被全表扫描,所以扫描次数越少越好
left join 时,选择小表作为驱动表,大表作为被驱动表,right join 反之(但是 left join 时一定是左边是驱动表,右边是被驱动表,right join 反之)。
inner join 时,mysql会自己帮你把小结果集的表选为驱动表。
子查询尽量不要放在被驱动表,有可能使用不到索引。
尽可能减少 join 的次数。
永远记得用小表驱动大表。
优先优化嵌套循环中的语句。
无法保证被驱动表的join条件字段被索引且内存资源充足的前提下,不要太吝啬JoinBuffer的设置。
有索引的情况下:
无索引的情况
本篇内容是根据尚硅谷的MySQL高级教程整理的。
码云仓库同步笔记,可自取欢迎各位star指正:https://gitee.com/noblegasesgoo/notes
如果出错希望评论区大佬互相讨论指正,维护社区健康大家一起出一份力,不能有容忍错误知识。
—————————————————————— 爱你们的 noblegasesgoo