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

时间:2014-06-23 23:52:52

标签: mysql sql performance indexing

我有以下表:

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

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

我需要获得前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 越多,这个差异变得越大(EG:对于 LIMIT 1000000,10000 ,第二个查询仍然不到1秒,第一个查询超过20秒)。这引出了以下问题:

  1. MySQL处理与第二个查询不同的第一个查询的方式是什么?
  2. 有没有办法提示MySQL以执行第二个查询的方式执行第一个查询?
  3. 可以公平地说,从中学到的教训是,每当我想获取一个不属于所使用索引一部分的值时,double order by和join是正确的方法吗?
  4. 附加说明:

    • SQLFiddle(如果有任何区别的话)
    • 注意我使用 SQL_NO_CACHE
    • 运行查询
    • MySQL版本:5.5.37

2 个答案:

答案 0 :(得分:3)

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

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

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

答案 1 :(得分:0)

似乎我在 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>);

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

回答我自己的问题:

  1. MySQL处理与第二个查询不同的第一个查询的方式是什么?

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

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

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

  3. 可以公平地说,从中学到的教训是,每当我想获取一个不属于正在使用的索引一部分的值时,双顺序by和join是正确的方法吗? / p>

    看起来如此。但是,对于小偏移,使用延迟连接可能不会提高性能。