【问题标题】:MySQL "IN" queries terribly slow with subquery but fast with explicit valuesMySQL“IN”查询对子查询非常慢,但对显式值快速
【发布时间】:2011-06-28 10:40:56
【问题描述】:

我有一个 MySQL 查询(Ubu 10.04、Innodb、Core i7、16Gb RAM、SSD 驱动器、MySQL 参数优化):

SELECT
COUNT(DISTINCT subscriberid)
FROM
em_link_data
WHERE
linkid in (SELECT l.id FROM em_link l WHERE l.campaignid = '2900' AND l.link != 'open')

表 em_link_data 大约有 700 万行,em_link 有几千行。 完成此查询大约需要 18 秒。但是,如果我替换结果 子查询并执行以下操作:

SELECT
COUNT(DISTINCT subscriberid)
FROM
em_link_data
WHERE
linkid in (24899,24900,24901,24902);

那么查询将在不到 1 毫秒的时间内运行。子查询单独运行不到1ms,列linkid被索引。

如果我将查询重写为连接,也少于 1 毫秒。为什么带有子查询的“IN”查询如此之慢,为什么其中的值如此之快?我无法重写查询(购买的软件),所以我希望有一些调整或提示来加速这个查询!任何帮助表示赞赏。

【问题讨论】:

  • 你的解释计划是怎么说的?你配置了哪些索引?
  • 我猜em_link需要一个包含campaignidlink的索引。
  • 这是优化器的结果: select count(distinct ackci.em_link_data.subscriberid) AS COUNT(DISTINCT subscriberid) from ackci.em_link_data where (ackci .em_link_data.linkid, (((ackci.em_link_data.linkid) 在 em_link 中 PRIMARY where ((ackci.l.@987654304 @ = '2900') 和 (ackci.l.link '打开') 和 ((ackci.em_link_data.linkid) = ackci.@987654348 @.id)))))
  • 我相信 MySQL 5.6.7 使用 materialization 选项解决了这个问题。如果子查询独立于外部查询,那么它会被执行一次,在内部变成临时表,然后加入外部查询。这一直是 MySQL 的一个非常令人沮丧的问题,Oracle 在几十年前设法解决了这个问题。

标签: mysql query-optimization


【解决方案1】:

如果您的子查询速度很快,那么campaignid 和link 绝对会被编入索引。 l.id 是 PK 并且集群因此速度很快。 但据我记得(从我上次检查这个主题开始),mysql 描述了它对“in”子查询的内部优化,以使用子查询结果的索引排序来提高性能,并且还在“IN”的左侧使用缓存更快地将其拖到子查询中,如果索引设置为 true,则使用内部联接或“IN”而不是缓存不能有这种差异,这可能是由于缓存问题和大量数据。 http://dev.mysql.com/doc/internals/en/transformation-scalar-in.html

我不知道软件的情况,但是如果您可以使用 INNER JOIN 并且您(可能)在外部查询的 WHERE 子句中的 IN 子句之前有一些额外的定义,请确保将这些子句移到之前您通过临时 INNER JOIN 进行的主要 INNER JOIN 的行为类似于按顺序插入的“where”子句,并减少了 JOIN 中的交叉比较次数,如下所示:

SELECT ... FROM t
INNER JOIN (SELECT 1) AS tmp ON t.asd=23
INNER JOIN t2 ON ...

普通和临时连接查找的示例比较:1000 * 1000 > 1000 + (100 * 1000)

似乎子查询是由常量 vals 过滤的,因此如果是我,我会将子句放在生成结果集的子查询中,并像这样减少 JOIN 中的比较次数:

SELECT ... FROM t
INNER JOIN (SELECT ... FROM t2 WHERE constant clauses) AS tbl2 ON ...

无论如何,在“IN”查询中,将子查询中的表的任何列与外部查询中的表的任何列进行比较,都需要对双方的列进行精确索引(关于复合索引),但仍然可能是缓存问题。

已编辑: 我也很想问:在 l.campaignid、l.link 和 l.id 上制作综合索引是否有意义?

【讨论】:

    【解决方案2】:

    问题在于 MySQL 从外到内执行查询,而您可能认为您的子查询只执行一次,然后将其结果传递给外部查询的 WHERE 表达式(请参阅MySQL documentation)。

    如果你不能重写你的查询,你应该做以下优化:

    • 如 FrustratedWithFormsDesigner 所说,在 campaignidlink 上添加索引
    • 通过EXPLAIN SELECT ...检查子查询是否正确使用索引
    • 启用和调整查询缓存,因为这样可以加快子查询被多次调用的速度

    另一个想法是安装MySQL proxy 并编写一个小脚本来拦截您的查询并重写它以使用连接。

    【讨论】:

    • 这让我大开眼界——子查询实际上为外部查询中的每一行运行一次这一事实太疯狂了!假设相反,我一直在写我的查询。谢谢!
    • 什么?!?我正在阅读它并且不相信。您能否解释一下它与预期相反的原因是什么?为什么从外部 -> 内部?
    【解决方案3】:

    每次评估子查询时都会执行(无论如何在 MySQL 中,不是所有的 RDBMS),也就是说,您基本上是在运行 700 万个查询!如果可能,使用 JOIN 会将其减少到 1。即使添加索引可以提高它们的性能,您仍在运行它们。

    【讨论】:

    • 但是我提供子查询结果的第二个示例怎么会超快? mySQL 不会先执行子查询然后执行主查询吗?我在想优化器正在丢球……如果它第一次执行子查询并获取4个值的列表然后执行主查询应该没问题……我在建议的所有列中都有索引…… .
    • @Franco - 是的,优化器的性能在这些方面非常糟糕。见stackoverflow.com/questions/3417074/…
    • @Franco:在第二个示例中,您提供了四个标量值。无需执行子查询,比较四个整数非常快。
    • 是的,包含 4 个标量的列表不是子查询,它只是看起来很模糊,有点像一个
    • 马丁,谢谢你指点我,你太棒了!我搜索但没有适当的“语言”来搜索这个。这解释了它是一个优化器错误,否则它根本没有意义。 @Brian:我提供了 4 个标量作为我想象的理想优化器会做的示例,即首先执行独立子查询并缓存结果;然后它的行为就像我认为的标量版本。
    【解决方案4】:

    是的,带有子查询的IN 很慢。请改用联接。

    SELECT
    COUNT(DISTINCT subscriberid)
    FROM em_link_data JOIN em_link ON em_link_data.linkid=em_link.id
    WHERE em_link.campaignid = '2900' AND em_link.link != 'open'
    

    并确保您已在 em_link_data.linkidem_link.id 上定义了索引。

    【讨论】:

    • 对不起,从来没有做过任何基准测试......只是从经验中知道这一点。
    • 奇怪的是,如果我将子查询更改为它应该返回的 4 个值,我会快速闪电。优化器是否搞乱了执行顺序?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-30
    • 1970-01-01
    • 2011-09-02
    • 1970-01-01
    相关资源
    最近更新 更多