与复合PK的MySQL ORDER BY

时间:2019-07-15 09:18:56

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

我有一张桌子

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_idWHERE子句限制,并且product, epoch的顺序保留在PK中吗?

  2. 为什么product_id, epoch_id不需要文件排序,但是category_id, epoch_id却需要文件排序? -出于相同的原因,product_id, epoch_id是从PK中保留的

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

那么,这是否意味着MySQL将遍历聚集索引并检索按默认值排序的所有行,然后不需要重新排序?

1 个答案:

答案 0 :(得分:2)

您的问题

我在EXPLAINs中感到困惑。有人说“ ALL”;有人说“ index..PRIMARY”。好吧,对于InnoDB,这些实际上是相同的。 PRIMARY KEY与数据一起聚集在同一B + Tree中。

({EXPLAIN是在InnoDB之前的日子里写的,主要是针对MyISAM,它没有对PK进行聚类。)

EXPLAIN不够详细,无法清晰回答您的问题。 EXPLAIN FORMAT=JSON更好,但可能仍然不够清晰。

至于为什么缺少“文件排序”,请考虑一下。如果category是常数,那么您实际上希望按(product_id, epoch_id)进行排序。这就是表的子集排序的顺序。如果您尝试了任何其他组合(例如,首先使用epoch_id),则需要对其进行排序。您的第二种情况和第三种情况(忽略常量category_id之后)都是如此。

对于第三季度:是的,它等效于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之间来回跳跃会非常昂贵。

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