(1)回顾
    SELECT COUNT(id) FROM users WHERE id IN(SELECT user_id FROM users_extent_info WHERE latest_login_time 系统运行时,会先跑一下COUNT聚合函数来查询这个结果集有多少数据,然后再分批查询。结果就是这个COUNT聚合函数的SQL,在千万大表的场景下,都要花几十秒才能跑出来,这种性能,系统基本没法跑了。所以今天分析下这个SQL的执行计划,不过需要注意的是,因为不同的MySQL版本执行计划可能都不一样,实际上每个不同的MySQL版本 都可能会调整生成计划的方式,所以同样的SQL在不同的MySQL版本下跑,可能执行计划都不要一样。
    可能同样的SQL去不同的MySQL里没法还原出来这里的执行计划,重点学习执行计划的分析思路,以及如何从执行计划里看出性能问题所在,最后就是如何进行调优。

    (2)执行计划分析
    EXPLAIN SELECT COUNT(id) FROM users WHERE id IN (SELECT user_id FROM users_extent_info WHERE latest_login_time < xxxx)
    前景为了方便看,把执行计划简化了几个字段,保留最关键的几个字段,为了调优在测试环境的单表2万条数据场景下跑出来的执行计划,users表5万条,当时这个SQL跑了十多秒,所以足够复现问题。 MATERIALIZED :物化表

    1. +----+-------------+-------+------------+-------+---------------+----------+---------+------+
    2. | id | select_type | table | type | key | rows | filtered | Extra |
    3. +----+-------------+-------+------------+-------+---------------+----------+---------+------+
    4. | 1 | SIMPLE | <subquery2> | ALL | NULL | NULL | 100.00 | NULL |
    5. | 1 | SIMPLE | users | ALL | NULL | 49651 | 10.00 | Using where; Using join buffer(Block Nested Loop) |
    6. | 2 | MATERIALIZED | users_extent_info | range | idx_login_time | 4561 | 100.00 | NULL |
    7. +----+-------------+-------+------------+-------+---------------+----------+---------+------+

    首先针对子查询,是执行计划里的第三行实现的,针对users_extent_info,使用了 idx_login_time 这个索引,做了range类型的索引范围扫描,查出来4561条数据,没有其他额外的筛选,所以filtered是100%;
    接着是MATERIALIZED,表明了这里把子查询的4561条数据代表的结果集进行了物化,物化成一个临时表,这个临时表物化,一定会把4561条数据临时落到磁盘文件里去,这个过程挺慢的。
    然后第二条执行计划表明针对users表做了一个全表扫描,在全表扫描的时候扫出来49651条数据,同时Extra字段,显示一个Using join buffer 的信息,这个表示在执行join操作???
    接着执行计划第一条,针对子查询查出的一个物化临时表,也就是 ,做了一个全表扫描,把里面的数据扫描一遍,为什么对这个临时表进行全表扫描呢?原因是让在users表的每一条数据,都要跟物化临时表里的数据进行join,所以针对users表里的每一条数据,只能去全表扫描一遍物化临时表,查找物化临时表那些数据是跟他匹配的,才能筛选出一条结果。
    第二条执行计划的全表扫描的结果表明一共扫到了49651条数据,但是全表扫描过程中,因为跟物化临时表执行了一个join操作,而物化临时表就4561条数据,所以最终第二条执行计划的filtered显示的是100%,也就是,最终从users表里筛选出来4000多条数据。

    (3)结尾
    这就是该条SQL语句的执行计划,不同MySQL版本可能不一样,甚至差别很大,所以没必要还原这个执行计划。主要看SQL执行计划的分析过程。