MySQL-索引优化实例

MySQL-索引优化实例

承接上文,我们已经就索引的底层数据结构及其查找算法作了介绍,如下

你真的懂数据库的索引吗?MySql索引详谈

以及对SQL语句建立SQL分析的方法

Mysql-使用Explain分析你的SQL

然后是对索引的使用做出了一系列规范

索引是如何失效的?详谈MySQL索引使用规范

现在,我们终于可以真正进入索引的优化专题,索引优化的重要性无需多言,在本篇博客我会给出一些索引的优化思路

关联查询优化

注意,这篇博客演示的数据表及数据SQL由于篇幅较长,我将之放在文章末尾,需要的自取

如果SQL基础不太好的话,可以看下我写的SQL专题

扫描二维码关注公众号,回复: 11037285 查看本文章

SQL-增删改查一锅端

你真的懂SQL的连接查询吗?Mysql Join连接查询一锅端

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
发布了182 篇原创文章 · 获赞 121 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/JunSIrhl/article/details/105649184