节流ALTER TABLE磁盘利用率

时间:2017-04-06 13:18:26

标签: mysql database indexing alter-table

我将从MySQL Online DDL Limitations页面开始:

  

没有机制可以暂停在线DDL操作或限制在线DDL操作的I / O或CPU使用率。

但是,我仍然对我可能错过的解决方案感兴趣。

情况:索引越来越大,而且它们变得越来越大,以至于没有足够的内存用于所使用的查询,导致磁盘I / O飙升,一切都陷入混乱。已经创建了较小的新复合索引,但问题在于运行ALTER TABLE而没有破坏任何内容。

事实如下:

  1. 这是一张InnoDB表。
  2. 该表没有主键或唯一索引。
  3. 没有列的组合适合作为主键或唯一索引。
  4. 该表没有外键。
  5. 该表每月分区(目前为50个)。
  6. 该表必须始终接受写入。
  7. 最新的3-6分区必须接受读取。
  8. 有一个id列,但这不是唯一的。
  9. 该表包含大约20亿行。
  10. 当前月份的分区是唯一接收写入的分区。
  11. 分区提前1个月进行;总有一个空分区。
  12. SHOW CREATE TABLE(我没有包含所有分区):

    CREATE TABLE `my_wonky_table` (
      `id` bigint(20) unsigned NOT NULL,
      `login` varchar(127) DEFAULT NULL,
      `timestamp` int(10) unsigned NOT NULL,
      `ip` varchar(32) CHARACTER SET ascii DEFAULT NULL,
      `val_1` int(10) unsigned DEFAULT NULL,
      `val_2` varchar(127) DEFAULT NULL,
      `val_3` varchar(255) DEFAULT NULL,
      `val_4` varchar(127) DEFAULT NULL,
      `val_5` int(10) unsigned DEFAULT NULL,
      KEY `my_wonky_table_id_idx` (`id`),
      KEY `my_wonky_table_timestamp_idx` (`timestamp`),
      KEY `my_wonky_table_val_1_idx` (`val_1`,`id`),
      KEY `my_wonky_table_val_2_idx` (`val_2`,`id`),
      KEY `my_wonky_table_val_4_idx` (`val_4`,`id`),
      KEY `my_wonky_table_val_5_idx` (`val_5`,`id`),
      KEY `my_wonky_table_ip_idx` (`ip`,`id`),
      KEY `my_wonky_table_login_idx` (`login`,`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8
    /*!50100 PARTITION BY RANGE (`id`)
    (PARTITION pdefault VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */
    

    关于查询:在SELECT上始终为id,其他所有内容都用于过滤。

    我想避免的事情:

    • 关闭数据库实例。
    • 100%的磁盘I / O

    我曾想过使用pt-online-schema-change工具进行节流,但遇到了无主键墙。另一种解决方案是在代码中执行此操作,有效地将触发器移动到代码库,并使用有些奇怪的块(例如,使用时间戳列的一小时数据块)缓慢地复制数据,因为没有唯一索引。

    是否有其他解决方案和/或工具可用?

3 个答案:

答案 0 :(得分:6)

  1. 创建一个类似于new表的real表,但使用修订后的索引。添加PRIMARY KEY以便您不会再被困。 - 这是ALTER,但还不是“填充”。
  2. 在新表格中,使用季度或年度分区来处理旧内容;当前和(稍后)未来分区的每月。 - 这是为了减少分区的总数。我的经验法则是“不超过50个分区”。 (如果您对此计划有疑问,请告诉我。)
  3. 编写脚本以将分区中的所有数据缓慢复制到new表中。我对chunking的建议在这里可能很有用。
  4. 在您赶上之前,创建一个新分区。但是不要复制它。停止上一个分区末尾的“复制”脚本。
  5. 当抓住除了这个新分区之外,停止写入。
  6. 复制最后一个分区。 - 这是第4步的成果。
  7. 原子交换:RENAME TABLE real TO old, new TO real;。然后再打开。
  8. 强烈建议编写所有脚本并在其他计算机上练习。这种做法可以在总数的一小部分,但它需要至少有几个分区。

答案 1 :(得分:1)

我将此作为单独的答案提出,因为最里面的部分是完全不同的。

与我的其他答案一样,您需要带有新索引的new表,以及用于复制所有数据的脚本。但是,最重要的是模拟你的应用程序中的触发器

幸运的是,你有id,即使它不是PRIMARY KEY。并且,即使它不是UNIQUE,也可以使用它(假设您没有数千个具有相同ID的行 - 如果您这样做,我们可以进一步讨论)。

“复制脚本”和应用程序相互通信。

复制脚本是一个很长的循环:

  • SELECT GET_LOCK('copy', 5), high_water_mark FROM tbl; - (或其他一些超时)
  • 使用id BETWEEN high_water_mark AND high_water_mark + 999复制行。
  • UPDATE tbl SET high_water_mark = high_water_mark + 1000;
  • 暂停(1秒?)
  • 循环直至ids

应用程序在读取时继续从旧表中读取。但在写作时,确实如此:

  • SELECT GET_LOCK('copy', 5), high_water_mark FROM tbl; - (或其他一些超时)
  • 如果超时,则需要修理。
  • 写入旧表 - (因此,读取继续工作)
  • 如果id< = high_water_mark,也请写入新表。
  • SELECT RELEASE_LOCK('copy');

监控进度。在某些时候,您需要停止所有内容,复制最后几行并执行RENAME TABLE

我不知道你的超时,睡眠或块大小的最佳值。但我不认为块大小超过1K是明智的。

这项技术有利于您将来可能需要做的各种更改,因此请保持良好的状态。

答案 2 :(得分:1)

这将归结为MySQL变种& amp;您正在使用的版本,但如果它是每个连接一个线程(my.cnf thread_handling=one-thread-per-connection,可能是您的构建中的默认值),并且您可以将ALTER TABLE工作负载放在新连接中,那么工作负载是一个唯一的PID,您可以在其上使用ionice / renice

我有点蹩脚的答案,但它的侵入性比其他选项要小。

如果查看ps -eLf |grep mysql,您可以看到线程/轻量级进程,只需要弄清楚PID属于您的特定连接。如果通过TCP连接,则可以匹配本地连接端口并将其映射到lsof以查找特定线程。其他方式可以使用strace,systemtap等,或运行您可以查看的初始查询。

之后,您可以使用ionice / renice来影响系统上的PID。你真的想确保你捕获它是什么PID,并重置好和&之后的优先级,不影响其他任何事情。

与其他人一样,您确实需要长期重塑此表。分区很有用,但不是最终结果,因为您运行的是1.3TiB的在线数据,并且您声明只需要读取最近的3-6个分区。在添加本机分区之前来自MySQL,我认为这对于VIEW和单独的表(在需要翻转时以原子方式更新VIEW)是一个很好的例子。它还可以让您轻松地将一些旧表移动到离线存储。