3、MySQL 索引优化
示例表:
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=4 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());
‐‐ 插入一些示例数据 , 初始化10W数据
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('zhuge',i),i,'dev');
set i=i+1;
end while;
end;;
delimiter ;
call insert_emp();一、联合索引第一个字段用范围不会走索引
EXPLAIN SELECT * FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';
结论:联合索引第一个字段就用范围查找不会走索引,mysql内部可能觉得第一个字段就用范围,结果集应该很大,回表效率不高,还不如就全表扫描。
二、强制走索引
EXPLAIN SELECT * FROM employees force index(idx_name_age_position) WHERE name > 'LiLei' AND age = 22 AND position ='manager';
结论:虽然使用了强制走索引让联合索引第一个字段范围查找也走索引,扫描的行rows看上去也少了点,但是最终查找效率不一定比全表扫描高,因为回表效率不高。
三、覆盖索引优化
EXPLAIN SELECT name,age,position FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';
四、in和or在表数据量比较大的情况会走索引,在表记录不多的情况下会选择全表扫描
EXPLAIN SELECT * FROM employees WHERE name in ('LiLei','HanMeimei','Lucy') AND age = 22 AND position='manager';EXPLAIN SELECT * FROM employees WHERE (name = 'LiLei' or name = 'HanMeimei') AND age = 22 AND position='manager';
4.1、做一个小实验,将employees 表复制一张employees_copy的表,里面保留两三条记录
EXPLAIN SELECT * FROM employees_copy WHERE name in ('LiLei','HanMeimei','Lucy') AND age = 22 AND position ='manager';EXPLAIN SELECT * FROM employees_copy WHERE (name = 'LiLei' or name = 'HanMeimei') AND age = 22 AND position ='manager';
同样的SQL,数据量少了,直接全表扫描了。
五、like KK% 一般情况都会走索引
EXPLAIN SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees_copy WHERE name like 'LiLei%' AND age = 22 AND position ='manager';
5.1、什么是索引下推?
5.2、为什么范围查找Mysql没有用索引下推优化?
六、常见sql深入优化
6.1、order by/group by优化举例
6.1.1
6.1.2
6.1.3
6.1.4
6.1.5
6.1.6
6.1.7
6.1.8
6.2、优化总结
MySQL支持两种方式的排序filesort和index,Using index是指MySQL扫描索引本身完成排序。index效率高,filesort效率低。
order by满足两种情况会使用Using index。
order by语句使用索引最左前列。
使用where子句与order by子句条件列组合满足索引最左前列。
尽量在索引列上完成排序,遵循索引建立(索引创建的顺序)时的最左前缀法则。
如果order by的条件不在索引列上,就会产生Using filesort。
能用覆盖索引尽量用覆盖索引。
group by与order by很类似,其实质是先排序后分组,遵照索引创建顺序的最左前缀法则。对于group by的优化如果不需要排序的可以加上order by null禁止排序。注意,where高于having,能写在where中的限定条件就不要去having限定了。
6.3、Using filesort文件排序方式
6.3.1、单路排序
6.3.2、双路排序(又叫回表排序模式)
七、索引设计原则
7.1、代码先行,索引后上
7.2、联合索引尽量覆盖条件
7.3、不要在小基数字段上建立索引
7.4、长字符串我们可以采用前缀索引
7.5、where与order by冲突时优先where
7.6、基于慢sql查询做优化
八、分页查询优化
select * from employees limit 10000,10;
8.1、根据自增且连续的主键排序的分页查询
select * from employees limit 90000,5; -- 优化为 select * from employees where id > 90000 limit 5;
对比一下执行计划
EXPLAIN select * from employees limit 90000,5;
EXPLAIN select * from employees where id > 90000 limit 5;
8.2、根据非主键字段排序的分页查询
select * from employees ORDER BY name limit 90000,5;
查看执行计划
select * from employees e inner join (select id from employees order by name limit 90000,5) edon e.id = ed.id;
九、Join关联查询优化
‐‐ 示例表: 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(); 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();
9.1、嵌套循环连接 Nested-Loop Join(NLJ) 算法
一次一行循环地从第一张表(称为驱动表)中读取行,在这行数据中取到关联字段,根据关联字段在另一张表(被驱动表)里取出满足条件的行,然后取出两张表的结果合集。
EXPLAIN select * from t1 inner join t2 on t1.a= t2.a;
驱动表是 t2,被驱动表是 t1。先执行的就是驱动表(执行计划结果的id如果一样则按从上到下顺序执行sql);优化器一般会优先选择小表(数据量少)做驱动表。所以使用 inner join 时,排在前面的表并不一定就是驱动表。
当使用left join时,左表是驱动表,右表是被驱动表,当使用right join时,右表时驱动表,左表是被驱动表,当使用join时,mysql会选择数据量比较小的表作为驱动表,大表作为被驱动表。
一般 join 语句中,如果执行计划 Extra 中未出现 Using join buffer 则表示使用的 join 算法是 NLJ。
整个过程会读取 t2 表的所有数据(扫描100行),然后遍历这每行数据中字段 a 的值。
9.2、基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法
EXPLAIN select * from t1 inner join t2 on t1.b= t2.b;
join_buffer内存区域
9.3、总结
关联字段加索引,让mysql做join操作时尽量选择NLJ算法
小表驱动大表,写多表连接sql时如果明确知道哪张表是小表可以用straight_join写法固定连接驱动方式,省去mysql优化器自己判断的时间。
straight_join
-- 代表指定mysql选着 t2 表作为驱动表。 select * from t2 straight_join t1 on t2.a = t1.a;
对于小表的定义
十、in和exsits优化
10.1、in
select * from A where id in (select id from B);
10.2、exists
select * from A where exists (select 1 from B where B.id = A.id);
十一、count(*)查询优化
‐‐ 临时关闭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只有根据某个字段count不会统计字段为null值的数据行
字段有索引
字段没索引
11.1、查询mysql自己维护的总行数
11.2、show table status
版权声明
非特殊说明,本文由Zender原创或收集发布,欢迎转载。
ZENDER






























发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。