【问题标题】:Mysql ORDER BY with compound PKMysql ORDER BY 与复合 PK
【发布时间】:2019-07-15 09:18:56
【问题描述】:

我有一张桌子

CREATE TABLE price(
  product_id int,
  category_id int,
  epoch_id int,
  name varchar, 
  price decimal(10),
  add constraint primary key (product_id, category_id, epoch_id)
);

我想选择类别中产品的所有价格,但要选择所有时期:

SELECT * FROM prices where category_id = 1 ORDER BY product_id, category_id, epoch_id;

但我担心ORDER BY 将无法使用主键并且会占用太多资源来对行进行排序(因为我指定了category_id = 1,它在索引中排在第二位)

我不想更改索引中的列顺序或创建一个新的。我想了解一下,MySQL 是否能够使用聚集索引来快速执行排序。

更新: 我已经生成了大约 100,000 行,这就是我所拥有的:

explain SELECT * FROM price where category_id = 1 ORDER BY category_id, product_id, epoch_id;

id  select_type table   partitions  type    possible_keys   key key_len ref rows    filtered    Extra
1   SIMPLE  price       index       PRIMARY 12      97739   10  Using where

explain SELECT * FROM price where category_id = 1 ORDER BY category_id, epoch_id;
id  select_type table   partitions  type    possible_keys   key key_len ref rows    filtered    Extra
1   SIMPLE  price       ALL                 97739   10  Using where; Using filesort


explain SELECT * FROM price where category_id = 1 ORDER BY category_id, epoch_id, product_id;
id  select_type table   partitions  type    possible_keys   key key_len ref rows    filtered    Extra
1   SIMPLE  price       ALL                 97739   10  Using where; Using filesort

explain SELECT * FROM price where category_id = 1 ORDER BY product_id, epoch_id, category_id;
id  select_type table   partitions  type    possible_keys   key key_len ref rows    filtered    Extra
1   SIMPLE  price       index       PRIMARY 12      97739   10  Using where

explain SELECT * FROM price where category_id = 1 ORDER BY product_id, epoch_id;
id  select_type table   partitions  type    possible_keys   key key_len ref rows    filtered    Extra
1   SIMPLE  price       index       PRIMARY 12      97739   10  Using where

所以现在我有几个问题:

  1. 为什么product_id, epoch_id, category_id 不使用文件排序,虽然顺序与PK 顺序相矛盾? - 是不是因为category_id受到WHERE子句的限制,而product, epoch的顺序被PK保留了?

  2. 为什么product_id, epoch_id 不需要文件排序,而category_id, epoch_id 需要? - 其实同样的原因,product_id, epoch_id 是从 PK 中保留下来的

  3. 实际上category_id 确实很重要,我们可以从ORDER BY 中消除它。

那么,是不是说MySQL会遍历聚集索引,取回所有默认排序的行,然后就不需要重新排序了?

【问题讨论】:

  • 基于您的主键索引,基于类别 id 的 where 子句不能使用索引 .. .. 如果您对此确实有性能问题,您必须考虑添加一个新索引
  • 运行EXPLAIN ... 并在此处发布结果。他们会清楚地向您解释运行查询时会发生什么。
  • @MadhurBhaiya,我目前没有足够的数据(只有几行),所以执行计划不会太现实。我对这种理论上的可能性更感兴趣。我的意思是,当我们找到所有 ROWID 时,我们是要随机读取它还是可以直接读取它,因为它完全按 PK 字段排序(并且所有这些主要字段都在查询中选择)。

标签: mysql sql-order-by query-optimization


【解决方案1】:

您的问题

我在EXPLAINs 中看到了混乱。有人说“全部”;有人说“索引..PRIMARY”。好吧,对于 InnoDB,它们实际上是相同的。 PRIMARY KEY 与数据聚集在同一个 B+Tree 中。

EXPLAIN 是在 InnoDB 之前的日子里写回的,主要用于 MyISAM,它没有集群 PK。)

EXPLAIN 不够详细,无法明确回答您的问题。 EXPLAIN FORMAT=JSON 更好,但可能还不够清楚。

至于为什么“filesort”不见了……想想吧。如果category 是常量,那么您实际上需要按(product_id, epoch_id) 排序的内容。这就是该表子集的排序顺序。如果您尝试过任何其他组合(例如,首先使用epoch_id),则需要对其进行排序。您的第 2 和第 3 案例会这样做(在忽略常量 category_id 之后)。

对于 Q3:是的,相当于 ORDER BY product_id, category_id, epoch_id。

“那么,是不是说MySQL会遍历聚集索引,取回所有默认排序的行,这样就不需要重新排序了?” - 是的。而“文件排序”是一个准确(但不完整)的指示。

GROUP BY x ORDER BY b 的情况下需要 2 种排序,但 EXPLAIN 只显示一次。 (EXPLAIN FORMAT=JSON 确实提供了详细信息。)

让我讨论一下这个查询:

SELECT  *
    FROM  prices
    where  category_id = 1
    ORDER BY  product_id, category_id, epoch_id;

优化器有两种方法来处理它。

  • 专注于过滤 (where category_id = 1),希望符合该限制的行数不多。
  • 关注ORDER BY,希望避免排序所节省的成本比遍历整个表进行过滤所节省的成本更多。

当出现这样的两种情况时,它会收集一些统计信息(可能不太完美),以便对执行查询的方式做出有根据的猜测。也许主要统计数据(在您的示例中)是表中有多少百分比具有category=1

假设只有少数行有category=1。那么这是最优的:

PRIMARY KEY(category_id, product_id, epoch_id)

在这种情况下,PK 的“集群”特性将允许通过仅定位和读取行 category=1(加上一个额外的行来知道它已停止)来执行查询。 B+Tree 对于定位第一个这样的行和扫描所有这样的行都很有效。

或者,假设 PK 不能更改。然后可以使用辅助INDEX(category_id)。这会在 B+Tree 中扫描该索引并(一一)跳转到数据以查找行。

使用任一索引,此案例将以对找到的行进行排序结束。

假设只有大量行有category=1。那么这是最优的:

PRIMARY KEY(product_id, category_id, epoch_id)

这样,它可以避免排序(又名“文件排序”)。但是它会读取所有行,跳过没有category=1 的行。

如果您无法更改 PK,那么辅助 INDEX(product_id, category_id, epoch_id) 会有些用处。但是二级索引和数据BTree之间来回跳转的代价是相当大的。

哪个更好?优化器会选择哪个?很难说。

【讨论】:

  • IN(1) 变成 =1,所以这不是问题。但雷蒙德是正确的,IN 具有多个元素将不会避免排序。
  • 不小心删除了我的评论,但我说IN(1, 2)不是IN(1),我知道转换为=1 ...但是WHERE category_id IN(1, 2) ORDER BY product_id, category_id, epoch_id现在我重新考虑应该只能使用索引因为category_idproduct_id 中排序 -> primary key (product_id, category_id, epoch_id)
  • 可能= constantIS NULL 是唯一可以避免排序的东西(在这种情况下)。
猜你喜欢
  • 2012-01-30
  • 1970-01-01
  • 1970-01-01
  • 2012-04-12
  • 2014-01-09
  • 2011-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多