Java学习笔记开发技巧Java 杂谈

案例加源码:万字长文带你彻底搞懂MySQL的索引优化

2021-01-18  本文已影响0人  并发量就是我的发量

一、索引优化

1,单表索引优化

建表

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 VARCHAR(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');

View Code

查询案例

案例加源码:万字长文带你彻底搞懂MySQL的索引优化

优化一:

案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化

优化二:

案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化

2,两表索引优化

建表

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 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)));

View Code

查询案例

案例加源码:万字长文带你彻底搞懂MySQL的索引优化

添加右表索引

案例加源码:万字长文带你彻底搞懂MySQL的索引优化

添加左表索引

案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化

3,三表索引优化

建表

CREATE TABLE IF NOT EXISTS phone(
    phoneid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    card INT(10) UNSIGNED NOT NULL,
    PRIMARY KEY(phoneid)
)ENGINE=INNODB;

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)));

View Code

查询案例:

案例加源码:万字长文带你彻底搞懂MySQL的索引优化

创建索引:

案例加源码:万字长文带你彻底搞懂MySQL的索引优化

4,总结

将 left join 看作是两层嵌套 for 循环

  1. 尽可能 减少Join语句中的NestedLoop的循环总次数
  2. 永远用 小结果集驱动大的结果集 (在 大结果集中建立索引 ,在小结果集中遍历全表);
  3. 优先优化 NestedLoop的 内层循环
  4. 保证Join语句中被驱动表上 Join条件字段已经被索引
  5. 当无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,不要太吝惜JoinBuffer的设置;

二、索引失效

创建表:

CREATE TABLE staffs(
    id INT PRIMARY KEY AUTO_INCREMENT,
    `name` VARCHAR(24)NOT 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());

View Code

1,索引失效准则

  1. 全值匹配我最爱
  2. 最佳左前缀法则 :如果索引了多例,要遵守最左前缀法则。指的是 查询从索引的最左前列开始并且不跳过索引中的列
  3. 不在索引列上做任何操作 (计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
  4. 存储引擎 不能使用索引范围条件右边的列
  5. 尽量使用覆盖索引(只访问索引的查询( 索引列和查询列一致 )), **减少 select ***
  6. mysql在使用 不等于 (!=或者<>)的时候无法使用索引会导致 全表扫描 (并不绝对,需考虑 成本 问题,例如id!=''时还是会用到索引的)
  7. **is null , is not null **也无法使用索引(并不绝对,需考虑 成本 问题)
  8. like以通配符开头(’%abc…’)mysql索引失效会变成全表扫描操作(如果是'a%'则使用range索引)
  9. 字符串不加引号索引失效
  10. 少用or ,用它连接时会索引失效

2,索引失效案例

a)创建复合索引

#创建复合索引
ALTER TABLE staffs ADD INDEX index_staffs_nameAgePos(`name`,`age`,`pos`);
#查看索引
SHOW INDEX FROM staffs;

案例加源码:万字长文带你彻底搞懂MySQL的索引优化

b)where条件匹配

案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化

注意在in、!=、is null和is not null,到底什么时候索引,什么时候采用全表扫描呢? 详情描述请点击查看

成本。对于使用二级索引(innodb)进行查询来说,成本组成主要有两个方面:
    读取二级索引记录的成本
    将二级索引记录执行回表操作,也就是到聚簇索引中找到完整的用户记录的操作所付出的成本。
很显然,要扫描的二级索引记录条数越多,那么需要执行的回表操作的次数也就越多,达到了某个比例时,使用二级索引执行查询的成本也就超过了全表扫描的成本
(举一个极端的例子,比方说要扫描的全部的二级索引记录,那就要对每条记录执行一遍回表操作,自然不如直接扫描聚簇索引来的快)。
所以MySQL优化器在真正执行查询之前,对于每个可能使用到的索引来说,都会预先计算一下需要扫描的二级索引记录的数量。所以对于以上三种查询条件是否会命中索引就取决于二级索引查询的成本与全局查询成本的高低。

三、索引案例

1,建表

create table test03(
    id int primary key not null auto_increment,
    c1 char(10),
    c2 char(10),
    c3 char(10),
    c4 char(10),
    c5 char(10)
);

insert into test03(c1,c2,c3,c4,c5) values ('a1','a2','a3','a4','a5');
insert into test03(c1,c2,c3,c4,c5) values ('b1','b2','b3','b4','b5');
insert into test03(c1,c2,c3,c4,c5) values ('c1','c2','c3','c4','c5');
insert into test03(c1,c2,c3,c4,c5) values ('d1','d2','d3','d4','d5');
insert into test03(c1,c2,c3,c4,c5) values ('e1','e2','e3','e4','e5');
#创建复合索引
create index idx_test03_c1234 on test03(c1,c2,c3,c4);

案例加源码:万字长文带你彻底搞懂MySQL的索引优化

2,案例

#只有where
EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c2='a2' AND c3='a3' AND c4='a4';
EXPLAIN SELECT * FROM test03 WHERE c4='a4' AND c3='a3' AND c2='a2' AND c1='a1';
EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c2='a2' AND c3>'a3' AND c4='a4';
EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c2='a2' AND c4>'a4' AND c3='a3';
#where条件与order by 结合
EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c2='a2' AND c4='a4' ORDER BY c3;
EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c2='a2' ORDER BY c3;
EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c2='a2' ORDER BY c4;
EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c5='a5' ORDER BY c2, c3;
EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c5='a5' ORDER BY c3, c2;
EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c2='a2' ORDER BY c2, c3;
EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c2='a2' AND c5='a5' ORDER BY c2, c3;
EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c2='a2' AND c5='a5' ORDER BY c3, c2;
#where与group by结合
EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c4='a4' GROUP BY c2, c3;
EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c4='a4' GROUP BY c3, c2;

3,案例分析

a)where查询

案例加源码:万字长文带你彻底搞懂MySQL的索引优化

b)where与order by结合

案例加源码:万字长文带你彻底搞懂MySQL的索引优化 案例加源码:万字长文带你彻底搞懂MySQL的索引优化

c)where与group by

案例加源码:万字长文带你彻底搞懂MySQL的索引优化

结论: group by 基本上都需要进行排序 (使用情况基本与order by相同,索引顺序均会出现在where之后), 但凡使用不当,会有临时表产生

4,索引失效总结

a)建议

  1. 对于单键索引,尽量选择针对当前query过滤性更好的索引
  2. 在选择 组合索引 的时候,当前query中 过滤性最好的字段 在索引字段顺序中,位置 越靠左越好
  3. 在选择 组合索引 的时候,尽量选择可以能包含当前query查询条件中 where子句更多字段的索引
  4. 尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的

b)案例

案例加源码:万字长文带你彻底搞懂MySQL的索引优化

如果本文对你有帮助,可以点赞关注支持一下

上一篇 下一篇

猜你喜欢

热点阅读