为啥 MIN() 查询会比 ORDER BY X LIMIT 1 慢?

Posted

技术标签:

【中文标题】为啥 MIN() 查询会比 ORDER BY X LIMIT 1 慢?【英文标题】:Why would MIN() query be slower than ORDER BY X LIMIT 1?为什么 MIN() 查询会比 ORDER BY X LIMIT 1 慢? 【发布时间】:2017-09-21 11:04:22 【问题描述】:

首先,我看到了:Why is MAX() 100 times slower than ORDER BY ... LIMIT 1?

看起来是同一个问题,但问题是缺少索引。所以让我澄清一下我的情况。


为了概括,我将简化我的两个查询:

-- min:
SELECT min(id) FROM my_table WHERE s_time >= now() - INTERVAL 14 DAY;
-- exec time: ~0.260 s

-- order-limit:
SELECT id FROM my_table WHERE s_time >= now() - INTERVAL 14 DAY ORDER BY s_time, id LIMIT 1;
-- exec time: ~0.060 s

这里,id 是主键,s_time 是索引时间戳

运行explain format=json,表明这两个查询之间的区别在于order-limit 版本有一个ordering_operation 表示using_filesort: false。两者都显示相同的query_cost 分析。

现在,我对此的理解是,如果列被索引,那么它在 btree 中排序。并且,这些索引条目具有有关主键的信息。找到第一个(限制 1)应该是对 btree 的简单遍历,而且很快。

但是,执行MIN(primary_key) FROM foo WHERE indexed_entry > bar,应该以相同的方式处理。这仅仅是innoDb优化不佳的一个案例吗?


如果使用 LIMIT 有一个特殊的优化案例来分析条目数量的内存需求,并且如果可能使用优先级队列而不是快速排序,那么 MIN() 不应该属于使用 @987654331 的相同用例@?


explain区别:

min-case:


  "query_block": 
    "select_id": 1,
    "cost_info": 
      "query_cost": "91987.68"
    ,
    "table": 
      "table_name": "my_table",
      "access_type": "range",
      "possible_keys": [
        "s_time"
      ],
      "key": "s_time",
      "used_key_parts": [
        "s_time"
      ],
      "key_length": "4",
      "rows_examined_per_scan": 229128,
      "rows_produced_per_join": 229128,
      "filtered": "100.00",
      "using_index": true,
      "cost_info": 
        "read_cost": "46162.08",
        "eval_cost": "45825.60",
        "prefix_cost": "91987.68",
        "data_read_per_join": "104M"
      ,
      "used_columns": [
        "id",
        "s_time"
      ],
      "attached_condition": "(`db`.`my_table`.`s_time` >= <cache>((now() - interval 14 day)))"
    
  

order-limit


  "query_block": 
    "select_id": 1,
    "cost_info": 
      "query_cost": "92215.71"
    ,
    "ordering_operation": 
      "using_filesort": false,
      "table": 
        "table_name": "my_table",
        "access_type": "range",
        "possible_keys": [
          "s_time"
        ],
        "key": "s_time",
        "used_key_parts": [
          "s_time"
        ],
        "key_length": "4",
        "rows_examined_per_scan": 229696,
        "rows_produced_per_join": 229696,
        "filtered": "100.00",
        "using_index": true,
        "cost_info": 
          "read_cost": "46276.51",
          "eval_cost": "45939.20",
          "prefix_cost": "92215.71",
          "data_read_per_join": "105M"
        ,
        "used_columns": [
          "id",
          "s_time"
        ],
        "attached_condition": "(`db`.`my_table`.`started_time` >= <cache>((now() - interval 14 day)))"
      
    
  


有趣的相关文档:https://dev.mysql.com/doc/dev/mysql-server/8.0.0/filesort_8cc.html 中的方法 bool check_if_pq_applicable()

DESCRIPTION 给定这样的查询: SELECT ... FROM t ORDER BY a1,...,an LIMIT max_rows;此函数测试是否应使用优先级队列来保留结果。必要条件是:

估计它实际上比合并排序便宜 足够的内存来存储记录。

【问题讨论】:

不介意解释否决票。如果它应该属于 dba,那么只需投票关闭并保留它。 你搞错时间了吗? (order by (0.26s) > min (0.06s),但我认为反之亦然)。除此之外:您的查询做了两件完全不同的事情(并且可以返回不同的值):第一个取满足条件的第一个日期(通过索引),然后停止(限制)。它可以与min id 相同(特别是如果您按顺序添加数据),但可能是任何其他id(比min id 的时间短)。 min-query 以相同的条目/id 开始,但随后必须检查之后的每个条目以查看是否有较低的 id(时间更长)。 @Solarflare 我确实混淆了时代。感谢您发现它。固定的。至于你的解释,确实有道理。 【参考方案1】:

他们做不同的事情,因此一个人必须更加努力。

SELECT  min(id)
    FROM  my_table
    WHERE  s_time >= now() - INTERVAL 14 DAY;

搜索过去两周内的所有项以找到最低的idINDEX(s_time, id) 会有所帮助。

SELECT  id
    FROM  my_table
    WHERE  s_time >= now() - INTERVAL 14 DAY
    ORDER BY  s_time, id
    LIMIT  1;

如果你有INDEX(stime, id), then it will look at only one row -- the first one of 14 days ago. No scan. No checking to see if it is the smallestid`。

注意:如果您有PRIMARY KEY(id), INDEX(stime),则该索引实际上是(stime, id)

由于您可能stime 的顺序插入了行,因此结果可能是相同的。但优化器没有知道这一点。

【讨论】:

以上是关于为啥 MIN() 查询会比 ORDER BY X LIMIT 1 慢?的主要内容,如果未能解决你的问题,请参考以下文章

为啥删除 ORDER BY 会显着加快此查询的速度?

SQL - GROUP BY和ORDER BY MIN

MYSQL:为啥两个几乎相同的查询的 ORDER BY 时间不同

为啥 MySQL 查询在使用 LIMIT 和 Order BY 时会变慢?

允许 null 的 ORDER BY 列很慢。为啥?

为啥主键上的“order by”会更改查询计划,从而忽略有用的索引?