有索引的查询性能比没有索引的查询性能差

zte4gxcn  于 2021-06-21  发布在  Mysql
关注(0)|答案(4)|浏览(344)

我使用的是mysql 5.6,有一个表被date类型的'network\u date'列分区(每天都有一个分区,例如'2018-05-01',每个分区包含大约400000行)。该表有两个复合索引(不是唯一的),其中还包括“network\u date”列(按6列的顺序排列)。索引为:
_每日广告级别需求id:网络日期、发布者网络id、显示广告商id、业务规则id、活动id、广告id
_每日广告级别供应:网络日期、发布者网络id、发布者id、小部件id
但是,根据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命令输出,索引中包含“network\u date”
在做了一些研究和困惑之后,我决定从索引中删除'network\u date'列-分区修剪应该做必要的查找,所以将它包含在索引中似乎是多余的。再次运行explain命令表明,现在正在选择索引。您可以在这里看到结果:explain命令输出,索引中不包含“network\u date”
就查询持续时间而言,当优化器选取索引时,性能会下降:从9.75秒降至12.4秒。。。问题是为什么???
分析第一个explain命令输出(不使用索引的输出),可以看到'filtered'和'rows'列的值分别为50.00和4474281。是否优化器推断出一个完整的表扫描比使用一个只消除大约一半行的索引便宜?如果是这样的话,我希望在第二个场景中也会出现同样的行为,但事实并非如此:优化器选择了一个索引,而查询的性能很差。
有人知道是什么导致这种行为吗?

sauutmhj

sauutmhj1#

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

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

2w3kk1z52#

在阅读了各位的评论之后,我突然意识到按列顺序分组会显著影响查询性能,也就是说,如果我对按列分组进行重新排序以匹配索引列顺序(并添加一个查询中当前缺少的额外列-business\u rule\u 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 ;

您可以在这里看到结果屏幕截图:优化的查询输出
下面是未优化的结果截图:未优化的查询输出
虽然结果不完全相同(由于groupby子句中添加了business\u rule\u id列),但它仍然很好地理解了优化器的“思维方式”,因此只要进行适当的调整,就可以获得所需的结果。
很好的指导,谢谢!

but5z9lq

but5z9lq3#

第1步-更好的索引
不要以开始索引 network_date ,用它结束他们。为什么?一般来说,一旦达到“范围”测试,就不能使用索引的更多列。
您的第一个查询需要

INDEX(publisher_network_id, network_date)  -- in this order

当优化大于可以缓存在ram(缓冲池)中的表时,最大的考虑因素是磁盘命中率。此索引将磁盘命中次数降至最低。
无关:我认为没有必要将日期时间 Package 在 STR_TO_DATE .
第2步-如果不需要,抛出分区
你在用吗 PARTITIONs 不知为什么?
表现——不太可能有帮助;当然不比 INDEX 我只是推荐。
清除旧记录——一个很好的理由。
我无法分析查询的其余部分,因为不知道每列在哪个表中。例如,如果 GROUP BY 列不是全部在一个表中,因此无法使用索引。
如果表中有大约50多个分区,则会导致其他低效率问题。在这种情况下,建议切换到每周或每月分区。
我们还应该考虑其他问题吗?
步骤3-更好的群集主键
去掉分区(除非清除时需要分区),然后
使 PRIMARY KEY 从…开始 (publisher_network_id, network_date) . (钉上 id 或者任何使其独特的必要条件,因为pk必须是唯一的。)
为什么这会更好?然后,所有必要的行都是连续的(“集群”)在一起,从而最大限度地减少磁盘命中数。
当然,然后会有一个临时表,排序等,为 GROUP BY ,但这实际上可能发生在ram中。
步骤4-汇总表
数据仓库涉及“报告”。因为需要读取多少行,所以从原始数据中提取它们的成本非常高。构建并维护一个摘要表,该表中的每一行对应于每一天的键组合。然后对该表运行“report”;它的速度可能是原来的10倍。
有关摘要表的详细信息:http://mysql.rjweb.org/doc.php/summarytables

zpf6vheq

zpf6vheq4#

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

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\u id是表中的主键

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

相关问题