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

Mos*_*cho 5 mysql sql indexing performance

我有以下人员表:

| Id | FirstName | Children |
|----|-----------|----------|
|  1 |      mark |        4 |
|  2 |      paul |        0 |
|  3 |      mike |        3 |
Run Code Online (Sandbox Code Playgroud)

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

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

SELECT firstName, children FROM people
WHERE children > 0
ORDER BY children DESC
LIMIT 0, 10000
Run Code Online (Sandbox Code Playgroud)

问题是从一个包含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 |
Run Code Online (Sandbox Code Playgroud)

在我看来,范围告诉我正在扫描索引并与一个值进行比较(在这种情况下,这是孩子> 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
Run Code Online (Sandbox Code Playgroud)

以前的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        |
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是,此查询的执行速度比第一个几倍.但是,我增加LIMIT X越多,这个差异变得越大(EG:对于LIMIT 1000000,10000,第二个查询仍然低于1秒,第一个查询超过20秒).这引出了以下问题:

  1. MySQL以什么方式处理与第二个不同的第一个查询?
  2. 有没有办法提示MySQL执行第一个查询的方式执行第二个查询?
  3. 可以公平地说,从中学到的教训是,每当我想获取一个不属于正在使用的索引的值时,双顺序和连接是正确的方法吗?

补充说明:

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

Mos*_*cho 0

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

\n\n

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

\n\n
\n

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

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

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

\n\n

所以回答我自己的问题:

\n\n
    \n
  1. MySQL 处理第一个查询与第二个查询的方式有何不同?

    \n\n

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

  2. \n
  3. 有没有什么方法可以提示 MySQL 以执行第二个查询的方式执行第一个查询?

    \n\n

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

  4. \n
  5. 公平地说,从中吸取的教训是,每当我想要获取不属于正在使用的索引的值时,双重 order by 和 join 是正确的方法吗?

    \n\n

    所以它看起来。但是,对于较小的偏移量,使用延迟连接可能不会带来性能提升。

  6. \n
\n