【问题标题】:MySQL: How avoid all partitions scan (year-based) when doing ID lookup?MySQL:在进行 ID 查找时如何避免所有分区扫描(基于年份)?
【发布时间】:2020-12-03 01:16:48
【问题描述】:

如果我有一个按年份分区的表;当我必须通过其 ID 查找行并且无法在查找查询中使用分区修剪时,如何避免扫描所有分区?

CREATE TABLE part_table (
    id bigint NOT NULL auto_increment,
    moment datetime NOT NULL,

    KEY (id),
    KEY (moment)
)-- partitioning information (in years)
    PARTITION BY RANGE( YEAR(moment) ) (
    PARTITION p2020 VALUES LESS THAN (2021),
    PARTITION p2021 VALUES LESS THAN (2022),
    PARTITION p2022 VALUES LESS THAN (2023),
    PARTITION p2023 VALUES LESS THAN (2024),
    PARTITION p2024 VALUES LESS THAN (2025),
    PARTITION p2025 VALUES LESS THAN (2026),
    PARTITION pFuture VALUES LESS THAN (maxvalue) )
;

例如查询查询:

SELECT * FROM part_table WHERE ID = <nr>

【问题讨论】:

  • 试试PRIMARY KEY(id, moment)?
  • @Barmar;使用该主键不会导致扫描不同的分区集 - 当使用 EXPLAIN SELECT * FROM part_table WHERE id = 1 时,两种情况下都会显示相同的所有分区集。

标签: mysql partitioning pruning


【解决方案1】:
  • 您不想要PRIMARY KEY(id, moment)PRIMARY KEY(moment, id) 而不是INDEX(id)
  • 索引被分区。每个分区本质上是一个“表”。它有一个用于数据和 PK 的 BTree,以及一个用于每个二级索引的 BTree。
  • 所以,要找到id=123 需要检查每个分区中的INDEX(id)。这就是为什么 PARTITIONed 表有时比等效的非分区表的原因之一。
  • 预先创建未来的分区(除了一个)是低效的。

向我们展示您的主要疑问。我可能会解释为什么不应该对表进行分区。我在您的定义中看到了两个可能的好处:

  • 删除“旧”数据比 DELETEing 它快得多。
  • `WHERE something-else AND moment between ..

一些案例

对于本次讨论,我假设以某种方式(BY RANGE(TO_DAYS(moment))BY ... (YEAR(moment)) 等)按日期时间进行分区。

WHERE id BETWEEN 111 and 222

分区可能会带来些许伤害,因为无论有哪些索引可用,查询都必须查看每个分区。

WHERE id BETWEEN 111 and 222
  AND moment > NOW() - INTERVAL 1 MONTH
with some index starting with `id`

这是分区“修剪”有益的情况。它将查看一个或两个分区(取决于查询是否在一月份运行)。然后它会在某种程度上有效地使用索引来查找id

现在让我们讨论一下索引以id 开头的两种风格(并假设上面的WHERE 子句之一:

PRIMARY KEY(id, moment)

PK 与数据“聚集”在一起。也就是说,数据首先按id 排序,然后是moment。因此id BETWEEN... 将在 BTree 中连续找到行——这是最有效的。 AND moment... 用于过滤一些行。

INDEX(id)

不是“集群”。它是二级索引。二级索引需要两个步骤。 (1) 在二级BTree中搜索id,但不通过moment过滤; (2) 使用为您提供的人工PK进入数据BTree; (3) 现在可以通过moment 进行过滤。更多的步骤,更多的阅读块等等。

DROP PARTITION p2020

id much 比 `DELETE .. WHERE moment

更多

查看所有主要查询很重要。 X=constantX BETWEEN... 可以在优化方面产生很大的不同;请提供适合您应用的具体示例。

此外,有时“覆盖”索引可以弥补其他效率低下的索引。因此,这些示例需要显示重要查询中的所有列。以及它们是什么数据类型。

在没有这些细节的情况下,我将做出以下广泛的陈述(可能会因细节而无效):

  • 如果 WHERE 仅引用一列,则 PARTITIONing 可能永远不会有用。
  • 如果WHERE 有一个= 测试和一个“范围”测试,则可能有一个复合索引比分区更有效。
  • 当有两个范围测试时,分区可能会发光,但前提是可以应用“修剪”。 (修剪有很多限制。)
  • 对于 2 个范围,未修剪的范围应位于 PRIMARY KEY 的开头。
  • 当使用修剪但WHERE 的其余部分不能使用某些索引时,这意味着扫描分区。如果只有几个分区,那可能是一次大扫描。
  • 不要预先构建多个分区。在不剪枝的情况下,打开所有分区却发现有些是空的,代价有点高。

【讨论】:

  • 主键必须包含分区键。
  • @Rick James + Barmar:确实,用于分区的列需要包含在所有唯一索引中,所以这就是 id 索引不能唯一的原因。我希望也有一种方法可以使用 id 列进行分区修剪;例如如果每个分区都记住其 id 索引的最小/最大 id 值,则修剪算法可能能够检查在查找某个值时是否可以跳过某些分区。
  • @Rick James:分区的主要好处是可以轻松删除旧数据。大多数查询将对与当前年份相关的行进行操作,因此这可能是将查找查询限制到某个分区的最佳可行方法 - 将 id 标准与 WHERE 子句中的时刻(年份)标准结合起来。
  • @Rick James;使用 PRIMARY KEY(id, moment) 而不是 KEY(id) 有什么好处?由于使用了列矩,唯一性标准并没有真正的意义。还是我错过了什么? :-)
  • @whale70 - 我添加了一些带注释的示例。
猜你喜欢
  • 2014-02-07
  • 2021-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-25
  • 2021-10-15
相关资源
最近更新 更多