所用sql
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();
嵌套循环连接 Nested-Loop Join(NLJ) 算法
每一次扫描一行,循环地从第一张表(称为驱动表)中读取行,在这行数据中取到关联字段,根据关联字段在另一张表(被驱动 表)里取出满足条件的行,然后取出两张表的结果合集。看下面的例子
Nested-Loop Join 算法
在关联查询中先执行的表是驱动表,后执行的表是被驱动表。所以t2是驱动表,t1是被驱动表。如果extra中没有出现Using join buffer 那么就是使用了NLJ算法。
上面sql的大致执行流程:
整个过程中,从t2获取所有的数据总共要扫描100行,每次扫描到一行数据都要找到关联字段再去t1中找数据,由于关联字段有索引,因此从t1中获取数据不需要一行一行的扫描,可以算作只扫描一次。那么t1表也扫描100行。总共扫描了200行。这是关联字段有索引的情况下。
如果关联字段没有索引,那会是怎么个情况。首先依然是从t2表中扫描100行数据,然后找到关联字段,在去匹配t1表。由于没有索引,每次匹配的时候都要全盘扫描。t1表有1万行数据,就是每次需要扫描1万行,要扫描100次,一共需要扫描100*10000
100万行。也就是说在没有索引的情况这种扫描算法效率极差。这时候就要有另一种算法Block Nested-Loop Join
基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法
把驱动表的数据读入到 join_buffer 中,然后扫描被驱动表,把被驱动表每一行取出来跟 join_buffer 中的数据做对比。看下面的案例
从上面explain分析结果看出,extra出现Using join buffer说明使用了BNL算法。
上面sql的大致流程如下:
整个过程要从t2表中获取所有数据,需要扫描100次,然后放入join_buffer中,从t1表一行一行的扫描数据和join_buffer中的数据尽心比较,需要扫描1万次,由于join_buffer中的数据是无序,每一次匹配数据都要匹配100次,总共要匹配10000*100
100万次。总的来说,这个案例中需要扫描表10100次,然后100万次数据比较。
再来回顾一下,在Nested-Loop Join算法中也说了,如果没有索引的情况,如果按照NLJ算法,要扫描100万次磁盘。如果按照BNL算法,需要扫描10100次磁盘和100万次的join_buffer比较。虽然这两种算法都出现了100万,但是NLJ算法是扫描磁盘100万次,BNL是内存比较100万次,孰轻孰重这个就不用说了吧。但是就算使用了BNL算法仍然要扫描磁盘10100次。也就是说如果数据量大话,查询效率依然不高。
总结一下:
join优化其实没有太好的优化方案,说白了就是explain分析之后对每一条分析结果进行优化。很多大厂一般也不建议使用join来查询。
原则:小表驱动大表,即小的数据集驱动大的数据集
in:当B表的数据集小于A表的数据集时,in优于exists select * from A where id in (select id from B)
#等价于: for(select id from B){
select * from A where A.id = B.id
}
exists:当A表的数据集小于B表的数据集时,exists优于in 将主查询A的数据,放到子查询B中做条件验证,根据验证结果(true或false)来决定主查询的数据是否保留
select * from A where exists (select 1 from B where B.id = A.id)
#等价于: for(select * from A){
select * from B where B.id = A.id
}
employees表是之前优化三四中用到的表
#临时关闭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;
四个sql的执行计划一样,说明这四个sql执行效率应该差不多
字段有索引:count(*)≈count(1)>count(字段)>count(主键 id)
,字段有索引,count(字段)统计走二级索引,二级索引存储数据比主键索引少,所以count(字段)>count(主键 id)
字段无索引:count(*)≈count(1)>count(主键 id)>count(字段)
,字段没有索引,count(字段)统计走不了索引,count(主键 id)还可以走主键索引,所以count(主键 id)>count(字段)
count(1)跟count(字段)执行过程类似,不过count(1)不需要取出字段统计,就用常量1做统计,count(字段)还需要取出 字段,所以理论上count(1)比count(字段)会快一点。
count(*)
是例外,mysql并不会把全部字段取出来,而是专门做了优化,不取值,按行累加,效率很高,所以不需要用 count(列名)或count(常量)来替代 count(*)。
优化方案
show table status
如果只需要知道表总行数的估计值
可以用如下sql查询,性能很高1、代码先行,索引后上 不知大家一般是怎么给数据表建立索引的,是建完表马上就建立索引吗? 这其实是不对的,一般应该等到主体业务功能开发完毕,把涉及到该表相关sql都要拿出来分析之后再建立 索引。
2、联合索引尽量覆盖条件 比如可以设计一个或者两三个联合索引(尽量少建单值索引),让每一个联合索引都尽量去包含sql语句里的 where、order by、group by的字段,还要确保这些联合索引的字段顺序尽量满足sql查询的最左前缀原 则。
3、不要在小基数字段上建立索引 索引基数是指这个字段在表里总共有多少个不同的值,比如一张表总共100万行记录,其中有个性别字段, 其值不是男就是女,那么该字段的基数就是2。 如果对这种小基数字段建立索引的话,还不如全表扫描了,因为你的索引树里就包含男和女两种值,根本没法进行快速的二分查找,那用索引就没有太大的意义了。 一般建立索引,尽量使用那些基数比较大的字段,就是值比较多的字段,那么才能发挥出B+树快速二分查找的优势来。
4、长字符串我们可以采用前缀索引 尽量对字段类型较小的列设计索引,比如说什么tinyint之类的,因为字段类型较小的话,占用磁盘空间也会 比较小,此时你在搜索的时候性能也会比较好一点。 当然,这个所谓的字段类型小一点的列,也不是绝对的,很多时候你就是要针对varchar(255)这种字段建立 索引,哪怕多占用一些磁盘空间也是有必要的。 对于这种varchar(255)的大字段可能会比较占用磁盘空间,可以稍微优化下,比如针对这个字段的前20个 字符建立索引,就是说,对这个字段里的每个值的前20个字符放在索引树里,类似于 KEY index(name(20),age,position)。 此时你在where条件里搜索的时候,如果是根据name字段来搜索,那么此时就会先到索引树里根据name 字段的前20个字符去搜索,定位到之后前20个字符的前缀匹配的部分数据之后,再回到聚簇索引提取出来 完整的name字段值进行比对。 但是假如你要是order by name,那么此时你的name因为在索引树里仅仅包含了前20个字符,所以这个排序是没法用上索引的, group by也是同理。所以这里大家要对前缀索引有一个了解。
5、where与order by冲突时优先where 在where和order by出现索引设计冲突时,到底是针对where去设计索引,还是针对order by设计索引?到 底是让where去用上索引,还是让order by用上索引?
一般这种时候往往都是让where条件去使用索引来快速筛选出来一部分指定的数据,接着再进行排序。 因为大多数情况基于索引进行where筛选往往可以最快速度筛选出你要的少部分数据,然后做排序的成本可 能会小很多。
6、基于慢sql查询做优化 可以根据监控后台的一些慢sql,针对这些慢sql查询做特定的索引优化。 关于慢sql查询不清楚的可以参考这篇文章:https://blog.csdn.net/qq_40884473/article/details/89455740
1、了解b+tree的数据结构。所用的索引优化都建立在b+tree的基础上
2、 表的数据量尽量不要太大,太大的情况可以考虑分库分表。怎么才算大可以参考阿里巴巴的开发手册。数据量大于500万或者容量大于2G。
3、根据上面的索引设计原则为sql设计合适的索引。尽量以两到三个联合索引,覆盖80%的sql查询。
4、如果查询的数据量过大,一般情况下是不会走辅助索引(二级索引)的。因为辅助索引查出来的数据都要进行回表。因此尽量使用到覆盖索引,直接能从辅助索引中获取到查询的数据。
5、in和or在不同的情况下,有可能走索引,有可能不走。需要在实际环境中通过explain进行分析。
6、like查询如果%在开头会导致索引失效,在中间和后面就不会。如果非得要在前面,可以建立覆盖索引。如果建不了,可以使用搜索引擎。
7、Order by与Group by优化 尽量避免文件排序,尽量使用到索引排序。如果Order by与Group by优化的时候和where产生冲突,那么优先解决where,因为where筛选的数据量小了,排序压力自然就小了。
8、分页优化。具体看分页优化那部分吧。
9、count(*)
查询优化,mysql内部专门做了优化,不会取出全部的值,而是按行累加,效率很高,所以不需要用 count(列名)或count(常量)来替代 count(*)。
10 join优化。在关联字段上用到索引,尽量让mysql采用NLJ算法。使用left join 和right join的时候基于小表驱动大表。至于小表并不是数据量小的表就是小表,而是通过where筛选后的数据量小的表才是小表。