查询优化

1.单表使用索引常见的索引失效
2.关联查询优化
3.子查询优化

1.单表使用索引常见的索引失效

1. 全值匹配我最爱

1.1 有以下 SQL

EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd'

1.2 建立索引

CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME);

在这里插入图片描述
结论:全职匹配我最爱指的是,查询的字段按照顺序在索引中都可以匹配到!
在这里插入图片描述
2. 最佳左前缀法则
在这里插入图片描述

3. 不要在索引列上做任何计算

不在索引列上做任何操作(计算、函数、(自动 or 手动)类型转换),会导致索引失效而转向全表扫描。
在这里插入图片描述
3.2 在查询列上做了转换

create index idx_name on emp(name);
explain select sql_no_cache * from emp where name='30000';
explain select sql_no_cache * from emp where name=30000;

字符串不加单引号,则会在 name 列上做一次转换!
在这里插入图片描述
4. 索引列上不能有范围查询

存储引擎不能使用索引中范围条件右边的列,即范围之后全失效

explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=5 AND emp.name = 'abcd';
explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid<=5 AND emp.name = 'abcd';

在这里插入图片描述

5. 尽量使用覆盖索引

尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *

explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptId=4 and name='XamgXt';
explain SELECT SQL_NO_CACHE age,deptId,name FROM emp WHERE emp.age=30 and deptId=4 and name='XamgXt';

在这里插入图片描述
6. 使用不等于(!= 或者<>)的时候

mysql 在使用不等于(!= 或者<>)时,会无法使用索引会导致全表扫描。
在这里插入图片描述
7. 字段的 is not null 和 和 is null
在这里插入图片描述
当字段允许为 Null 的条件下:
在这里插入图片描述

8. like 的前后模糊匹配

LIKE 百分写最右
在这里插入图片描述
问题:解决like '%字符串%'时索引不被使用的方法??

  • 用覆盖索引

9. 减少使用 or
在这里插入图片描述
使用 union all 或者 union 来替代:
在这里插入图片描述
口诀:
全职匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
LIKE 百分写最右,覆盖索引不写 *;
不等空值还有 OR,索引影响要注意;
VAR 引号不可丢,SQL 优化有诀窍。

一般性建议:

  • 对于单键索引,尽量选择针对当前query过滤性更好的索引
  • 在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。(避免索引过滤性好的索引失效)
  • 在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引
  • 尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的

2.关联查询优化

  1. 建表语句
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`)
);

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

2.1 left join

EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;

在这里插入图片描述
如何优化?在哪个表上建立索引?

ALTER TABLE `book` ADD INDEX idx_card( `card`);

在这里插入图片描述
删除 book 表的索引:drop index idx_card on book;
在 class 表上建立索引:alter table class add index idx_card(card);

在这里插入图片描述
结论:
①在优化关联查询时,只有在被驱动表上建立索引才有效
②left join 时,左侧的为驱动表,右侧为被驱动表!要小表(驱动表)驱动大表(被驱动表)。

小表驱动大表的思想:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 inner join

EXPLAIN SELECT * FROM book inner join class on class.card=book.card;

在这里插入图片描述
②两个查询字段调换顺序,发现结果也是一样的!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2.3 四个关联查询案例分析

CREATE TABLE `t_dept` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`deptName` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,

PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `t_emp` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`deptId` INT(11) DEFAULT NULL,
empno int not null,

PRIMARY KEY (`id`),
KEY `idx_dept_id` (`deptId`)
#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

#增加掌门字段
ALTER TABLE `t_dept` add CEO INT(11) ;
EXPLAIN SELECT ed.name '人物',c.name '掌门' FROM
(SELECT e.name,d.ceo from t_emp e LEFT JOIN t_dept d on e.deptid=d.id) ed
LEFT JOIN t_emp c on ed.ceo= c.id;

在这里插入图片描述

EXPLAIN SELECT e.name '人物',tmp.name '掌门'
FROM t_emp e LEFT JOIN (SELECT d.id did,e.name FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id)tmp
ON e.deptId=tmp.did;

在这里插入图片描述
上述两个案例,第一个查询效率较高,且有优化的余地。第二个案例中,子查询作为被驱动表,由于子查询是虚表,无法建立索引,因此不能优化。

结论:
子查询尽量不要放在被驱动表,有可能使用不到索引;
left join时,尽量让实体表作为被驱动表。

EXPLAIN SELECT e1.name '人物',e2.name '掌门' FROM t_emp e1
LEFT JOIN t_dept d on e1.deptid = d.id
LEFT JOIN t_emp e2 on d.ceo = e2.id ;

在这里插入图片描述

Explain SELECT e2.name '人物',
(SELECT e1.name FROM t_emp e1 where e1.id= d.ceo) '掌门'
from t_emp e2 LEFT JOIN t_dept d on e2.deptid=d.id;

在这里插入图片描述
结论:能够直接多表关联的尽量直接关联,不用子查询!

关联查询优化建议:
1、保证被驱动表的join字段已经被索引

  • 被驱动表 join 后的表为被驱动表 (需要被查询)

2、left join 时,选择小表作为驱动表,大表作为被驱动表。

  • left join 时一定是左边是驱动表,右边是被驱动表

3、inner join 时,mysql会自己帮你把小结果集的表选为驱动表。

  • mysql 自动选择。小表作为驱动表。因为 驱动表无论如何都会被全表扫描?。所以扫描次数越少越好

4、子查询尽量不要放在被驱动表,有可能使用不到索引。

select a.name ,bc.name from t_emp a left join
 (select b.id , c.name from t_dept b inner join t_emp c on b.ceo = c.id) 
 bc on bc.id = a.deptid.

上段查询中用到了子查询,必然 bc 表没有索引。肯定会进行全表扫描上段查询 可以直接使用 两个 left join 优化

select a.name, c.name from t_emp a
    left outer join t_dept b on a.deptid = b.id
    left outer join t_emp c on b.ceo=c.id

所有条件都可以使用到索引

若必须用到子查询,可将子查询设置为驱动表,因为驱动表的type 肯定是 all,而子查询返回的结果表没有索引,必定也是all

3.子查询优化

表关联是可以利用两个表的索引的,如果是用子查询,至少第二次查询是没有办法使用索引的。

SQL中优化中有一条:将子查询转换为连接查询
原因:子查询会多次运算查询,而连接查询利于优化器优化,而且可以使用多线程查询各个连接子句。

子查询
在某些情况下,当进行查询的时候,需要的条件是另外一个select语句的结果,这个时候,就要用到子查询
为了给主查询提供数据而首先执行的查询被叫做子查询
用于子查询的关键字主要包括 in、not in、=、<> 等

1、如果只需要返回 一个表的数据,建议用exists 或者in。
2、如果要返回2个或多个表的数据,那么就用关联

1. 案例

取所有不为掌门人的员工,按年龄分组!

select age as '年龄', count(*) as '人数' from t_emp where id not in
(select ceo from t_dept where ceo is not null) group by age;

在这里插入图片描述
如何优化?
①解决 dept 表的全表扫描,建立 ceo 字段的索引:
在这里插入图片描述
此时,再次查询:
在这里插入图片描述
②进一步优化,替换 not in。
上述 SQL 可以替换为:

select age as '年龄',count(*) as '人数' from emp e left join dept d on e.id=d.ceo where d.id is null group by age;

在这里插入图片描述
结论: 在范围判断时,尽量不要使用 not in 和 not exists,使用 left join on xxx is null 代替。

用in 还是 exists?

有索引的情况下 用 inner join 是最好的 其次是 in ,exists最糟糕

无索引的情况下用 小表驱动大表 因为join 方式需要distinct,没有索引distinct消耗性能较大
所以 exists 性能最佳 in其次 join性能最差?

无索引的情况下大表驱动小表 in 和 exists 的性能应该是接近的 都比较糟糕 exists稍微好一点 超不过5% 但是inner join 优于使用了 join buffer 所以快很多
如果left join 则最慢

常见的 Join 查询图
在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值