29

不久之后,我们将需要对生产数据库进行架构更改。我们需要尽量减少这项工作的停机时间,但是,ALTER TABLE 语句将运行相当长的一段时间。我们最大的表有1.5亿条记录,最大的表文件是50G。所有表都是 InnoDB,并且它被设置为一个大数据文件(而不是一个文件每个表)。我们在 8 核机器、16G 内存和 RAID10 配置上运行 MySQL 5.0.46。

我有一些 MySQL 调优的经验,但这通常侧重于来自多个客户端的读取或写入。在 Internet 上可以找到很多关于这个主题的信息,但是,关于(临时)调整 MySQL 服务器以加速 InnoDB 表上的 ALTER TABLE 或 INSERT INTO 的最佳实践的信息似乎很少。 . SELECT FROM(我们可能会使用它而不是 ALTER TABLE 以获得更多的机会来加快速度)。

我们计划做的架构更改是向所有表添加一个整数列,并使其成为主键,而不是当前的主键。我们还需要保留“旧”列,因此不能选择覆盖现有值。

什么是尽快完成这项任务的理想设置?

4

6 回答 6

16

您可能想查看 Percona 工具包中的pt-online-schema-change。本质上它的作用是:

  • 复制原始表结构,运行 ALTER。
  • 将旧表中的行复制到新创建的表中。
  • 复制时使用触发器跟踪和同步更改。
  • 一切完成后,它通过重命名两个表来交换表。

对于单实例数据库非常有效,但如果您使用复制可能会非常棘手,并且您无法负担停止从属服务器并在以后重建它们。

这里还有一个很好的网络研讨会

PS:我知道这是一个老问题,只是回答以防有人通过搜索引擎点击此问题。

于 2012-07-03T20:43:32.957 回答
15

您需要更仔细地考虑您的要求。

在最简单的级别上,更改表的“最快”方法是在尽可能少的ALTER TABLE语句中完成,最好是一个。这是因为 MySQL 复制表的数据以更改架构并进行 15 次更改,同时进行一次复制显然(并且确实)比复制表 15 次更快,一次进行一次更改。

但是我怀疑您是在问如何以最少的停机时间进行此更改。我这样做的方式,你基本上综合了非块的ALTER TABLE工作方式。但它有一些额外的要求:

  1. 您需要一种方法来跟踪添加和更改的数据,例如为后者使用“修改”日期字段,或AUTO_INCREMENT为前者使用字段。
  2. 您需要空间才能在数据库上拥有两个表副本。
  3. 您需要一个时间段,在该时间段内对表的更改不会比快照提前太多

基本技术如您所建议的那样,即使用INSERT INTO ... SELECT .... 至少你在前面,因为你从 InnoDB 表开始,所以SELECT不会阻塞。我建议在ALTER TABLE新的空表上执行,这将保存 MySQL 再次复制所有数据,这意味着您需要在INSERT INTO ... SELECT ...语句中正确列出所有字段。然后你可以做一个简单的RENAME语句来交换它。然后你需要做另一个INSERT INTO ... SELECT ... WHERE ...,也许是一个UPDATE ... INNER JOIN ... WHERE ...来获取所有修改过的数据。您需要快速INSERT执行此操作,否则您的代码将开始向您的快照添加新行和更新,这干扰您的更新。UPDATE (如果您可以将您的应用程序从RENAME.

除此之外,您可以为一个会话更改一些与键和缓冲区相关的设置,这些设置可能有助于主要数据的移动。像这样的东西read_rnd_buffer_size,并且read_buffer_size将有助于增加。

于 2009-03-18T05:38:22.047 回答
12
  1. 设置从站
  2. 停止复制。
  3. 在奴隶上进行 ALTER
  4. 让奴隶赶上主人
  5. 交换主机和从机,因此从机成为具有更改结构和最小停机时间的生产服务器
于 2009-10-06T12:57:56.657 回答
11

不幸的是,这并不总是像staticsan在他的回答中提到的那样简单。在线创建新表并移动数据很容易,在维护模式下进行清理也很容易,但是,Mysql RENAME 操作会自动操作对旧表的任何外键引用。这意味着对原始表的任何外键引用仍将指向您将表重命名为的任何内容。

因此,如果您对要更改的表有任何外键引用,那么您要么更改这些表以替换对新表的引用,要么更糟的是,如果该表很大,您必须重复该过程表二。

过去对我们有用的另一种方法是处理一组 Mysql 副本来处理变更。我不是最适合谈论这个过程的人,但它基本上包括中断对一个从属的复制,在该实例上运行补丁,一旦改变表完成就重新打开复制,以便它赶上复制。一旦复制赶上,您将站点置于维护模式(如有必要)以从您的主数据库切换到这个新的修补过的从属数据库作为新的主数据库。

我不记得的唯一一件事是你将其他奴隶指向新主人的确切时间,以便他们也得到应用。对此过程有一个警告,我们通常在代码需要更改之前或在代码更改为不再引用列/键之后使用它来滚动更改补丁。

于 2009-06-09T20:53:47.390 回答
7

我测试了各种策略来加速一张变更表。最终,在我的特定情况下,我的速度提高了大约 10 倍。结果可能适用于您的情况,也可能不适用于您的情况。但是,基于此,我建议尝试使用 InnoDB 日志文件/缓冲区大小参数。

简而言之,只有增加 innodb_log_file_size 和 innodb_log_buffer_size 才会产生可衡量的效果(小心!更改innodb_log_file_size 是有风险的。请参阅下文了解更多信息)。

根据粗略的写入数据速率 (iostat) 和 cpu 活动,瓶颈是基于 io 的,而不是数据吞吐量。在更快的 500 秒运行中,写入吞吐量至少与您对硬盘的预期相同。

尝试了性能优化:

更改 innodb_log_file_size 可能很危险。请参阅http://www.mysqlperformanceblog.com/2011/07/09/how-to-change-innodb_log_file_size-safely/链接中解释的技术(文件移动)在我的情况下效果很好。

另请参阅http://www.mysqlperformanceblog.com/2007/11/03/choosing-innodb_buffer_pool_size/http://www.mysqlperformanceblog.com/2008/11/21/how-to-calculate-a-good-innodb -log-file-size/有关 innodb 和调整日志大小的信息。较大日志文件的一个缺点是崩溃后的恢复时间较长。

试运行和粗略的时间安排:

  • 将数据简单加载到新创建的表中:6500s
  • 加载数据 w。innodb_log_file_size=200M,innodb_log_buffer_size=8M,innodb_buffer_pool_size=2200M,自动提交= 0;unique_checks=0, foreign_key_checks=0: 500s
  • 加载数据 w。innodb_log_file_size=200M, innodb_log_buffer_size=8M: 500s
  • 等效的直接更改表 w。datainnodb_log_file_size=200M, innodb_log_buffer_size=8M: 500s

测试详细信息:表:InnoDB,6M 行,磁盘 2.8G,单个文件(innodb_file_per_table 选项),主键是 1 个整数,+2 非队列约束/索引,8 列,平均。行长 218 字节。服务器:Ubuntu 12.04,x86_64,虚拟机,8 核,16GB,sata 消费级磁盘,无 raid,无数据库活动,其他进程活动很少,其他和更小的虚拟机中的活动很少。mysql 5.1.53。初始服务器配置是非常默认的,除了增加了 1400M 的 innodb_buffer_pool_size。alter 表添加了 2 个小列。我没有对原始的 alter table 计时,而是尝试了等效的 load data infile 语句,最后我做了直接的 alter table 并得到了可比较的结果。

这个问题至少与以下问题有关:

于 2012-10-02T09:57:39.500 回答
-5

我真的不知道如何优化它,但在进行此类更新之前将站点置于离线模式通常是一个好习惯。

然后,您可以在凌晨 3 点运行您的 DB 脚本,因此如果停机时间比理想情况长很多,这无关紧要。

于 2009-03-17T15:01:33.143 回答