将 JOIN 和 ORDER BY 添加到查询后,性能意外提升

Posted

技术标签:

【中文标题】将 JOIN 和 ORDER BY 添加到查询后,性能意外提升【英文标题】:Unexpected performance boost after adding JOIN and ORDER BY to query 【发布时间】:2014-06-23 23:52:51 【问题描述】:

我有以下 人物 表:

| Id | FirstName | Children |
|----|-----------|----------|
|  1 |      mark |        4 |
|  2 |      paul |        0 |
|  3 |      mike |        3 |

请注意,我在 FirstName 中有一个非唯一索引,在 Children 中有另一个索引。

我需要获取每个有孩子的人的前 10000 个名字和孩子数量。所以我决定采用这个解决方案:

SELECT firstName, children FROM people
WHERE children > 0
ORDER BY children DESC
LIMIT 0, 10000

问题是从包含 260 万条记录的表中返回结果需要 4 秒。这是解释:

| ID | SELECT_TYPE | TABLE  | TYPE  | POSSIBLE_KEYS | KEY      | KEY_LEN | REF    |       ROWS | EXTRA       |
|----|-------------|--------|-------|---------------|----------|---------|--------|------------|-------------|
|  1 |      SIMPLE | people | range | children      | children |       4 | (null) |    2677610 | Using where |

正如我所见,范围告诉我正在扫描索引并与一个值进行比较(在本例中为 children > 0)。我会说这应该足够快。然后,我的猜测是,在获取所有匹配的索引元素后,DBMS 通过将索引中的值与表中的值进行内部连接,从表中获取 firstName

如果我将上一段翻译成 SQL,我会得到如下内容:

SELECT firstName, children FROM people
JOIN (
    SELECT id FROM people
    WHERE children > 0
    ORDER BY children DESC
    LIMIT 0, 10000
) s
ON people.id = s.id
ORDER BY children DESC

上一条SQL语句的解释是:

| ID | SELECT_TYPE | TABLE      | TYPE   | POSSIBLE_KEYS | KEY      | KEY_LEN | REF    |    ROWS | EXTRA                           |
|----|-------------|------------|--------|---------------|----------|---------|--------|---------|---------------------------------|
|  1 |     PRIMARY | <derived2> | ALL    | (null)        | (null)   |  (null) | (null) |   10000 | Using temporary; Using filesort |
|  1 |     PRIMARY | p          | eq_ref | PRIMARY       | PRIMARY  |       4 | s.id   |       1 |                                 |
|  2 |     DERIVED | people     | range  | children      | children |       4 | (null) | 2687462 | Using where; Using index        |

令我惊讶的是,这个查询的执行速度比第一个查询几倍。但是,我增加 LIMIT X 的次数越多,这种差异就越大(例如:对于 LIMIT 1000000, 10000,第二个查询仍然不到 1 秒,第一个查询超过 20秒)。这导致我提出以下问题:

    mysql 处理第一个查询与第二个查询有何不同? 有没有办法提示 MySQL 以执行第二个查询的方式执行第一个查询? 可以公平地说,从中吸取的教训是,每当我想获取不属于所使用索引的值时,双排序和连接是正确的方法吗?

补充说明:

SQLFiddle(如果有什么不同的话) 请注意,我正在使用 SQL_NO_CACHE 运行查询 MySQL 版本:5.5.37

【问题讨论】:

第一次查询使用索引提示SELECT * FROM table1 USE INDEX (col1_index) WHERE col1=1;dev.mysql.com/doc/refman/5.0/en/index-hints.html 实际上,我已经尝试过类似的方法,方法是强制它用于 JOIN 的主键和 ORDER BY 的子索引。但我也没有运气。我也试过你提到的那个,我也没有运气。 尝试使用Analyze Table 更新您的统计信息并再次运行第一个查询 阅读这个人的博客mysqlperformanceblog.com/2006/09/01/…他提出了一些提高性能的方法 【参考方案1】:

我很确定您可以通过在children, firstname 上建立索引来修复第一个查询的性能。这是查询的覆盖索引,因此它应该消除对数据页的访问。

第一个执行计划表明索引正用于wherelimit 最后应用,因此它似乎在应用limit 之前为所有 行获取firstname。这看起来很奇怪,但与您所看到的性能一致。

在第二个版本中,正在读取 10000 个 ID。假设它们是主键,那么数据页查找应该非常快——并且由限制明确控制。这可能说明了为什么这个版本更快,尽管它看起来有点神秘。不过,大多数情况下,我希望children, firstname 上的索引能够改进第一个版本的查询。

【讨论】:

我同意所有这些观点。覆盖指数应该很快。但是,children 字段是非常动态的,因此性能影响将出现在插入/更新时间。无论如何,如果我已经有一个没有索引的更快查询,我不会添加索引来使第一个查询工作得更快。我也同意firstname 在限制之前为所有行提取,这是主要问题:如果它真的使用where 的索引,那么为什么它会去表中获取值(firstnames)超出 (children) 限制范围?闻起来像是限制条款的糟糕实现。 @MostyMostacho 。 . . limit 子句应用在其他所有内容之后,包括 order by。当有其他列时,似乎是一种糟糕的排序方法选择。换句话说,它似乎是在 after 拾取额外的列而不是之前订购。这作为一种通用方法是有意义的(因为可以在order by 中使用额外的列)。但是当有索引时很容易优化。【参考方案2】:

我似乎在High Performance MySQL - B. Schwartz一书中发现了这个问题。

在第 193 页中有一些高偏移量示例(即 LIMIT 1000000, 10)查询以及一些改进它们的替代方法。之后我引用:

另一个优化此类查询的好策略是使用延迟连接,这也是我们使用覆盖索引仅检索您最终要检索的行的主键列的术语。然后,您可以将其连接回表以检索所有所需的列。这有助于最大限度地减少 MySQL 必须做的收集数据的工作量,而这些数据只会被丢弃。这是一个需要(性别,评级)索引才能有效工作的示例:

SELECT <cols> FROM profiles INNER JOIN (
    SELECT <primary key cols> FROM profiles
    WHERE x.sex='M' ORDER BY rating LIMIT 100000, 10
) AS x USING(<primary key cols>);

因此,关键因素似乎是使用(现有)主键作为内部查询的覆盖索引。

所以回答我自己的问题:

    MySQL 处理第一个查询与第二个查询有何不同?

    似乎第一个获取的不仅仅是偏移量之前所有行的主键。

    有没有办法提示 MySQL 以执行第二个查询的方式执行第一个查询?

    显然不是。您将不得不再次重写整个查询。

    可以公平地说,从中吸取的教训是,每当我想获取一个不属于所使用索引的值时,双重排序和连接是正确的方法吗?

    看起来是这样。但是,对于较小的偏移量,使用延迟连接可能不会提高性能。

【讨论】:

以上是关于将 JOIN 和 ORDER BY 添加到查询后,性能意外提升的主要内容,如果未能解决你的问题,请参考以下文章

使用 JOIN 优化 SQL 查询的 ORDER BY 和 WHERE

在 sql 查询中使用 LIMIT 和 ORDER BY 和 LEFT JOIN

使用 LEFT JOIN 和 ORDER BY...LIMIT 查询慢,使用 Filesort

Django order_by 导致 LEFT JOIN

使用 ORDER BY 和 INNER JOIN 优化 MySQL 查询(选择用户关注的位置)

如何优化这个简单的 JOIN+ORDER BY 查询?