Mysql 在向查询添加联接和订购方式后,性能意外提高

Mysql 在向查询添加联接和订购方式后,性能意外提高,mysql,sql,performance,indexing,Mysql,Sql,Performance,Indexing,我有以下人表: | Id | FirstName | Children | |----|-----------|----------| | 1 | mark | 4 | | 2 | paul | 0 | | 3 | mike | 3 | 注意:我在FirstName中有一个非唯一索引,在Children中有另一个索引 我需要得到每个有孩子的人的前10000个名字和孩子数量。所以我决定采用这种解决方案: SELECT

我有以下表:

| 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 |
在我看来,范围告诉我索引正在被扫描并与一个值进行比较(在本例中,这是子项>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        |
| ID |选择|类型|类型|可能的|键|键|列|参考|行|额外|
|----|-------------|------------|--------|---------------|----------|---------|--------|---------|---------------------------------|
|1 | PRIMARY | | ALL |(null)|(null)|(null)|(null)|(null)| 10000 |使用临时命令;使用文件排序|
|1 | PRIMARY | p | eq | ref | PRIMARY | PRIMARY | 4 | s.id | 1 ||
|2 |派生|人|范围|儿童|儿童| 4 |(空)| 2687462 |使用where;使用索引|
令我惊讶的是,这个查询的执行速度比第一个查询快了几倍。但是,限制X的增量越大,此差异就越大(例如:对于限制1000000,10000第二个查询仍在1秒以下,第一个查询超过20秒)。这使我想到以下问题:

  • MySQL处理第一个查询和第二个查询的方式有什么不同
  • 有没有办法提示MySQL以执行第二个查询的方式执行第一个查询
  • 公平地说,从中得到的教训是,每当我想要获取一个不属于所使用索引的值时,双order by和join是正确的方法吗
  • 补充说明:

    • (如果有区别的话)
    • 注意,我使用SQL\u NO\u缓存运行查询
    • MySQL版本:5.5.37

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

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


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

    我似乎在《高性能MySQL-B.Schwartz》一书中详细地发现了这个问题

    在第193页中,有一些高偏移量(即限制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>);
    
    从配置文件内部连接中选择(
    从配置文件中选择
    其中x.sex='M'按评级限制排序100000,10
    )使用()作为x;
    
    因此,关键因素似乎是使用(现有)主键作为内部查询的覆盖索引

    因此,回答我自己的问题:

  • MySQL处理第一个查询和第二个查询的方式有什么不同

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

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

    显然不是。您必须再次重写整个查询

  • 公平地说,从中得到的教训是,每当我想要获取一个不属于所使用索引的值时,双order by和join是正确的方法吗

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


  • 使用索引提示
    从表1中选择*使用索引(col1_index),其中col1=1
    对于第一个查询,实际上,我已经尝试过一个类似的查询,强制它转到JOIN的主键和orderby的子索引。但我也没有运气。我也试过了你提到的那一个,但我也没有任何运气。试着使用并再次运行第一个查询更新你的统计数据阅读这家伙的博客,他提出了一些提高性能的方法我同意所有这些观点。覆盖指数应该相当快。但是,children字段是非常动态的,因此性能影响将出现在插入/更新时。不管怎样,我不会再加一个