为何MySQL在该关联统计查询中忽略sms表的索引?
解析MySQL索引选择与EXPLAIN rows字段的疑问
一、为什么初始查询中MySQL忽略sms表的单独索引?
你的查询需要同时满足关联条件sms_csinteve_id = customer_service_interaction_events.csinteve_id和过滤条件sms_handler_user_id = 0,虽然两个字段都有单独索引,但MySQL优化器判断单独索引的执行成本高于全表扫描,核心原因有两点:
- 单独索引的回表开销:MyISAM使用非聚簇索引,单独的
sms_csinteve_id索引只能定位到匹配行的位置,但需要回表读取sms_handler_user_id的值做过滤;同理,单独的sms_handler_user_id索引能找到所有未处理SMS,但要回表读取sms_csinteve_id做关联。如果符合单条件的行数占全表比例很高(比如未处理SMS数量极多),回表的IO成本会远超过全表扫描,优化器自然会选择后者。 - 优化器的成本估算逻辑:MySQL会依赖表的统计信息(字段基数、总行数等)计算执行计划成本。如果单独索引过滤后的结果集仍然很大,优化器会认为“索引查找+回表”的总开销比直接全表扫描更高,因此放弃使用单独索引。
二、添加复合索引后rows数值仍较大的原因?
你新增的复合索引(sms_handler_user_id, sms_csinteve_id)是完美的覆盖索引(Extra显示Using index,说明索引本身就能满足统计行数的需求,无需回表),且查询类型变为ref,已经在高效利用索引了。但rows字段显示的2083577是优化器的估算行数,而非实际匹配的行数,主要原因:
- 统计信息过时:MySQL依赖表的统计信息做行数估算,如果sms表数据量庞大且近期有大量数据变更,统计信息可能未及时更新,导致优化器无法准确判断复合索引下的实际匹配行数。你可以手动执行
ANALYZE TABLE sms;更新统计信息,之后再看EXPLAIN的rows值会更贴近真实情况。 - 字段基数的影响:如果
sms_handler_user_id=0的行本身就占全表绝大多数,即使加上sms_csinteve_id的过滤,优化器可能还是以sms_handler_user_id的基数为基础做估算,导致rows值接近全表行数。但此时实际执行效率已经远高于全表扫描,你可以对比两次查询的实际执行时间来验证这一点。
另外补充:你给customer_service_interaction_events添加的复合索引(csinteve_interaction_id, csinteve_id)是冗余的——InnoDB的二级索引会自动包含主键字段,原有的csinteve_interaction_id索引已经能通过ref定位到行,并且直接获取到主键csinteve_id,这个复合索引没有额外作用,可以考虑删除。
内容的提问来源于stack exchange,提问作者tonix
相关产品推荐
相关产品推荐

