MySQL-索引优化实例
承接上文,我们已经就索引的底层数据结构及其查找算法作了介绍,如下
以及对SQL语句建立SQL分析的方法
然后是对索引的使用做出了一系列规范
现在,我们终于可以真正进入索引的优化专题,索引优化的重要性无需多言,在本篇博客我会给出一些索引的优化思路
关联查询优化
注意,这篇博客演示的数据表及数据SQL由于篇幅较长,我将之放在文章末尾,需要的自取
如果SQL基础不太好的话,可以看下我写的SQL专题
扫描二维码关注公众号,回复: 11037285 查看本文章
Left-Join左连接查询
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
- class代表类目 card代表书的存放位置
通过分析:两张表都没有用到索引,所以Type都来到ALL级别(全表扫描)
优化:
-
考虑在哪个表上建立索引?查询条件用到了crad,当然对card建立索引
-
对哪个表的card建立索引呢?
-
先试试在右表book建立索引
ALTER TABLE `book` ADD INDEX idx_card(`card`) ;
再进行一次查询,看看化验单
发现book表确实使用到了索引(idx_card),并且来到ref(非唯一索引查询)级别,在Extra中还发现使用了覆盖索引,rows也来到1,明显是使用了索引的结果
-
再看看,如果在左表(class)上建立索引
drop index idx_card on book; alter table class add index idx_card(card);
看看化验单
发现class表也用到了索引,但级别不及上面的高,速度不如上面的快,只来到了index级别,发现rows字段我们可以发现,两边的rows都是20,表明索引失效
-
结论:
- 在优化关联查询时,只有在被驱动表上建立索引才有效!
- left join 时,左侧的为驱动表,右侧为被驱动表!
Inner Join
EXPLAIN SELECT * FROM book inner join class on class.card=book.card;
这块不再演示了,因为过程如上,直接说下结论
- inner join 时,mysql会自己帮你把小结果集的表选为驱动表,结果集大的表选为被驱动表
- 在优化关联查询时,只有在被驱动表上建立索引才有效!
综合案例分析
t_emp:员工表 ; t_dept:部门表
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 AS '人物', tmp.name AS '掌门'
FROM t_emp e
LEFT JOIN (
SELECT d.id AS 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的右表),有可能使用不到索引
- left jon时,尽量让实体表作为被驱动表(右表)
- 能够直接多表关联的尽量直接关联,不用子查询!
子查询优化案例
取所有不为掌门人的员工,按年龄分组
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;
explain 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字段建立索引
create index idx_ceo on t_dept(ceo) ;
-
进一步优化:去除子查询改为连接查询,is not null 改为is null
explain select age as'年龄',count(*)as'人数' from t_emp e left join t_dept d on e.id=d.ceo where d.id is null group by age ;
结论:在范围判断时,尽量不要使用notin和notexists,使用leftjoinonxxxisnull代替
排序分组优化
必定产生FileSort文件内排序的情况:
-
无过滤,不索引:where,limt都相当于一种过滤条件,所以才能使用上索引!否则索引就没有了意义
- 如果没有使用过滤条件,那么在Extra会出现using filesort字段,表示mysql对该列进行了手工排序,这是会消耗性能的,因为手动排序需要消耗额外的空间(创建临时表)和时间
-
排序字段不在存在索引范围内
如
explain select * from t_emp where age=45 order by deptid,empno;
empno并不在索引范围内
-
排序(order by)顺序与索引顺序不同:可以通过desc 与 asc调整回来
Group by
原则几乎跟orderby一致,唯一区别是
groupby即使没有过滤条件用到索引,也可以直接使用索引。
附录-表SQL
Book与Class:
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 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));
dept与emp(部门与员工)
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`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO t_dept(deptName,address) VALUES('华山','华山');
INSERT INTO t_dept(deptName,address) VALUES('丐帮','洛阳');
INSERT INTO t_dept(deptName,address) VALUES('峨眉','峨眉山');
INSERT INTO t_dept(deptName,address) VALUES('武当','武当山');
INSERT INTO t_dept(deptName,address) VALUES('明教','光明顶');
INSERT INTO t_dept(deptName,address) VALUES('少林','少林寺');
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('风清扬',90,1,100001);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('岳不群',50,1,100002); INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('令狐冲',24,1,100003); INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('洪七公',70,2,100004); INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('乔峰',35,2,100005); INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('灭绝师太',70,3,100006); INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('周芷若',20,3,100007); INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张三丰',100,4,100008); INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张无忌',25,5,100009); INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('韦小宝',18,null,100010);
————————————————
版权声明:本文为CSDN博主「JunSIr_deCp」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/JunSIrhl/article/details/105623587
ALTER TABLE `t_dept` add CEO INT(11) ;
update t_dept set CEO=2 where id=1;
update t_dept set CEO=4 where id=2;
update t_dept set CEO=6 where id=3;
update t_dept set CEO=8 where id=4;
update t_dept set CEO=9 where id=5;
————————————————
版权声明:本文为CSDN博主「JunSIr_deCp」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/JunSIrhl/article/details/105623587