【问题标题】:How to speed up a query containing HAVING?如何加快包含 HAVING 的查询?
【发布时间】:2022-10-16 06:44:48
【问题描述】:

我有一个包含近十亿条记录的表,需要使用HAVING 对其进行查询。它非常慢(在体面的硬件上大约需要 15 分钟)。如何加快速度?

SELECT ((mean - 3.0E-4)/(stddev/sqrt(N))) as t, ttest.strategyid, mean, stddev, N, 
  kurtosis, strategies.strategyId 
FROM ttest,strategies 
WHERE ttest.strategyid=strategies.id AND dataset=3 AND patternclassid="1" 
  AND exitclassid="1" AND N>= 300 HAVING t>=1.8

我认为问题是t 不能被索引,因为它需要被计算。我无法将其添加为列,因为“3.0E-4”会因查询而异。

桌子:

create table ttest (
  strategyid bigint,
  patternclassid integer not null,
  exitclassid integer not null,
  dataset integer not null,
  N integer,
  mean double,
  stddev double,
  skewness double,
  kurtosis double,

  primary key (strategyid, dataset)
);
create index ti3 on ttest (mean);
create index ti4 on ttest (dataset,patternclassid,exitclassid,N);

create table strategies (
  id bigint ,
  strategyId varchar(500),

  primary key(id),
  unique key(strategyId)
);

explain select..

id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE ttest NULL range PRIMARY,ti4 ti4 17 NULL 1910344 100.00 Using index condition; Using MRR
1 SIMPLE strategies NULL eq_ref PRIMARY PRIMARY 8 Jellyfish_test.ttest.strategyid 1 100.00 Using where

【问题讨论】:

  • 在子查询(或 cte)中做所有涉及 ttest 的事情,然后加入策略
  • 如果你不介意,我需要更多帮助。我虽然这样做可以解决问题:select ((mean-3.0E-4)/stddev/sqrt(N)), ttest.strategyid, mean, stddev, N, skewness, kurtosis, strategies.strategyId FROM ttest,strategies where ttest.strategyid=strategies.id AND dataset=3 AND patternclassid="1" AND exitclassid="1" AND N>= 300 and (select ((mean - 3.0E-4)/(stddev/sqrt(N))) from ttest) >1.8 ; 不幸的是:错误 1242 (21000):子查询返回超过 1 行
  • 为什么两个表中都有`strategyid,而不是你要加入的?
  • 您遇到的错误是由于不必要的SELECT。该表达式可以简单地用在WHERE 子句中,(见我的回答。)
  • CTE 是一种新奇的装饰,在这个案子。

标签: mysql performance query-optimization having


【解决方案1】:

查询需要重新制定需要添加索引。

计划一:

SELECT  ((tt.mean - 3.0E-4)/(tt.stddev/sqrt(tt.N))) as t,
        tt.strategyid, tt.mean, tt.stddev, tt.N, tt.kurtosis,
        s.strategyId
    FROM  ttest AS tt
    JOIN  strategies AS s  ON tt.strategyid = s.id
    WHERE  tt.dataset = 3
      AND  tt.patternclassid = 1
      AND  tt.exitclassid = 1
      AND  tt.N >= 300
      AND  ((tt.mean - 3.0E-4)/(tt.stddev/sqrt(tt.N))) >= 1.8

以及测试中的“复合”和“覆盖”指数。用这个替换你的ti4(使其“覆盖”):

INDEX(dataset, patternclassid, exitclassid,  -- any order
      N, strategyid)     -- in this order

B计划:

SELECT  ((tt.mean - 3.0E-4)/(tt.stddev/sqrt(tt.N))) as t,
        tt.strategyid, tt.mean, tt.stddev, tt.N, tt.kurtosis,
        ( SELECT s.strategyId 
              FROM strategies AS s
              WHERE s.id = tt.strategyid = s.id
        ) AS strategyId
    FROM  ttest AS tt
    WHERE  tt.dataset = 3
      AND  tt.patternclassid = 1
      AND  tt.exitclassid = 1
      AND  tt.N >= 300
      AND  ((tt.mean - 3.0E-4)/(tt.stddev/sqrt(tt.N))) >= 1.8

具有相同的索引。

不幸的是,t 的表达式需要重复。通过将其从 HAVING 移动到 WHERE,可以避免收集不需要的行,最终将它们丢弃。也许优化器会自动执行此操作。请提供EXPLAIN SELECT ...查看。

此外,尚不清楚这两种配方中的一种是否会比另一种运行得更快。

【讨论】:

  • 计划 A 需要 27 秒(几次尝试后 21 秒),计划 B 需要 29 秒(几次尝试后 23 秒)。与15分钟前的巨大差异!
  • @Markiemark - 感谢您的时间安排。 (我假设它是新指数?)
  • 是的,使用新索引。
【解决方案2】:

老实说,我从未见过HAVING 被这样使用; 20 多年以来,我一直认为它只能在GROUP BY 情况下使用!

无论如何,恕我直言,您在这里不需要它,正如 Rick James 指出的那样,您可以将其全部放在 WHERE 中。 稍微重写一下,我最终得到:

SELECT ((t.mean - 3.0E-4)/(t.stddev/sqrt(t.N))) as t, 
       t.strategyid, 
       t.mean, 
       t.stddev, 
       t.N, 
       t.kurtosis, 
       s.strategyId 
 FROM ttest t,
 JOIN strategies s
   ON s.id = t.strategyid =  
WHERE t.dataset=3 
  AND t.patternclassid="1" 
  AND t.exitclassid="1" 
  AND t.N>= 300 
  AND ((t.mean - 3.0E-4)/(t.stddev/sqrt(t.N))) >= 1.8
  

其中大部分我们确实可以预见一个合理的指数。最后一个计算问题仍然存在:

  AND ((t.mean - 3.0E-4)/(t.stddev/sqrt(t.N))) >= 1.8

然而,在我们开始之前:如果你忽略这个“公式”,有多少行? 100? 200?如果是这样,在 Rick James 的回答中预见的索引应该是足够的恕我直言。 如果它是 1000 或更多,那么问题就变成了:公式抛出了多少? 1%? 50% 99%?如果它再次处于低位,那么 Rick James 提出的索引就可以了。但是,如果您只需要保留一些,您可能需要进一步优化它并相应地索引。 根据您的解释,我了解到3.0E-4 是可变的,因此我们不能将其包含在索引中。所以我们需要提取我们可以提取的部分:

如果我的代数没有让我失望,你可以使用这样的公式:

 AND ((t.mean - 3.0E-4) / (t.stddev / sqrt(t.N))) >= 1.8
 AND ((t.mean - 3.0E-4) ) >=  1.8 * (t.stddev / sqrt(t.N))
 AND   t.mean - 3.0E-4    >= (1.8 * (t.stddev / sqrt(t.N)))
 AND          - 3.0E-4    >= (1.8 * (t.stddev / sqrt(t.N))) - t.mean 
 

所以查询变成:

SELECT ((t.mean - 3.0E-4)/(t.stddev/sqrt(t.N))) as t, 
       t.strategyid, 
       t.mean, 
       t.stddev, 
       t.N, 
       t.kurtosis, 
       s.strategyId 
 FROM ttest t,
 JOIN strategies s
   ON s.id = t.strategyid =  
WHERE t.dataset=3 
  AND t.patternclassid="1" 
  AND t.exitclassid="1" 
  AND t.N>= 300 
  AND (1.8 * (t.stddev / sqrt(t.N))) - t.mean <= -3.0E-4    

我不熟悉 mysql 但浏览文档应该可以在索引中包含“生成的列”。所以,我们将使用(1.8 * (t.stddev / sqrt(t.N)) - t.mean) 来做这件事。

您的索引字段因此变为:

dataset, paternclassid, exitclassid, N, (1.8 * (t.stddev / sqrt(t.N))) - t.mean)

请注意,系统将必须为您在表上执行的插入(并可能更新)每一行计算此值。但是,一旦出现(并被索引),它应该会使查询更快一些。

【讨论】:

  • 目前我想不出为什么我需要 HAVING。现在看来只是愚蠢。我喜欢你的公式操作。不幸的是,1.8 也可能会有所不同......但由于只有一些有用的变化,我想我会试一试。专门为“1.8”创建索引后,查询在10秒内完成。很好! (回答你的问题:结果的数量从几百到一万不等)
  • 制作表达式sargable 是一种有效的优化。您提到的代数重新表述没有实现这一点,因此它们无济于事。 (或者至少没有明显的数量。)
  • @RickJames 假设我们将(1.8 * (t.stddev / sqrt(t.N))) - t.mean) 设为计算列(或在 mysql-speak 中“生成”?)并索引该列,那么这怎么可能不可用呢?还需要什么? (我喜欢维基百科页面顺便说一句)
  • @deroby - 在这个在这种情况下,生成的索引列可能无济于事。已经有一个“范围”测试(N&gt;=300)。优化器不能在 INDEX 中使用 2 个范围测试。
  • @RickJames 那是有道理的,但另一方面它使索引“覆盖”,从而避免了大量的书签查找。也许只是 INCLUDEing 字段(甚至是公式?)可能就足够了。同样,我没有使用 MySQL 的经验,所以不完全确定这是否适用于此。根据@markiemark 的测试结果,计算字段确实降低了运行时间而不是没有它,所以必须有一些东西在起作用......SQL有时可能很奇怪=)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多