2

我有一个查询:

select SQL_NO_CACHE id from users
 where id>1 and id <1000
   and id in  ( select owner_id from comments and content_type='Some_string');

(请注意,它缺少用于我的狮身人面像索引的实际大型查询,代表问题) 此查询大约需要3.5 秒(从 id = 1..5000 修改范围使其大约15 秒)。

users 表有大约 35000 个条目,comments 表有大约 8000 个条目。

解释上面的查询:

explain select SQL_NO_CACHE id from users
        where id>1 and id <1000
          and id in  ( select distinct owner_id from d360_core_comments);

| 编号 | 选择类型 | 表| 类型 | 可能的键 | 关键 | key_len | 参考 | 行 | 额外 |


| 1 | 初级 | 用户 | 范围 | 初级 | 初级 | 4 | 空 | 1992 | 使用哪里;使用索引 |

| 2 | 依赖子查询 | d360_core_comments | 全部 | 空 | 空 | 空 | 空 | 6901 | 使用哪里;使用临时 |

这里的单个子查询(select owner_id from d360_core_comments where content_type='Community20::Topic';)需要将近 0.0 秒。

但是,如果我在 owner_id,content_type 上添加索引,(请注意此处的顺序)

create index tmp_user on d360_core_comments (owner_id,content_type);

我的子查询在 ~0.0 秒内按原样运行,没有使用索引:

mysql> 解释 select owner_id from d360_core_comments where content_type='Community20::Topic';

| 编号 | 选择类型 | 表| 类型 | 可能的键 | 关键 | key_len | 参考 | 行 | 额外 |


| 1 | 简单 | d360_core_comments | 全部 | 空 | 空 | 空 | 空 | 6901 | 使用位置 |

然而,现在我的主要查询 ( select SQL_NO_CACHE id from users where id>1 and id <1000 and id in ( select owner_id from d360_core_comments where content_type='Community20::Topic');) 现在在 ~0 秒内运行,解释如下:

mysql> 解释 select SQL_NO_CACHE id from users where id>1 and id <1000 and id in (select owner_id from d360_core_comments where content_type='Community20::Topic');

| 编号 | 选择类型 | 表| 类型 | 可能的键 | 关键 | key_len | 参考 | 行 | 额外 |


| 1 | 初级 | 用户 | 范围 | 初级 | 初级 | 4 | 空 | 1992 | 使用哪里;使用索引 |

| 2 | 依赖子查询 | d360_core_comments | 索引子查询 | tmp_user | tmp_user | 5 | 功能 | 34 | 使用位置 |

所以我的主要问题是:

  • 如果在我的子查询中使用的表上定义的索引没有在我的实际子查询中使用,那么它如何在这里优化查询?
  • 以及为什么当实际的子查询和主查询独立地更快时,第一个查询首先要花费这么多时间?
4

4 回答 4

3

在没有索引的完整查询中似乎发生的是 MySQL 将构建(某种)子查询生成的所有 owner_id 的临时表。然后对于 users 表中与 id 约束匹配的每一行,将在此临时构造中执行查找。目前尚不清楚开销是否是创建临时构造,或者查找是否实现次优(以便所有元素与外部查询的每一行线性匹配。

当您在 owner_id 上创建索引时,当您只运行子查询时这不会改变任何内容,因为它对 owner_id 没有任何条件,索引也不会覆盖 content_type 列。

但是,当您使用索引运行完整查询时,会有更多可用信息,因为我们现在有来自外部查询的值,这些值应该与索引覆盖的 owner_id 匹配。所以现在的执行似乎是运行外部查询的第一部分,并为每个匹配的行按 owner_id 进行索引查找。换句话说,一个可能的执行计划是:

From Index-Users-Id Get all id matching id>1 and id <1000
For Each Row
    Include Row If Index-Comment-OwnerId Contains row.Id
                   And Row Matches content_type='Some_string'

因此,在这种情况下,运行 1000 个(我假设)索引查找的工作比构建 8000 个可能的 owner_id 的临时结构要快。但这只是一个假设,因为我不太了解 MySQL。

于 2013-01-30T19:17:46.087 回答
2

如果您阅读 MySQL 参考手册的这一部分:使用Strategy优化子查询EXISTS,您会看到查询优化器将您的子查询条件从:

id in ( select distinct owner_id
          from d360_core_comments
         where content_type='Community20::Topic')

进入:

exists ( select 1
           from d360_core_comments
          where content_type='Community20::Topic'
            and owner_id = users.id )

(owner_id, content_type)这就是为什么当子查询作为独立查询测试时索引没有用,但在考虑转换后的子查询时它很有用。

于 2013-02-01T18:15:00.343 回答
1

你应该知道的第一件事是 MySQL 不能优化依赖子查询,这是长期以来众所周知的 MySQL 缺陷,将在 MySQL 6.x 中修复(只是谷歌“mysql依赖子查询”和你等着瞧)。也就是说,子查询基本上是针对users表中的每个匹配行执行的。由于您有一个附加条件,因此总执行时间取决于该条件。解决方案是用连接替换子查询(您期望从 MySQL 获得的优化)。

其次,您的子查询中存在语法错误,并且我认为 owner_id 存在条件。因此,当您在其上添加索引时,owner_id它被使用,但对于第二个条件是不够的(因此 no using index),但为什么根本没有提到EXPLAIN是一个问题(我认为是因为 上的条件users.id

第三,我不知道你为什么需要那个id > 1 and id < 5000条件,但你应该明白,这两个范围条件需要非常准确、有时不明显且依赖数据的索引方法(与相等比较条件相反),如果你真的不需要它们,仅用于不明白为什么查询需要这么长时间,那么这是一个坏主意,它们不会发光。

如果需要条件并且索引owner_id仍然存在,我将重写查询如下:

SELECT id 
FROM (
  SELECT owner_id as id
  FROM comments
  WHERE owner_id < 5000 AND content_type = 'some_string'
) as ids
JOIN users ON (id)
WHERE id > 1;

PS 上的复合索引(content_type, owner_id)甚至更适合查询。

于 2013-02-01T11:30:41.103 回答
0

第 1 步:使用id BETWEEN x AND y而不是id >= x AND id <= y. 您可能会发现一些令人惊讶的收获,因为它的索引更好。

第 2 步:调整您的 sub-SELECT进行过滤,这样就不必重复两次:

SELECT SQL_NO_CACHE id 
  FROM users
 WHERE id IN (SELECT owner_id 
                FROM comments
               WHERE content_type='Some_string' 
                 AND owner_id BETWEEN 1 AND 1000);

你的陈述似乎有几个错误。例如,您正在选择 2 到 999,可能两端都相差一个,并且子选择无效。

于 2013-01-28T18:08:05.223 回答