Mysql Innodb 性能 - 如何最小化多列索引?

Posted

技术标签:

【中文标题】Mysql Innodb 性能 - 如何最小化多列索引?【英文标题】:Mysql Innodb Performance - How to minimise multicolumn index? 【发布时间】:2017-05-13 11:50:53 【问题描述】:

下表包含1000万行,

CREATE TABLE Sample1 (
  c1 bigint(20) NOT NULL AUTO_INCREMENT,
  c2 varchar(45) NOT NULL,
  c3 tinyint(4) NOT NULL DEFAULT 0,
  c4 tinyint(4) NOT NULL DEFAULT 0,
  c5 varchar(45) DEFAULT NULL,
  time bigint(20) DEFAULT NULL,
  PRIMARY KEY (c1),
  KEY varchar_time_idx (c2,Time),
  KEY varchar_c3_time_idx (c2,c3,Time),
  KEY varchar_c4_time_idx (c2,c4,Time),
  KEY varchar_c3_c4_time_idx (c2,c3, c4,Time)
) ENGINE=InnoDB AUTO_INCREMENT=10093495 DEFAULT CHARSET=utf8;

选择 创建了四个多列索引以在 where 中选择具有以下条件的行

1) c2 和时间 例如:从 Sample1 中选择 c1、c5,其中 c2 = 'sometext' order by time limit 30;

2) c2 和 c3 以及时间 例如:从 Sample1 中选择 c1、c5,其中 c2 = 'sometext' and c3 = int order by time limit 30;

3) c2 和 c4 以及时间 例如:从 Sample1 中选择 c1、c5,其中 c2 = 'sometext' 和 c4 = int 按时间限制 30 排序;

4) c2 和 c3 和 c4 和时间 例如:从 Sample1 中选择 c1、c5,其中 c2 = 'sometext' and c3 = int and c4 = int order by time limit 30;

为了使上面的选择更快,创建了四个多列索引。

基数明智的 c2、c3 和 c4 非常低。 (例如:在一百万个 c2 中,c3 和 c4 各有 100 个唯一列)。

同样分布不均。 c2 中的每个组的行数都是奇数。 (例如:c2 = 1 包含 100000,c2 = 2 包含 1500000 等等)

列时间(以毫秒为单位的时间戳)主要包含唯一字段。

选择正常发生(一小时10到30次,但应该是高速)

插入 插入非常频繁。 但它是按顺序处理的(一个接一个)。

更新 所有更新基于 C1(主键)。 (频率水平:插入时为 20%) 更新 Sample1 设置 c3 = INT,c4 = INT,时间 = CurrentTimeInMilliSecond 其中 c1 = INT

表格有 5 个索引字段(4 个多列)。由于这个 1) 索引字段的插入和更新变得更昂贵 2)随着表的不断增长(可能达到1亿),索引大小也增长得更快

请在 mysql 中提出解决此用例的好方法。

其他必要的细节 innodb_buffer_pool_size:16106127360(15 GB); CPU 核心:32; 内存:32GB

【问题讨论】:

实际上有 10,093,495 行 @RiggsFolly 你遇到过同样的情况吗? 请向我们展示所有需要这些索引的查询。我们应该首先讨论您是否可以摆脱或合并某些索引。 10M 只是十亿的 1/100。也许表“将增长到 1B”?你有多少内存? innodb_buffer_pool_size的设置是什么?当INSERTing时,c2的值有多随机? time 本质上是当前时间吗? (这些问题会导致您判断INSERTs 在您到达 1B 时的表现。) 如果c2 具有低基数,那么它可能应该被规范化并替换为INT(和UNSIGNED)的某种风格。 【参考方案1】:

注意:TMI 即将到来。我不得不做一些猜测;如果您提供更多详细信息,我可以更具体...

您拥有的 4 个辅助键最适合您列出的 4 个查询。

与流行的妻子故事相反,基数与复合索引和SELECT 性能无关。

在 100M 行时,表(包括索引)可能为 20GB。你有多少内存? innodb_buffer_pool_size 的值是多少?除非您的 RAM 很小,否则这些可能无关紧要。

回到“基数”。

让我们看一下INDEX(c2, Time),其中c2 有100 个不同的值,Time 基本上在不断增加。每个新的INSERT 都会将新行放在 100 个位置之一——每个 c2 簇的末端。这意味着 100 个“热点”,这意味着 100 个块(大部分)足以处理更新这一索引。 100 个块 = 1.6MB 的 buffer_pool——希望是一小部分。

同时,PRIMARY KEYAUTO_INCREMENT,所以有一个热点和一个区块——甚至更小。

但是...其他 3 个辅助键将有更多的热点(块),因此它们可能更重要。让我们去最糟糕的(c2, c3, c4, Time)。试探性地,那将有 100*100*100 个热点。但我认为这将超过整个索引中的块。 (所以,数学崩溃了。)所以那会很忙。

题外话...你INSERT在一个事务中有多少行?多少行/秒? innodb_flush_log_at_trx_commit (flatc) 的值是多少?好吧,让我们将其简化为一次完全刷新的一行,而不是批量刷新的许多行。

回到计算...

在一个极端:小型缓冲池单行事务 flatc=1 HDD:您将需要一些IOP。我希望你不需要插入超过 20 行/秒。

在另一个极端:大型缓冲池批处理 flatc=2 SSD:平均不到 1 IOPS。您可能每秒可以处理超过 1000 行插入。

标准化 c2 可能会将 20GB 估计值减少一半,从而对计算进行多次调整。

回到SELECTs——你真的为给定的c2获取了100K行吗?如果你有更多的过滤,ORDERingLIMITing等,请给他们看;这可能会对分析产生重大影响。

回到标题——我还没有看到任何有用的方法来更改/最小化这些索引。它们似乎对SELECTs非常有用,而对INSERTs 的危害最小

哦,UPDATEs。在考虑那里的后果之前,我们需要查看 UPDATEs 上的 WHERE 子句。

更多(经过多次更新问题)

PRIMARY KEY(c1) 负责使UPDATEs 尽可能快(除了需要最终更新索引)。

SELECTs 非常罕见;我的索引使每次运行都尽可能快

15GB 的 Buffer_pool 表示整个表及其所有索引都将存在于池中(一旦它被预热)——对于当前的 10M 行。在 100M 行时,它可能仍然可以。我这样说是因为可能导致流失的查询是SELECTs,但他们都说AND Time > ...。这意味着一个“工作集”,它是表的“末端”。如果你达到十亿行,这一段需要重新审视。

MySQL 应该能够每天处理一百万个INSERTs,即使是最差的设置。因此,如果您不希望在 3 个月内获得 100M 行,我认为INSERTs 不是问题。

【讨论】:

感谢里克·詹姆斯。更新更多细节。

以上是关于Mysql Innodb 性能 - 如何最小化多列索引?的主要内容,如果未能解决你的问题,请参考以下文章

MySQL-高性能索引

MySQL-高性能索引

如何添加和调整mysql innodb log文件

MySQL COUNT性能分析

about MySql Innodb Index

如何在 MySQL 中有一个涉及 ForeignKey 字段的 unique_together 约束?