2016年4月6日星期三

警惕MySQL连接中的微型表

今天在准备MySQL培训材料的时候对线上的一个SQLTuning, 发现一个简单但是Performance很差的SQL(已经对原SQL进行简化):
SELECT *
FROM
 `order`.`order` a
JOIN
`system`.`company` c

ON a.`company_id` = c.`company_id`

ORDER BY a.order_no
LIMIT 20;
她的执行计划:
执行结果: 184环境 41.216s.
简单的两个表Join, 20行数据, 41.216s, 而这个SQL的复杂版本其实就是我们的order list, 这个是每天都要执行很多遍的.
其实从执行计划我们已经可以预料出这个SQL的性能会很差因为使用了临时表使用了filesort, 这两个都是极耗费资源的操作.
但是看到这个结果我很是奇怪:
我们知道凡有order by都尽量将order by条件所在表作为前导表我们的执行计划中order表已经是前导表order by的条件也是order表的主键order_no, 至于company大家都知道很小的一个表,几条数据而已在怎么样MySQL都不应该选择这样去执行呀!!
我们看一下另外一个SQL:
SELECT *
FROM `order`.`order` a

JOIN `common`.`currency` b
  
ON a.`order_currency` = b.`currency_code`
ORDER BY a.order_no
LIMIT 20;


执行时间: 0s.
同样是小表同样是拿在order表没有indexorder_currency去关联执行结果却差别如此之大!!!
百思不得其解, MySQL会如此的Stupid!!
但是下面的操作稍微让我有点释怀(为了测试我创建了另外一个表test.company):
CREATE TABLE test.company AS SELECT
company_id, `name` FROM system.company;
ALTER TABLE test.company ADD PRIMARY
KEY(company_id);
Select count(*) from system.company;  -- 5条数据
我将SQL改为串test.company, 执行计划以及执行结果是一样的.
INSERT INTO test.company(company_id,
`name`) VALUES(6, 'aaaaa');
看执行计划没有变.
INSERT INTO test.company(company_id,
`name`) VALUES(7, 'bbbb’);
然后我再来看执行计划奇迹出现啦!!

对此我的理解是:

MySQL会根据键的选择性来选择是不是走索引当某个字段的选择性超过某一个数值(一般说是20%)的时候,  如果以这个字段为条件扫描吗MySQL会选择全表扫描而不是该字段上的索引我们这个SQL, 一开始, 5条数据company_id作为主键选择性是20%, 加到6选择性是16.67%, 可能MySQL觉得这个数值还是不够小加到7条的时候选择性下降到14.28%,
MySQL认为这个值够了可以走索引了.

对此我验证一下:
DROP TABLE test.company;
CREATE TABLE test.company AS SELECT
company_id, `name` FROM system.company;
ALTER TABLE test.company ADD PRIMARY
KEY(company_id);
EXPLAIN
SELECT *
FROM
 `order`.`order` a
JOIN
`test`.`company` c FORCE INDEX(PRIMARY)

ON a.`company_id` = c.`company_id`

ORDER BY a.order_no
LIMIT 20;
我们看执行计划:

果然我们强制MySQL在对company表扫描的时候走其主键索引于是整个执行都快了!
(只是为什么从表的索引选择会影响主表的索引选择?)

同样的我强制走主表的索引也达到了一样的效果:
EXPLAIN
SELECT *
FROM
 `order`.`order` a  FORCE INDEX(PRIMARY)
JOIN
`test`.`company` c

ON a.`company_id` = c.`company_id`

ORDER BY a.order_no
LIMIT 20;

结论 & 提醒大家在写SQL的时候需要警惕那种很小的微型表因为他们的索引字段选择性不够导致MySQL在扫描这些表的时候选择全表扫描从而会影响整个SQL的执行计划导致其他表的索引也不能正常使用!!