mysql前100条_MySQL优化实战⼆
常见的sql深⼊优化
很多时候我们业务系统实现分页功能可能会⽤如下sql实现
mysql> select * from e_books limit 10000,10;
表⽰从表 e_books 中取出从 10001 ⾏开始的 10 ⾏记录。看似只查询了 10 条记录,实际这条 SQL 是先读取 10010条记录,然后抛弃前 10000 条记录,然后读到后⾯ 10 条想要的数据。因此要查询⼀张⼤表⽐较靠后的数据,执⾏效率是⾮常低的。
分页场景优化技巧
1、根据⾃增且连续的主键排序的分页查询
⾸先来看⼀个根据⾃增且连续主键排序的分页查询的例⼦
mysql> select * from e_books limit 90000,5;
该 SQL 表⽰查询从第 90001开始的五⾏数据,没添加单独 order by,表⽰通过主键排序。我们再看表 e
_books,因为主键是⾃增并且连续的,所以可以改写成按照主键去查询从第 90001开始的五⾏数据,如下:
mysql> select * from e_books where id > 90000 limit 5;
查询的结果是⼀致的。我们再对⽐⼀下执⾏计划:
mysql> EXPLAIN select * from employees limit 90000,5;
mysql> EXPLAIN select * from employees where id > 90000 limit 5;
显然改写后的 SQL ⾛了索引,⽽且扫描的⾏数⼤⼤减少,执⾏效率更⾼。
但是,这条 改写的SQL 在很多场景并不实⽤,因为表中可能某些记录被删后,主键空缺,导致结果不⼀致,先删除⼀条前⾯的记录,然后再测试原 SQL 和优化后的 SQL:
两条 SQL 的结果并不⼀样,因此,如果主键不连续,不能使⽤上⾯描述的优化⽅法。
另外如果原 SQL 是 order by ⾮主键的字段,按照上⾯说的⽅法改写会导致两条 SQL 的结果不⼀致。所以这种改写得满⾜以下两个条件:
主键⾃增且连续
结果是按照主键排序的
2、根据⾮主键字段排序的分页查询
再看⼀个根据⾮主键字段排序的分页查询,SQL 如下:
mysql> select * from e_books ORDER BY name limit 90000,5;
mysql> EXPLAIN select * from e_books ORDER BY name limit 90000,5;
发现并没有使⽤ name 字段的索引(key 字段对应的值为 null),具体原因上⼀篇⽂章讲过:扫描整个索引并查到没索引的⾏(可能要遍历
多个索引树)的成本⽐扫描全表的成本更⾼,所以优化器放弃使⽤索引。
知道不⾛索引的原因,那么怎么优化呢?
其实关键是让排序时返回的字段尽可能少,所以可以让排序和分页操作先查出主键,然后根据主键查到对应的记录,SQL改写如下
mysql> select * from e_books e inner join (select id from e_books order by name limit 90000,5) f on e.id = f.id;
需要的结果与原 SQL ⼀致,执⾏时间减少了⼀半以上,我们再对⽐优化前后sql的执⾏计划:
原 SQL 使⽤的是 filesort 排序,⽽优化后的 SQL 使⽤的是索引排序。
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 `id 往t1表插⼊1万⾏记录,往t2表插⼊100⾏记录
mysql的表关联常见有两种算法
Nested-Loop Join 算法
Block Nested-Loop Join 算法
1、 嵌套循环连接 Nested-Loop Join(NLJ) 算法
mysql删除重复的数据保留一条⼀次⼀⾏循环地从第⼀张表(称为驱动表)中读取⾏,在这⾏数据中取到关联字段,根据关联字段在另⼀张表(被驱动表)⾥取出满⾜条件的
⾏,然后取出两张表的结果合集。
mysql> EXPLAIN select*from t1 inner join t2 on t1.a= t2.a;
从执⾏计划中可以看到这些信息:
驱动表是 t2,被驱动表是 t1。先执⾏的就是驱动表(执⾏计划结果的id如果⼀样则按从上到下顺序执⾏sql);优化器⼀般会优先选择⼩表做驱动表。所以使⽤ inner join 时,排在前⾯的表并不⼀定就是驱动表。
使⽤了 NLJ算法。⼀般 join 语句中,如果执⾏计划 Extra 中未出现 Using join buffer 则表⽰使⽤的 join 算法是 NLJ。
上⾯sql的⼤致流程如下:
1. 从表 t2 中读取⼀⾏数据;
2. 从第 1 步的数据中,取出关联字段 a,到表 t1 中查;
3. 取出表 t1 中满⾜条件的⾏,跟 t2 中获取到的结果合并,作为结果返回给客户端;
4. 重复上⾯ 3 步。
整个过程会读取 t2 表的所有数据(扫描100⾏),然后遍历这每⾏数据中字段 a 的值,根据 t2 表中 a 的值索引扫描 t1 表中的对应⾏(扫描100次 t1 表的索引,1次扫描可以认为最终只扫描 t1 表⼀⾏完整数据,也就是总共 t1 表也扫描了100⾏)。因此整个过程扫描了 200
⾏。
如果被驱动表的关联字段没索引,使⽤NLJ算法性能会⽐较低(下⾯有详细解释),mysql会选择Block Nested-Loop Join算法。
2、 基于块的嵌套循环连接 Block Nested-Loop Join( BNL )算法
把驱动表的数据读⼊到 join_buffer 中,然后扫描被驱动表,把被驱动表每⼀⾏取出来跟 join_buffer 中的数据做对⽐。
mysql>EXPLAIN select*from t1 inner join t2 on t1.b= t2.b;
Extra 中 的Using join buffer (Block Nested Loop)说明该关联查询使⽤的是 BNL 算法。
上⾯sql的⼤致流程如下:
1. 把 t2 的所有数据放⼊到 join_buffer 中
2. 把表 t1 中每⼀⾏取出来,跟 join_buffer 中的数据做对⽐
3. 返回满⾜ join 条件的数据
整个过程对表 t1 和 t2 都做了⼀次全表扫描,因此扫描的总⾏数为10000(表 t1 的数据总量) + 100(表 t2 的数据总量) =10100。并且
join_buffer ⾥的数据是⽆序的,因此对表 t1 中的每⼀⾏,都要做 100 次判断,所以内存中的判断次数是100 * 10000= 100 万次。被
驱动表的关联字段没索引为什么要选择使⽤ BNL 算法⽽不使⽤ Nested-Loop Join 呢?
如果上⾯第⼆条sql使⽤ Nested-Loop Join,那么扫描⾏数为 100 * 10000 = 100万次,这个是磁盘扫描。很显然,⽤BNL磁盘扫描次
数少很多,相⽐于磁盘扫描,BNL的内存计算会快得多。因此MySQL对于被驱动表的关联字段没索引的关联查询,⼀般都会使⽤ BNL 算
法。如果有索引⼀般选择 NLJ 算法,有索引的情况下 NLJ 算法⽐ BNL算法性能更⾼
对于关联sql的优化
关联字段加索引,让mysql做join操作时尽量选择NLJ算法
⼩表驱动⼤表,写多表连接sql时如果明确知道哪张表是⼩表可以⽤straight_join写法固定连接驱动⽅式,省去mysql优化器⾃⼰判断的时
straight_join解释:straight_join功能同join类似,但能让左边的表来驱动右边的表,能改表优化器对于联表查询的执⾏顺序。
⽐如:select * from t2 straight_join t1 on t2.a = t1.a; 代表指定mysql选择 t2 表作为驱动表。
straight_join只适⽤于inner join,并不适⽤于left join,right join。(因为left join,right join已经代表指定了表的执⾏顺序)尽可能让优
化器去判断,因为⼤部分情况下mysql优化器是⽐⼈要聪明的。使⽤straight_join⼀定要慎重,因为部分情况下⼈为指定的执⾏顺序并不⼀
定会⽐优化引擎要靠谱。
in和exsits优化
原则:⼩表驱动⼤表,即⼩的数据集驱动⼤的数据集
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 } #A表与B表的ID字段应建⽴索引
1、EXISTS (subquery)只返回TRUE或FALSE,因此⼦查询中的SELECT * 也可以⽤SELECT 1替换,官⽅说法是实际执⾏时会忽略
SELECT清单,因此没有区别
2、EXISTS⼦查询的实际执⾏过程可能经过了优化⽽不是我们理解上的逐条对⽐
3、EXISTS⼦查询往往也可以⽤JOIN来代替,何种最优需要具体问题具体分析
count(*)查询优化
# 临时关闭mysql查询缓存,为了查看sql多次执⾏的真实时间
mysql> set global query_cache_size=0;
mysql> set global query_cache_type=0;
mysql> EXPLAIN select count(1) from e_books;
mysql> EXPLAIN select count(id) from e_books;
mysql> EXPLAIN select count(name) from e_books;
mysql> EXPLAIN select count(*) from e_books;
四个sql的执⾏计划⼀样,说明这四个sql执⾏效率应该差不多,区别在于根据某个字段count不会统计字段为null值的数据⾏
为什么mysql最终选择辅助索引⽽不是主键聚集索引?因为⼆级索引相对主键索引存储数据更少,检索性能应该更⾼
常见优化⽅法
1、查询mysql⾃⼰维护的总⾏数
对于myisam存储引擎的表做不带where条件的count查询性能是很⾼的,因为myisam存储引擎的表的总⾏数会被mysql存储在磁盘上,查询不需要计算对于innodb存储引擎的表mysql不会存储表的总记录⾏数,查询count需要实时计算。
2、show table status
如果只需要知道表总⾏数的估计值可以⽤如下sql查询,性能很⾼
3、将总数维护到Redis⾥
插⼊或删除表数据⾏的时候同时维护redis⾥的表总⾏数key的计数值(⽤incr或decr命令),但是这种⽅式可能不准,很难保证表操作和redis操作的事务⼀致性
4、增加计数表
插⼊或删除表数据⾏的时候同时维护计数表,让他们在同⼀个事务⾥操作。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。