与没有索引相比,索引的查询性能较差

时间:2018-05-15 14:17:29

标签: mysql database database-performance query-performance

我正在使用MySQL 5.6并且有一个由DATE类型的'network_date'列分区的表(每天都有 分区,例如'2018-05-01',每个分区包含大约400,000行)。该表有两个复合索引(非唯一),它们也包括'network_date'列(首先是6列的顺序)。索引是:

  
      
  1. _daily_ad_level_demand_idx:network_date,publisher_network_id,display_advertiser_id,business_rule_id,campaign_id,ad_id
  2.   
  3. _daily_ad_level_supply_idx:network_date,publisher_network_id,publisher_id,widget_id
  4.   

但是,根据EXPLAIN命令,运行以下查询时:

EXPLAIN EXTENDED SELECT 
    network_date,
    SUM(COALESCE(ad_view, 0)) AS ad_view,
    SUM(COALESCE(ad_spend_network, 0)) AS ad_spend_network,
    SUM(COALESCE(ad_click, 0)) AS ad_click,
    campaign_id,
    display_advertiser_id,
    publisher_network_id,
    ad_id
FROM
    daily_ad_level
WHERE
    (publisher_network_id = 16020)
    AND network_date BETWEEN STR_TO_DATE('2018-04-15 00:00:00.000000',
        '%Y-%m-%d %H:%i:%S.%f') AND STR_TO_DATE('2018-05-12 23:59:59.999000',
        '%Y-%m-%d %H:%i:%S.%f')
GROUP BY campaign_id, network_date, display_advertiser_id, 
publisher_network_id, ad_id

优化器未选择任何索引,并且正在进行全表扫描。 你可以在这里看到结果: EXPLAIN command output with 'network_date' included in index

在做了一些研究并且对它感到困惑之后,我决定从索引中删除“network_date”列 - 分区修剪应该进行必要的查找,因此将它包含在索引中似乎是多余的。再次运行EXPLAIN命令显示现在正在选择索引。你可以在这里看到结果: EXPLAIN command output with no 'network_date' included in index

就查询持续时间而言,当优化程序选择索引时,性能降低:从9.75秒到12.4秒......问题是为什么???

分析第一个 explain命令输出(没有索引用法的那个),可以看到'filtered'和'rows'列的值分别为50.00和4,474,281。可能是优化器推断出全表扫描比使用仅消除大约一半行的索引便宜吗? 如果是这样,我会期望第二种情况下的行为完全相同,情况并非如此:优化器选择索引并且查询执行效果不佳。

有人知道可能导致此行为的原因吗?

4 个答案:

答案 0 :(得分:1)

在阅读了您的评论后,我突然发现分组列的顺序会显着影响查询效果,也就是说,如果我按列重新排序组以匹配索引列顺序(以及添加查询中当前缺少的额外列 - business_rule_id) - 结果在0.23秒内获取,而之前为9.23秒!而且,优化器这次选择了正确的索引。这是修改过的查询:

SELECT 
    network_date,
    SUM(COALESCE(ad_view, 0)) AS ad_view,
    SUM(COALESCE(ad_spend_network, 0)) AS ad_spend_network,
    SUM(COALESCE(ad_click, 0)) AS ad_click,
    campaign_id,
    display_advertiser_id,
    publisher_network_id,
    ad_id
FROM
    daily_ad_level
WHERE
    (publisher_network_id = 16020)
    AND network_date BETWEEN STR_TO_DATE('2018-04-15 00:00:00.000000',
        '%Y-%m-%d %H:%i:%S.%f') AND STR_TO_DATE('2018-05-12 23:59:59.999000',
        '%Y-%m-%d %H:%i:%S.%f')
    GROUP BY  network_date, publisher_network_id ,display_advertiser_id, 
    business_rule_id, campaign_id, ad_id ;

您可以在此处查看结果屏幕截图:Optimized Query Output

这里是未经优化的结果截图:Unoptimized Query Output

虽然结果并不完全相同(由于group by子句中添加了business_rule_id列),但它仍然能够很好地理解优化器"思维方式",所以正确的调整,可以实现所需的结果。

很棒的指导家伙,谢谢!

答案 1 :(得分:0)

首先应该首先将字段与相等运算符(=)进行索引。然后,您应该添加带有范围运算符的列(>,<,BETWEEN,...)。在这种情况下,我没有理由将组中的列编入索引,因为我认为优化器不会选择它们。请参阅下面的推荐指数。

尝试添加此索引:

ALTER TABLE `daily_ad_level` ADD INDEX `daily_ad_level_idx_id_date` (`publisher_network_id`,`network_date`);

答案 2 :(得分:0)

我建议添加两个索引并重写查询。

ALTER TABLE daily_ad_level
ADD INDEX daily_ad_level_idx_id_date (publisher_network_id, network_date);

并且

ALTER TABLE daily_ad_level
ADD INDEX daily_ad_level_idx_campaign_id_network_date_display_advertiser_id_publisher_network_id_ad_id (campaign_id, network_date, display_advertiser_id, 
publisher_network_id, ad_id);

查询重写

我假设列ad_id是表格中的PRIMARY KEY

SELECT
    network_date,
    SUM(COALESCE(ad_view, 0)) AS ad_view,
    SUM(COALESCE(ad_spend_network, 0)) AS ad_spend_network,
    SUM(COALESCE(ad_click, 0)) AS ad_click,
    campaign_id,
    display_advertiser_id,
    publisher_network_id,
    ad_id
FROM (

    SELECT
     ad_id
    FROM  
     daily_ad_level
    WHERE
          publisher_network_id = 16020
        AND
          network_date BETWEEN STR_TO_DATE('2018-04-15 00:00:00.000000',
            '%Y-%m-%d %H:%i:%S.%f') AND STR_TO_DATE('2018-05-12 23:59:59.999000',
            '%Y-%m-%d %H:%i:%S.%f') 
    ) AS daily_ad_level_filterd

    INNER JOIN 
     daily_ad_level
    ON
     daily_ad_level_filterd.ad_id = daily_ad_level.ad_id 

    GROUP BY 
      campaign_id, network_date, display_advertiser_id, 
    publisher_network_id, ad_id

答案 3 :(得分:0)

第1步 - 更好的索引

不要使用network_date启动索引,最后使用它。为什么?通常,一旦达到“范围”测试,就不能使用更多的索引列。

您的第一个查询只需要

INDEX(publisher_network_id, network_date)  -- in this order

当优化大于可缓存在RAM(buffer_pool)中的表时,压倒性的考虑因素是磁盘命中。此索引可最大限度地减少磁盘命中数。

无关:我认为无需在STR_TO_DATE中包装日期时间。

第2步 - 如果不需要则分区

您出于某种原因使用PARTITIONs吗?

  • 表现 - 不太可能有所帮助;当然不会比我刚推荐的INDEX更好。
  • 清除旧记录 - 这是一个很好的理由。

我无法分析您的查询的其余部分,因为没有关于每列所在的表的线索。例如,如果GROUP BY列不在一个表中,那么没办法使用索引。

如果表中有超过50个分区,则会遇到其他低效问题。在这种情况下,建议切换到每周或每月分区。

我们应该考虑其他问题吗?

第3步 - 更好的群集PRIMARY KEY

  • 摆脱分区(除非你需要它进行清除)和
  • PRIMARY KEY (publisher_network_id, network_date)开头。 (加上id或任何使其独特的必要条件,因为PK必须是唯一的。)

为什么会更好?然后所有必要的行一起连续(“聚集”),从而最小化磁盘命中数。

当然,GROUP BY会有一个临时表,排序等,但实际上这可能发生在RAM中。

第4步 - 汇总表

数据仓库涉及“报告”。从原始数据中提取它们的成本非常高,因为需要读取多少行。构建并维护一个汇总表,其中包含每个键的每个组合的行,比如每天。然后针对该表运行“报告”;它可以快速运行10次

有关汇总表的更多信息:http://mysql.rjweb.org/doc.php/summarytables