赞
踩
表结构
- CREATE TABLE `a` (
- `id` INT(11) NOT NULL AUTO_INCREMENT,
- `code` CHAR(36) DEFAULT NULL,
- `name` CHAR(20) DEFAULT NULL,
- PRIMARY KEY (`id`),
- KEY `code` (`code`)
- ) ENGINE=INNODB DEFAULT CHARSET=utf8;
-
- CREATE TABLE `b` (
- `id` INT(11) NOT NULL AUTO_INCREMENT,
- `code` CHAR(36) DEFAULT NULL,
- `col1` CHAR(22) DEFAULT NULL,
- `col2` CHAR(22) DEFAULT NULL,
- `col3` CHAR(22) DEFAULT NULL,
- `col4` CHAR(22) DEFAULT NULL,
- `col5` CHAR(22) DEFAULT NULL,
- `col6` CHAR(22) DEFAULT NULL,
- `col7` CHAR(22) DEFAULT NULL,
- `col8` CHAR(22) DEFAULT NULL,
- `col9` CHAR(22) DEFAULT NULL,
- `col10` CHAR(22) DEFAULT NULL,
- `col11` CHAR(22) DEFAULT NULL,
- `col12` CHAR(22) DEFAULT NULL,
- PRIMARY KEY (`id`),
- KEY `code` (`code`)
- ) ENGINE=INNODB DEFAULT CHARSET=utf8;

-- 插入测试数据
- INSERT INTO a (NAME) VALUES('n');
- INSERT INTO a (NAME) SELECT NAME FROM a; -- 执行多次,达到一定数据量
- UPDATE a SET CODE = UUID();
- UPDATE a SET NAME = id;
- INSERT INTO b (CODE) SELECT CODE FROM a; -- 以a表code作为b表code
- -- 设置b表的其它字段值
- UPDATE b SET
- col1=CONCAT('c',id),col2=CONCAT('c',id),col3=CONCAT('c',id)
- ,col4=CONCAT('c',id),col5=CONCAT('c',id),col6=CONCAT('c',id)
- ,col7=CONCAT('c',id),col8=CONCAT('c',id),col9=CONCAT('c',id)
- ,col10=CONCAT('c',id),col11=CONCAT('c',id),col12=CONCAT('c',id);
- -- 再对b表翻倍2次,即4倍,执行2次
- INSERT INTO b (CODE,col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,`col12`)
- SELECT CODE,col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,`col12` FROM b;
-- a表有131072行,b表有524288行
-- 查询总结果集
- SELECT COUNT(1)
- FROM a
- INNER JOIN b ON a.code=b.code;
-- count: 524288
-- 未优化前
- EXPLAIN
- SELECT a.id AS aid,a.name
- ,b.id AS bid,b.code,b.col1
- ,b.col2,b.col3,b.col4,b.col5
- ,b.col6,b.col7,b.col8,b.col9
- ,b.col10,b.col11
- FROM a
- INNER JOIN b ON a.code=b.code
- ORDER BY b.id
- LIMIT 520000,20;
执行计划是这样的:
-- 耗时:14.2s
-- 优化1.先用临时表查询一部分字段,在inner join 查询全部需要的字段
- EXPLAIN
- SELECT t.aid,t.name
- ,b.id AS bid,b.code,b.col1
- ,b.col2,b.col3,b.col4,b.col5
- ,b.col6,b.col7,b.col8,b.col9
- ,b.col10,b.col11
- FROM (
- SELECT a.id AS aid,b.id,a.name
- FROM a INNER JOIN b ON a.code=b.code
- ORDER BY b.id
- LIMIT 520000,20
- ) t INNER JOIN b ON t.id=b.id;
执行计划是这样的:
解释一下执行计划id这一列:id的数值说明了多表连接的连接顺序,原则是:从大到小,从上到下,
所以,连接顺序第3行,第4行,第1行,第2行
-- 耗时:4s 说明起作用了,此方法优化的效率取决于临时表t的字段数量
-- 2.反向查询法,当偏移量大于where筛选后总量的【一半】时,可以使用反向查找(原理下面讲)
-- ,排序规则相反,缩小偏移量offsize,
-- 新偏移量=where筛选后总数-旧偏移量-size,所以,offsize=524288 - 520000 - 20 = 4268
-- 不过这样查出来的数据顺序与我们需要的结果是相反的,所以需要再倒转结果集,或者渲染时反向遍历
- EXPLAIN
- SELECT t.aid,t.name
- ,b.id AS bid,b.code,b.col1
- ,b.col2,b.col3,b.col4,b.col5
- ,b.col6,b.col7,b.col8,b.col9
- ,b.col10,b.col11
- FROM (
- SELECT a.id AS aid,b.id,a.name
- FROM a INNER JOIN b ON a.code=b.code
- ORDER BY b.id DESC
- LIMIT 4268,20
- ) t INNER JOIN b ON t.id=b.id ORDER BY t.id ASC;
- -- 最后这个order要注意一下,不要by b.id,否则会出现Using temporary; Using filesort
- -- by t.id只会有Using filesort,下面的sql同理,后面会解释原理的。
执行计划是这样的:
-- 耗时:3.8s 这个变化很微弱?或者说根本没用?我觉的可能被其它因素影响了,这个我们先放着,下面再验证
-- 优化到这一步,很明显3.8秒不符合正常的要求.
-- 经过分段执行发现,效率出现大损耗的地方是,第10行:【ORDER BY b.id DESC】
-- ,这时我就有疑惑了,虽然这里需要排序的数据量非常大,但b.id不是索引字段吗?
-- 此处应该用到索引排序的!可以通过这个执行计划第3行的Extra发现并没有,而是用到了额外的排序(using filesort),为什么?
-- 网上博客: "要尽可能的保证排序字段在驱动表中"
-- 先解释什么是驱动表:这一切还得从MySQL多表连接的处理方式说起,首先MySQL优化器要确定以谁为驱动表,
-- 也就是说以哪个表为基准,在处理此类问题时,MySQL优化器采用了简单粗暴的解决方法:哪个表的结果集小,
-- 就以哪个表为驱动表,当然MySQL优化器实际的处理方式会复杂许多,还会根据order by 的字段来优化
-- 也就是说如果你的order by 子句排序字段在大表的话,优化器‘或许’会选择大表为驱动表,当然前提是
-- 你的另一张表的关联字段建立了索引,否则还是会选择关联字段有索引的表为‘被’驱动的对象,比如:
-- if a表 < b表,a表的code字段没有索引,而b表的字段code有索引,则选择a为驱动,b为被驱动,a主b从
-- if a < b, a.code有索引,b.code无索引,则b主a从
-- if a < b, a.code和b.code都有索引,先不考虑order by的排序字段,则a主b从
-- if a < b, a.code和b.code都有索引,如有order by b.id,则不确定,如果a 小于 b很多的话
-- 还是会选择a为驱动,a主b从,如果差别不明显的话,b主a从,
-- 正因为优化器选择驱动表的复杂性,所以有时人是很难判断出哪个是驱动表,刚开始验证这个时,
-- a的数据量是b的1/2,本来‘反向查询法’那一块sql,执行的时候还是以a表为驱动,还是很耗时的,
-- 但过了一会儿以后,数据我并没有改变,再执行发现很快,瞬间懵逼了,通过explain 查看驱动表变成了b表
-- 我把b表翻倍以后,才让驱动表稳定为a表。
-- 这让我不得不怀疑MySQL有JIT的优化,至于事实有没有我没有找到资料文档。
-- 驱动表的解释就到这里了,下面接着理解“要尽可能的保证排序字段在驱动表中”这句话。
-- a = 1/4b,
-- 上面这个sql语句并没有where筛选过滤,所以a表结果集 < b表结果集,则a表是驱动
-- 显而易见,排序字段b.id是b表的字段,不在驱动表内
-- ,于是乎不可避免的出现了"额外的排序"[Using filesort],甚至使用了"临时表"[Using temporary]
-- 更准确的说,
-- 1.用非驱动表的字段排序会出现[Using temporary]
-- 则不管排序的字段有没有索引都会[Using filesort],
-- 3.而用驱动表字段排序则不会[Using temporary],
-- 则要想都没有[Using temporary]和[Using filesort]必须是驱动表的索引字段
-- 即两个条件:1:驱动;2:索引
-- 既然这样,那我们对语句修改,修改后的语句是:
- EXPLAIN
- SELECT t.aid,t.name
- ,b.id AS bid,b.code,b.col1
- ,b.col2,b.col3,b.col4,b.col5
- ,b.col6,b.col7,b.col8,b.col9
- ,b.col10,b.col11
- FROM (
- SELECT a.id AS aid,b.id,a.name
- FROM a INNER JOIN b ON a.code=b.code
- ORDER BY a.id DESC
- LIMIT 4268,20
- ) t INNER JOIN b ON t.id=b.id ORDER BY t.aid ASC;
执行计划是这样的:
发现执行计划第3行Extra没有了 Using temporary; Using filesort,并且type变成了index
-- 耗时:0.02s
-- 现在我们继续验证反向查找法是否有用,在此语句基础去掉反向查找法的优化看看效率怎样
- EXPLAIN
- SELECT t.aid,t.name
- ,b.id AS bid,b.code,b.col1
- ,b.col2,b.col3,b.col4,b.col5
- ,b.col6,b.col7,b.col8,b.col9
- ,b.col10,b.col11
- FROM (
- SELECT a.id AS aid,b.id,a.name
- FROM a INNER JOIN b ON a.code=b.code
- ORDER BY a.id
- LIMIT 520000,20
- ) t INNER JOIN b ON t.id=b.id;
-- 1.7s,1.7s > 0.02s 证明了反向查找法有效
-- 反向查找法有效的原理:首先limit 的本质是扫描offsize + size 次,舍弃前offsize行数据,只去后size行
-- 比如 limit 520000,20 ,则扫描520000+20行,舍弃前520000行,取后20行
-- 所以得出结论:偏移量offsize越小扫描的的次数越少,效率更高,通过比较explain的rows的信息可以看到扫描次数区别
-- 经过把排序字段改为驱动表的字段后(b.id -> a.id),效果立竿见影,只需0.02s和1.7s-- 这个提升级别是很大的
-- 但是有个问题,排序的字段变了,结果当然就不一样了
-- ,这个问题只能具体情况具体分析了,到底用不用驱动表的字段来排序要根据具体业务逻辑来
-- 如果一定要保证排序字段在驱动表中,需要用STRAIGHT_JOIN 代替 inner join 来强制连接顺序
- EXPLAIN
- SELECT t.aid,t.name
- ,b.id AS bid,b.code,b.col1
- ,b.col2,b.col3,b.col4,b.col5
- ,b.col6,b.col7,b.col8,b.col9
- ,b.col10,b.col11
- FROM (
- SELECT a.id AS aid,b.id,a.name
- FROM b STRAIGHT_JOIN a ON a.code=b.code
- ORDER BY b.id DESC
- LIMIT 4268,20
- ) t INNER JOIN b ON t.id=b.id ORDER BY t.id ASC;
执行计划是这样的:
-- 耗时:0.06s, 效率又差了点,这是意料之中的,大表驱动小表效率不差才怪,如果这里b表更大的话,效率会更差
-- 这要注意,改变驱动表,比如,b驱动a,如果a.code原来没有索引
-- ,则必须要另外为a.code建立索引,否则
-- ,数据量一大,等待执行结果就得looong time了
-- 总结:优化要根据实际情况来,没有绝对的好的语句,也没有永远美的语句
-- 不同时期的语句优化也不一样
-- 大家可能会遇到类似下面的问题:原本运行良好的查询语句,过了一段时间后,可能会突然变得很糟糕。
-- 一个很大可能的原因就是数据分布情况发生了变化,比如:b表变得比a表大
-- ,或者联合查询的where字句的条件筛选出现新变化,从而导致MySQL优化器对驱动表的选择发生了变化
-- ,进而出现on字句使用索引失效的情况,除非强制驱动表STRAIGHT_JOIN。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。