【问题标题】:What is the most performant table index for a set of history records?一组历史记录的性能最高的表索引是什么?
【发布时间】:2016-10-17 06:40:37
【问题描述】:

我有一个简单的历史表,我正在开发一个新的查找表。我想知道添加到该表中的最佳索引(如果有的话)是什么,以便尽可能快地查找。

历史表是一组简单的操作记录。每个动作都有一个类型和一个动作日期(以及一些其他属性)。系统每天都会生成一组新的操作记录。

相关的伪模式是:

TABLE history
    id int,
    type int,
    action_date date
    ...
INDEX
    id
    ...

注意:该表没有typeaction_date 上编入索引。

新的查找功能旨在检索在特定操作日期发生的特定类型的所有记录。

我最初的想法是定义一个由typeaction_date 组成的复合键。

但是,在我的情况下,会有许多具有相同类型和日期的操作。此外,每天的行动数量将大致均匀分布。

鉴于以上所有情况:(a) 是一个值得的索引; (b) 如果是,首选索引是什么?

我正在使用 MySQL,但我认为我的问题并非特定于此 RDBMS。

【问题讨论】:

  • 在 (type,action_date) (和/或 (action_date,type)) 上的索引似乎是明智的
  • 我会在日期 + 类型上使用复合索引。聚类因子会非常好,因为数据似乎是按日期排序的,所以我希望它能够很好地工作。但是,您必须对其进行测试,如果具有相同日期和类型的数据量非常多,则索引可能必须查找很多节点。
  • 如果复合索引运行良好,您也可以选择仅索引扫描,方法是将您要选择的其他字段添加到索引中。这样数据库就不需要从实际表中获取它们。 (但我猜这是另一个话题)
  • 您可以按类型或日期使用某些表分区。这将为查询带来一些速度。 arubin.org/files/PracticalPartitioning_Webinar.pdf
  • @valicu2000 - 在这种情况下,合适的索引似乎是可行的。因此,分区不太可能提高性能。

标签: mysql sql performance indexing


【解决方案1】:

索引上的第一个字段应该是在应用条件后为大多数查询提供最小数据集的字段。

根据您的业务需求,您可以请求特定日期或特定日期范围(很可能是日期范围)。所以日期应该是索引上的最后一个字段。很可能您将始终拥有日期条件。

一个常见的答案是拥有 (type,date) 索引,但如果您在查询中查询了多个类型值或只有几种类型(例如少于 5 个),则应该只考虑日期索引) 并且它们分布不均。

比如你有type 1 70%的表,type 2,3,4,...不到百分之几的表,而且你经常查询type 1,你最好有单独的日期索引,和类型索引(用于查询类型 2、3、4 的情况),而不是复合(类型、日期)索引。

【讨论】:

  • 如果 Date 是索引中的第一个或唯一一个字段,则索引 b-tree 将以最坏的情况结束,因为历史总是添加到最后。如果可能,最好使用
  • 同样,(type,date) 是此类讨论中的常见答案,但如果类型值覆盖超过 30% 的记录,即使您的日期范围很小,mysql 也不会使用索引第二个字段。
  • 也许为了改进你的答案,你应该提到按日期索引的历史表应该经常重新索引。
  • 约翰,我认为总是在最后添加较晚的日期值是增长索引的“自然”方式,并且永远不需要重新索引。但是对于 (type, date) 类型值是不可预测的,您将需要完全按照您的解释重新索引。
  • @John 我认为您误解了 B 树的工作原理。 B树总是平衡的。也就是说,树的所有分支都具有相同的深度。需要重新组织索引是因为由于页面拆分,索引页面最终可能会出现大量可用空间。如果在 B 树的末尾插入,则可以避免此问题,因为无需拆分现有页面即可添加新页面。
【解决方案2】:

INDEX(type, action_date),不管任一列的基数或分布。这样做将最大限度地减少需要查看的索引 BTree 的“行”数。 (是的,我不同意 Sergiy 的回答。)

即使WHERE type IN (2,3) AND action_date ... 也可以使用该索引。

为了检查日期范围,比如 2 周,我推荐这种模式:

AND action_date >= '2016-10-16`
AND action_date  < '2016-10-16` + INTERVAL 2 WEEK

一种查看查询需要多少“工作”的方法:

FLUSH STATUS;
SELECT ...;
SHOW SESSION STATUS LIKE 'Handler%';

显示的数字会让您了解需要触及多少索引(或数据)行。这可以很容易地查看两个可能的查询/索引中哪一个效果更好,即使表太小而无法获得可靠的计时。

【讨论】:

  • 正如我之前提到的,您的索引选择取决于实际数据集和查询业务模式(典型搜索)。如果第一个条件不是常量,Mysql 索引将不会生效。对于第一个字段的值列表,将使用索引,但不会使用索引的日期部分。
  • @SergiyTytarenko - 添加 MRR 后(5.6.0?5.6.10?),优化器可以使用IN 列表在索引中超越它。请参阅dev.mysql.com/doc/refman/5.6/en/mrr-optimization.html dave - 你使用的是什么版本?
  • Rick,我正在使用 mariadb 10.0 并且非常了解 MRR 设置和摆弄。当您设计表时,我不会依赖优化器。例如,简单的业务查询“所有类型两周”将对索引(类型,日期)进行全索引和表扫描。
  • 是的,需要INDEX(date)
  • Rick,MariaDB 是第一个引入这些优化的人。他们可以快速解决现有数据集上的某些查询问题,而不是设计数据集。仅仅因为您使用的是特定的 MySQL 版本,您将无法保护 (type,date) 索引以覆盖简单的日期范围索引覆盖。 B-tree 索引不随 MySQL 版本而变化。
【解决方案3】:

是的,索引是值得的。特别是如果您搜索表的一小部分。

如果您的搜索将匹配 20% 或更多的表(大约),MySQL 优化器会认为索引比它的价值更麻烦,即使索引可用,它也会进行表扫描。

如果您搜索一个特定的type 值和一个特定的date 值,(类型、日期)上的索引或(日期、类型)上的索引是一个不错的选择。您首先列出哪一列并不重要。

如果您搜索 多个 类型值或 多个 日期,那么列的顺序很重要。遵循本指南:

  • 索引的最左边的列应该是您进行相等性比较的列。相等比较是与一个值完全匹配的比较(即使该值在许多行中都存在)。

    WHERE type = 2 AND date = '2016-10-19' -- both equality
    
  • 索引的下一列可以是范围比较的一部分。范围比较匹配多个值。例如,&gt;IN( )BETWEEN!=

    WHERE type = 2 AND date > '2016-10-19' -- one equality, one range
    

    只有一个这样的列受益于索引。如果您对多列进行范围比较,则只有索引的第一列将使用索引来支持查找。随后的列将不得不“艰难地”搜索那些匹配的行。

    WHERE type IN (2, 3, 4) AND date > '2016-10-19' -- multiple range
    
  • 如果您有时使用type 上的范围条件和date 上的相等性进行搜索,则需要创建第二个索引。

    WHERE type IN (2, 3, 4) AND date = '2016-10-19' -- make index on (date, type)
    

    WHERE 子句中的术语顺序无关紧要。 SQL 查询优化器将找出并重新排序它们以匹配索引中定义的正确列。

【讨论】:

  • 是的,这是正确的,并且出于实际原因,业务将希望确保查询不止一种类型并确保查询日期范围。那时您会接到来自企业的电话,说您的查询速度很慢。当您的数据变大时,我确实认为 (type, date) 是一个陷阱。拥有日期和类型的单独索引将在以后使您的数据集受益并提供更好的覆盖范围。加上 mysql 和特别是 mariadb 可以有效地组合单独的索引结果。
  • INDEX(type), INDEX(date) 不如INDEX(type,date), INDEX(date,type)。换一种说法:“索引合并几乎没有复合索引好”。
  • Bill -- 20%“规则”是否适用于多列索引?我的印象是没有。
  • 我同意具有不同列顺序的两个复合索引优于两个单独的索引。坦率地说,我从未见过 index-merge 做任何有用的事情。
  • @RickJames,我不能说我专门测试了复合索引的 20% 规则。值得测试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-04-11
  • 2017-01-11
  • 1970-01-01
  • 1970-01-01
  • 2018-09-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多