节流 ALTER TABLE 磁盘利用率

Throttle ALTER TABLE disk utilization

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

There is no mechanism to pause an online DDL operation or to throttle I/O or CPU usage for an online DDL operation.

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

情况:索引越来越大,越来越大以至于没有足够的内存用于使用的查询,导致磁盘I/O暴涨,一切都陷入彻底的混乱。已创建较小的新复合索引,但问题是 运行 ALTER TABLE 没有破坏任何东西。

事实如下:

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

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) */

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

我想避免的事情:

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

是否有其他可用的解决方案 and/or 工具?

  1. 创建一个类似于 real table 的 new table,但具有修改后的索引。包括一个 PRIMARY KEY 这样你就不会再被困住了。 -- 这是 ALTER,但还不是 "populate"。
  2. 在新的table中,对旧内容使用季度或年度分区;当前和(以后)未来的分区每月一次。 -- 这是为了减少分区总数。我的经验法则是 "no more than 50 partitions"。 (如果您对此计划有疑问,请告诉我。)
  3. 编写一个脚本,慢慢地将所有数据从分区复制到newtable。我对 chunking 的建议在这里可能会有用。
  4. 就在你被赶上之前,创建一个新分区。但是不要从中复制。在前一个分区的末尾停止 "copy" 脚本。
  5. 当赶上除了这个新分区,停止写入。
  6. 复制最后一个分区。 -- 这是第 4 步得到回报的地方。
  7. 原子交换:RENAME TABLE real TO old, new TO real;。然后再次打开写入。

强烈建议编写所有脚本并在另一台机器上练习。实践可以在总数的一小部分上,但至少需要有几个分区。

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

与我的其他答案一样,您需要具有新索引的 new table,以及用于复制所有数据的脚本。然而,主要是在你的应用.

中模拟触发器

幸运的是,你有 id,尽管它不是 PRIMARY KEY。而且,即使它不是 UNIQUE,也可以使用(假设您没有数千行具有相同的 id——如果有,我们可以进一步讨论)。

"copy script" 和应用程序相互对话。

复制脚本在一个长循环中:

  • 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

应用程序在读取时继续从旧 table 读取。但是写的时候,确实是:

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

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

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

此技术对于您将来可能需要进行的各种更改具有优势,因此请保持胆量。

这将归结为您使用的 MySQL 变体和版本,但是如果每个连接一个线程(my.cnf thread_handling=one-thread-per-connection,这可能是您的默认设置build),你可以把你的ALTER TABLE工作负载放在一个新的连接中,那么工作负载就是一个唯一的PID,你可以在上面使用ionice/renice

我的答案有点蹩脚,但它比其他选项更具侵略性。

如果你查看 ps -eLf |grep mysql 你可以看到 threads/lightweight-processes,只需要弄清楚哪个 PID 属于你的特定连接。如果您通过 TCP 连接,您可以匹配您的本地连接端口并将其映射到 lsof 以查找特定线程。其他方法也可以使用 strace、systemtap 等,或者 运行 您可以查看的初始查询。

之后,您可以使用ionice/renice来影响系统上的PID。您真的很想确保捕获它是什么 PID,然后重置 nice 和优先级,以免影响其他任何东西。

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