【问题标题】:Huge performance difference: "WHERE x IN (...values...)" vs. "WHERE x IN (<subquery>)"巨大的性能差异:“WHERE x IN (...values...)”与“WHERE x IN (<subquery>)”
【发布时间】:2016-12-06 12:13:43
【问题描述】:

我想在表 T 中查询由某列 x 标识的行。应该返回行的x 的值由表U 上的一些子查询确定,该表携带列a(一个短字符串)和b(一个短字符串)上的实际过滤条件时间戳值,寻找范围)。

SELECT * FROM T
    WHERE x IN (
        SELECT x FROM U WHERE a = ? AND b BETWEEN ? AND ?
    )
    ORDER BY x, y

TU 两个表都非常大(在 1M..100M 行的范围内)。他们的定义在问题的最后。在典型的用例中,x 的大约 100 个不同值的结果集中大约有 500 到 1000 行。 x 的这些值不是连续的,而且确实非常“随机”。

这个查询需要相当长的时间(在 0.5 到 2 秒的范围内),尽管使用了两个表 TU 的正确索引,并且实际上只有少数行正在“检查”(大约 1000) 根据慢查询日志。

索引定义为:

  • U 上的索引idx_u 涵盖WHERE 子句(ab)中的列。
  • T 上的索引idx_t 涵盖列x(用于查找行)和y(用于二级排序标准)。李>

上述查询的EXPLAIN 是:

id  select_type  table  type   possible_keys  key    key_len  ref   rows  Extra     
1   SIMPLE       U      range  idx_u          idx_u  24       NULL  107   Using where; Using index; Using temporary; Using filesort
1   SIMPLE       T      ref    idx_t          idx_t  8        U.x   4     NULL

我还尝试了JOIN 而不是子查询:

SELECT * FROM T
    JOIN U ON T.x = U.x
    WHERE a = ? AND b BETWEEN ? AND ?
    ORDER BY T.x, T.y

EXPLAIN 的含义与上述完全相同。


但是,当单独执行子查询并逐字列出x 的值时,两个查询一起花费的时间要少得多(在 10 到 20 毫秒的范围内)。此外,他们的EXPLAIN 看起来也不同。

第一个查询(原来是子查询):

SELECT x FROM U WHERE a = ? AND b BETWEEN ? AND ?

它的EXPLAIN 匹配上面的第一个条目:

id  select_type  table  type   possible_keys  key    key_len  ref   rows  Extra     
1   SIMPLE       U      range  idx_u          idx_u  24       NULL  107   Using where; Using index; Using temporary; Using filesort

现在是第二个查询:

SELECT * FROM T
    WHERE x IN (
        3917,8525,13149,17729,22355,26908,31457,36053,40663,45250 -- ... (64 values here)
    )
    ORDER BY x, y

解释为

id  select_type  table  type   possible_keys  key    key_len  ref   rows  Extra     
1   SIMPLE       T      range  idx_t          idx_t  8        U.x   436   Using index condition

现在我注意到在原始(组合)查询中,对于表T,行数的猜测只有 4,这太低了。此外,T 的类型是 ref,而在单独的查询中(x 具有显式值),它是 range

T 中查找错误的行号是否会导致查询以次优方式执行? ref 类型是性能差异大的原因吗?

最后,要解决这个问题,我可以强制range 吗?请注意,这不是强制使用索引,因为它似乎已经被使用了。


编辑:表是这样创建的:

CREATE TABLE `U` (
  `x` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `a` varchar(20) CHARACTER SET ascii NOT NULL,
  `b` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  -- (more columns irrelevant here)
) ENGINE=InnoDB;

ALTER TABLE `U`
  ADD PRIMARY KEY (`x`),
  ADD KEY `idx_u` (`a`,`b`) USING BTREE;
CREATE TABLE `T` (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `x` bigint(20) UNSIGNED NOT NULL,
  `y` int(11) UNSIGNED NOT NULL,
  -- (more columns irrelevant here)
) ENGINE=InnoDB;

ALTER TABLE `T`
  ADD PRIMARY KEY (`id`),
  ADD KEY `idx_t` (`x`,`y`) USING BTREE;

【问题讨论】:

  • 该字段涉及什么数据类型,您能告诉我们创建索引吗?
  • @JuanCarlosOropeza 涉及的字段是整数类型(准确地说是BIGINT)。我编辑了问题,希望您在底部找到相关信息。
  • 尝试为(a, b, x) 创建索引,这样就不必查找x,并且您是否传递了正确的类型来与b 进行比较?
  • 你说其他列无关紧要。好吧,他们不是——因为你这样做了SELECT *。如果您选择TEXTBLOB 列,这很重要。因此,如果可以减少 * 以排除此类列,它可能运行得更快很多。请提供更多详细信息。

标签: mysql sql query-optimization


【解决方案1】:

如果您将查询更改为使用 WHERE EXISTS 而不是 IN 运算符,并且您已经尝试过使用 JOIN 转换会怎样

SELECT * FROM T
    WHERE EXISTS (SELECT 1 FROM U WHERE T.x = U.x
                  AND a = ? 
                  AND b BETWEEN ? AND ?
    )
    ORDER BY x, y;

奇怪的事实是,我看到您在 x,y(ADD KEY idx_t (x,y) USING BTREE) 上有一个索引,但您的解释显示它正在执行文件排序 Using where; Using index; Using filesort

【讨论】:

  • 嗯,从没见过这样的东西。但是,我刚刚尝试对T 进行了全面扫描,这需要更多时间(分钟)。子查询在EXPLAIN 中被标识为类型“DEPENDENT SUBQUERY”。
  • 评论您的编辑:好吧,文件排序仅发生在U,其中使用索引idx_u (a,b)。我不知道 EXPLAIN 中的“filesort”到底是什么意思...
  • EXPLAIN 中“filesort”的定位有时具有欺骗性;不要相信它在哪条线上。它的存在可能表明ORDER BY 不能(或选择不)使用INDEX(x,y) 请提供EXPLAIN,并在执行时明确存在哪些索引。
【解决方案2】:

你可以试试加入这样的

SELECT T.* FROM T
         JOIN U on T.x = U.x
WHERE U.a = ? AND U.b BETWEEN ? AND ?
ORDER BY T.x, T.y

【讨论】:

  • 对不起,我之前没有提到,但最初我尝试使用 JOIN。与子查询完全相同的结果。 :(
  • 这个查询一定要花这么多时间@leemes
  • 我不明白你的意思。 JOIN 和嵌套查询都需要大约 1 或 2 秒。两个单独的查询(子查询 + 带有显式 x 值的外部查询)总共需要 10 到 20 毫秒
猜你喜欢
  • 2019-03-09
  • 2022-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-27
  • 2021-07-25
相关资源
最近更新 更多