🌈🌈🌈🌈🌈🌈🌈🌈
欢迎关注公众号(通过文章导读关注:【11来了】),及时收到AI 前沿项目工具及新技术
的推送
发送资料
可领取深入理解 Redis 系列文章结合电商场景讲解 Redis 使用场景
、中间件系列笔记
和编程高频电子书
!文章导读地址:点击查看文章导读!
感谢你的关注!
🍁🍁🍁🍁🍁🍁🍁🍁
这里主要说一下 MySQL 中如何对 SQL 进行优化,其实主要还是根据索引来进行优化的,如果好好了解下边的 SQL 优化,可以对 MySQL 的理解更加深入
接下来的 SQL 优化,以下边这个 employees 表为例进行优化:
CREATE TABLE `employees` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
`age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
`position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',
`hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
PRIMARY KEY (`id`),
KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='员工记录表';
INSERT INTO employees(name,age,position,hire_time) VALUES('LiLei',22,'manager',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('HanMeimei', 23,'dev',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());
‐‐ 插入一些示例数据
drop procedure if exists insert_emp;
delimiter ;;
create procedure insert_emp()
begin
declare i int;
set i=1;
while(i<=100000)do
insert into employees(name,age,position) values(CONCAT('zqy',i),i,'dev');
set i=i+1;
end while;
end;;
delimiter ;
call insert_emp();
下边是 8 种使用 order by 的情况,我们通过分析以下案例,可以判断出如何使用 order by 和 where 进行配合可以走using index condition(索引排序)
而不是 using filesort(文件排序)
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' and position = 'dev' order by age;
分析:
查询用到了 name 索引,从 key_len=74 也能看出,age 索引列用在排序过程中,因此 Extra 字段里没有 using filesort
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' order by position;
分析:
从 explain 执行结果来看,key_len = 74,查询使用了 name 索引,由于用了 position 进行排序,跳过了 age,出现了 Using filesort
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' order by age, position;
分析:
查找只用到索引name,age和position用于排序,与联合索引顺序一致,因此无 using filesort。
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' order by position, age;
分析:
因为索引的创建顺序为 name,age,position,但是排序的时候 age 和 position 颠倒位置了,和索引创建顺序不一致,因此出现了 using filesort
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' and age = 18 order by position, age;
分析:
与 case 4 相比,Extra 中并未出现 using filesort,并且查询使用索引 name,age,排序先根据 position 索引排序,索引使用顺序与联合索引顺序一致,因此使用了索引排序
EXPLAIN SELECT * FROM employees WHERE name = 'zqy' order by age asc, position desc;
分析:
虽然排序字段列与联合索引顺序一样,但是这里的 position desc 变成了降序排序,导致与联合索引的排序方式不同
,因此产生了 using filesort
EXPLAIN SELECT * FROM employees WHERE name in ('LiLei', 'zqy') order by age, position;
分析:
先使用索引 name 拿到 LiLei,zqy 的数据,之后需要根据 age、position 排序,但是根据 name 所拿到的数据对于 age、position 两个字段来说是无序的,所以需要使用到 filesort。
为什么根据 name in 拿到的数据对于 age、position 来说是无序的:
对于下图来说,如果取出 name in (Bill, LiLei) 的数据,那么对于 age、position 字段显然不是有序的,因此肯定无法使用索引扫描排序
EXPLAIN SELECT * FROM employees WHERE name > 'a' order by name;
分析:
对于上边这条 sql 来说,是 select * 因此 mysql 判断不走索引,直接全表扫描更快,因此出现了 using filesort
EXPLAIN SELECT name FROM employees WHERE name > 'a' order by name;
分析:
因此可以使用覆盖索引
来优化,只通过索引查询就可以查出我们需要的数据,不需要回表,通过覆盖索引优化,因此没有出现 using filesort
我们实现分页功能可能会用以下 sql:
select * from employees limit 10000, 10;
该 sql 表示从 employees 表的第 10001 行开始的 10 行数据,虽然只查询了 10 条数据,但是会先去读取 10010 条记录,再抛弃前 10000 条数据,因此如果查询的数据比较靠后,效率非常低
该优化必须保证主键是自增的,并且主键连续,中间没有断层。
未优化 sql
select * from employees limit 9000, 5;
结果:
执行计划:
因为 id 是连续且自增的,所以可以直接通过 id 判断拿到 id 比 9000 大的 5 条数据,效率更高:
优化后 sql
select * from employees where id > 9000 limit 5;
结果
执行计划:
总结
未优化 sql
select * from employees order by name limit 9000, 5;
> OK
> 时间: 0.066s
explain select * from employees order by name limit 9000, 5;
根据执行计划
得,使用了全表扫描(type=ALL),并且 Extra 列为 using filesort,原因是联合索引为(name,age,position),但是使用了 select * 中有的列并不在联合索引中,如果使用索引还需要回表,因此 mysql 直接进行全表扫描
优化 sql
优化的点在于:
让在排序时返回的字段尽量为覆盖索引,这样就会走索引并且还会使用索引排序
先让排序和分页操作查出主键,再根据主键查到对应记录
select * from employees e inner join (select id from employees order by name limit 9000, 5) ed on e.id = ed.id;
> OK
> 时间: 0.032s
explain select * from employees e inner join (select id from employees order by name limit 9000, 5) ed on e.id = ed.id;
根据执行计划
得,优化后查询走了索引,并且排序使用了索引排序
总结
CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
create table t2 like t1;
‐‐ 插入一些示例数据
‐‐ 往t1表插入1万行记录
drop procedure if exists insert_t1;
delimiter ;;
create procedure insert_t1()
begin
declare i int;
set i=1;
while(i<=10000)do
insert into t1(a,b) values(i,i);
set i=i+1;
end while;
end;;
delimiter ;
call insert_t1();
‐‐ 往t2表插入100行记录
drop procedure if exists insert_t2;
delimiter ;;
create procedure insert_t2()
begin
declare i int;
set i=1;
while(i<=100)do
insert into t2(a,b) values(i,i);
set i=i+1;
end while;
end;;
delimiter ;
call insert_t2();
原则:小表驱动大表
in:
当 B 表的数据集小于 A 表的数据集时,使用 in
select * from A where id in (select id from B)
exists:
当 A 表的数据集小于 B 表的数据集时,使用 exists
将主查询 A 的数据放到子查询 B 中做条件验证,根据验证结果(true 或 false)来决定主查询的数据是否保留
select * from A where exists (select 1 from B where B.id = A.id)
总结
‐‐ 临时关闭mysql查询缓存,为了查看sql多次执行的真实时间
set global query_cache_size=0;
set global query_cache_type=0;
EXPLAIN select count(1) from employees;
EXPLAIN select count(id) from employees;
EXPLAIN select count(name) from employees;
EXPLAIN select count(*) from employees;
分析:
4 条 sql 语句的执行计划一样,说明这 4 个 sql 的执行效率差不多
总结
当字段有索引,执行效率:count(*) ≈ count(1) > count(字段) > count(主键id)
如果字段有索引,走二级索引,二级索引存储的数据比主键索引少,所以 count(字段)
比 count(主键id)
效率更高
当字段无索引,执行效率:count(*) ≈ count(1) > count(主键id) > count(字段)
count(1)
和 count(*)
比较
count(1)
不需要取出字段统计,使用常量 1 做统计,count(字段)
还需要取出字段,所以理论上 count(1)
比 count(字段)
快
count(*)
是例外,mysql 并不会把全部字段取出来,会忽略所有的列直接,效率很高,所以不需要用
count(字段)
或 count(常量)
来替代 count(*)
为什么对于 count(id)
,mysql最终选择辅助索引而不是主键聚集索引?因为二级索引相对主键索引存储数据更少,检索
性能应该更高,mysql内部做了点优化(在5.7版本才优化)。