【问题标题】:Is adding and dropping indexes everyday on huge tables a good practice?每天在大表上添加和删除索引是一种好习惯吗?
【发布时间】:2017-04-07 13:12:18
【问题描述】:

我正在构建一个连接到 MySQL 数据库的 Web 应用程序。 目前我有两个巨大的表,每个表包含大约 4000 万行,并且它们每天都会收到新行(每天增加约 500 000-1000 000 行)。

添加新行的过程在夜间运行,而没有人可以使用该应用程序,并且新行的内容取决于对当前数据库的一些基本SELECT 查询的结果。 为了足够快地获得SELECT 语句的结果,我在WHERE 子句中至少出现一次的每一列上使用简单索引(每个索引一列)。

问题是,白天会针对这些表运行一些完全不同的查询,包括一些“范围 WHERE 子句”(SELECT * FROM t1 WHERE a = a1 AND b = b1 AND (date BETWEEN d1 AND d2))。 我在堆栈上发现了这本非常有用的迷你食谱,它会根据查询数据库的方式建议您应该使用哪些索引:http://mysql.rjweb.org/doc.php/index_cookbook_mysql 他们建议使用复合索引:在我上面的示例查询中,它会给出 INDEX(a, b, date)。

它确实提高了白天运行查询的速度(从 1 分钟到 8 秒,所以我真的很高兴)。

但是,使用这些复合索引,在夜间添加新行所需的时间完全爆炸(添加每日内容需要超过一天的时间)。

这是我的问题:是否可以每天晚上删除所有索引,添加新内容并备份每日索引? 或者这会很危险,因为索引不是每天都要重建的,尤其是在这么大的表上? 我知道这样的操作总共需要大约两个小时(删除并重新创建索引)。

我知道ALTER TABLE table_name DISABLE KEYS; 的存在,但我使用的是 InnoDB,我相信它不适用于 InnoDB 表。

【问题讨论】:

  • 如果您每天添加 500k 到 1m 行新行,最好在 1 年内您将拥有 222.5m 行,因此如果目前需要 2 小时,则比当前数量多 5 倍,假设速度的线性下降(不太可能),这将需要 10 个小时。在两年的时间里,这将花费一整天的时间,您将永远无法使用该应用程序。如果我是你,我会开始考虑表分区。此外,您是否需要以最细粒度的方式查询数据?如果不是,您可能希望查看 OLAP 数据库和/或多维数据集,以提高查询数据的效率。
  • 你是对的,行数会增加,但它会达到最大数量,因为我每天都会删除超过一年的行,就在添加新行之后。无论如何,我要去看看那些 OLAP 数据库。如果你有一些很好的文档,我很乐意得到它!

标签: mysql sql database indexing


【解决方案1】:

我相信您已经回答了自己的问题:您在白天需要索引,但在晚上不需要。根据您的描述,您应该在晚上删除批量插入的索引,然后重新创建它们。删除数据加载的索引并非闻所未闻,而且在您的情况下似乎很合适。

我想问一下如何您插入新数据。一种方法是一次插入一行值。另一种是将值放入临时表(没有索引)并进行批量插入:

insert into bigtable( . . .)
    select . . .
    from smalltable;

它们具有不同的性能特征。您可能会发现使用单个 insert(如果您还没有这样做的话)对于您的目的来说已经足够快了。

【讨论】:

  • 好的,听到这样的情况并不少见,这是一个好消息。要回答您的问题,遗憾的是不可能将这些值放入临时表中,因为每个新添加的行都会影响下一行的内容。在我开始创建新行之前,需要存储每个创建的行。谢谢!
  • 我运行了描述的过程(删除并构建了夜间索引),它似乎工作正常。插入行的速度非常快,现在该应用程序已准备好用于日常使用。
【解决方案2】:

题外话...PARTITIONing 按日期应该对您非常有用,因为您删除了一年多前的内容。我会推荐 PARTITION BY RANGE(TO_DAYS(...)) 并将其分成 14 或 54 个分区(几个月或几周,加上一些开销)。这将消除删除旧行所需的时间,因为DROP PARTITION 几乎是即时的。

更多详情请见my partition blog。您的情况听起来像是用例 #1 和用例 #3。

回到你删除和重建索引的聪明想法。对于其他人,我要指出一个警告,即您可以在足够长的时间内不碰桌子以进行重建。

使用PARTITIONing,所有插入的行都将进入“最新”分区,对吗?这个分区比整个表小很多,因此索引更有可能适合 RAM,从而更新速度提高 10 倍(无需重建索引)。如果您提供SHOW CREATE TABLESHOW TABLE STATUSinnodb_buffer_pool_size 和 RAM 大小,我可以帮助您计算“最后一个”分区是否适合 RAM。

关于 InnoDB 中的索引更新的说明——它们通过位于“更改缓冲区”中而被“延迟”,该缓冲区是缓冲区池的一部分。请参阅innodb_change_buffer_size_max,自 5.6 起可用。你用的是那个版本还是更新的? (如果没有,您应该升级,原因有很多。)

该设置的默认值为 25,这意味着 25% 的 buffer_pool 留作索引的挂起更新,由 INSERT 等引起。这就像一个“缓存”,这样对相同的索引块被保留在那里,直到它们被撞出。较高的设置应该会减少索引更新访问磁盘的频率,从而更快地完成。

我的目标是……通过增加此设置,您可以使插入(直接,而不是重建)更有效率。我认为这可能会加快速度:

就在每晚INSERTs

innodb_change_buffer_size_max = 70
innodb_old_blocks_pct = 10

每晚INSERTs

innodb_change_buffer_size_max = 25
innodb_old_blocks_pct = 37

(我不确定其他设置,但将其排除在外似乎是合理的。)

同时,innodb_buffer_pool_size 的设置是什么?通常,它应该是可用 RAM 的 70%。

在一个类似的应用程序中,我有大量的、每小时的、要加载到表中的转储,以及 90 天的保留期。我通过有 90 个每日分区和 24 小时分区来扩展我的分区规则。每天晚上,我花很多时间(但不到一个小时)做REORGANIZE PARTITION 将 24 小时分区变成一个新的每日分区(并删除 90 天旧的分区)。在每个小时内,负载具有额外的优势,即没有其他任何东西触及 1 小时分区——我可以在 7 分钟内完成标准化、汇总和加载。整个 90 天适合 400GB。 (旁注:在 8.0 之前,大量分区是性能杀手;所以甚至不要考虑每日分区来保留 1 年。)

汇总表使 50 分钟的查询(在原型中)缩短到只有 2 秒。也许您需要一个带有PRIMARY KEY (a, b, date) 的汇总表?这将使您摆脱“事实”表上的此类索引。糟糕,这消除了您原始问题的整个前提!请参阅我的博客底部的链接;寻找“汇总表”。一般规则:在 Fact 表上不要有任何索引(PRIMARY KEY 除外);对需要更混乱索引的事物使用汇总表。

【讨论】:

  • 首先,感谢您的详细解答。我想我确实会使用分区。但不是日期,因为数据是按时间顺序添加的:我可以使用主键(自动递增的整数)轻松删除我想要的内容。而且我认为正确选择的分区可以帮助我在白天加速我的应用程序。此外,每天晚上还有至少 6 个小时没有人使用该应用程序,所以我仍然有一点能力来管理每天的删除会话。
  • 顺便说一句,感谢您在您的博客上收集所有这些建议,这真的很有帮助,它肯定会帮助我建立正确的分区。我会看看那些汇总表。 :) 关于我的 MySQL 配置,我目前使用的是 MySQL 5.7,并且 innodb_buffer_pool_size 已经设置为可用 RAM 的 70%。 innodb_change_buffer_max_size 的当前值确实是 25;第二天晚上我会尝试将其增加到 70,以评估插入速度有多快。
  • 请报告您使用 change_buffer_max_size 进行的实验。
  • 嗨 Rick,我一直在尝试检查更改对 change_buffer_size 的影响,但这并不容易,因为每天添加的数据量从一天到下一天都在显着变化。但是,我在最后三天进行了一些测试,似乎 change_buffer_max_size 比率较大的影响是两个表上的删除时间有所改善(我正在使用 PK 删除)。插入新行所需的时间并没有真正改变,因为此任务所需的大部分时间都用于确定某些 select 之后的行内容。
  • 我为此参数尝试了不同的值:25(默认)、35 和 45。看起来最适合我的值是 35。
猜你喜欢
  • 2013-11-01
  • 2017-08-09
  • 1970-01-01
  • 1970-01-01
  • 2021-04-11
  • 1970-01-01
  • 2010-10-14
  • 2021-04-22
  • 1970-01-01
相关资源
最近更新 更多