【问题标题】:Throttle ALTER TABLE disk utilization限制 ALTER TABLE 磁盘利用率
【发布时间】:2017-09-01 13:20:53
【问题描述】:

我将从 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 个月进行分区;总是有一个空分区。

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,其他所有内容都用于过滤。

我想避免的:

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

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

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

【问题讨论】:

  • 你有那个表的外键吗?此外,您是否需要能够始终读取整个数据(和/或您是否可以例如在维护期间调整您的应用程序以访问不同的表),因为您只指定您必须始终能够写入?此外,该特定表本身的 I/O 是否会出现问题,或者服务器上的 I/O 通常会出现问题(因为它可能会减慢服务器的其余部分)
  • 好问题!我更新了事实清单。此外,I/O 与整个服务器有关。当磁盘利用率达到 100% 时,奇怪的事情开始发生。
  • 请提供SHOW CREATE TABLE;我需要查看更多信息。多少个分区?典型的查询是什么?你在表演什么ALTERs?同时,关于分区的更多信息:mariadb.com/kb/en/mariadb/partition-maintenance
  • 是否有必要将所有这些数据保存在一个表中?
  • 添加了额外信息。 ALTER 是专门将几个索引组合成更少的复合索引,因为它们总是在一起。数据必须保留在此表中,是的。

标签: mysql database indexing alter-table


【解决方案1】:
  1. 创建一个类似于real 表的new 表,但使用修改后的索引。包括PRIMARY KEY,这样您就不会再次被困。 -- 这是ALTER,但还不是“填充”。
  2. 在新表中,对旧表使用季度或年度分区;当前和(以后)未来的分区每月一次。 -- 这是为了减少分区的总数。我的经验法则是“不超过 50 个分区”。 (如果您对此计划有任何疑问,请告诉我。)
  3. 编写一个脚本,将分区中的所有数据慢慢复制到new表中。我对chunking 的建议可能在这里有用。
  4. 就在您赶上之前,创建一个新分区。但不要复制它。在前一个分区的末尾停止“复制”脚本。
  5. 当赶上时除了这个新分区,停止写入。
  6. 复制最后一个分区。 -- 这就是第 4 步奏效的地方。
  7. 原子交换:RENAME TABLE real TO old, new TO real;。然后再次打开写入。

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

【讨论】:

  • 我当前的解决方案是使用 pt-online-schema-change 并关闭很多东西(块大小限制 0、无丢弃、无检查计划、无交换表,并且没有触发器(我全部禁用了它们))然后在交换后对两个 24 小时转储进行差异以在 ALTER 之后插入差异。不过,我非常喜欢这里的第 5 步!我想知道这是否更容易适应;我们遇到的问题是,写入表的系统有一个 24 小时保留队列。不过,我不能只是停止写入,除非你指的是副本?我想试试这个;不过类似于 percona 工具。
  • 祝你好运。啊,我没有意识到可以在pt-osc中关闭触发器。 24hr -- 24 小时后删除数据?但是你有几个月的数据?
  • “停止写入”也发生在 pt-osc 中。诀窍是让它如此简短,以至于作者不会真的不便。我不知道你的情况,所以我不能在那里详细说明。希望作者已经可以通过重试或其他方式处理短暂的网络中断。
  • pt-online-schema-change 工具最终是一个 Perl 脚本。我只是禁用了触发器(以及删除触发器的一些额外内容)。 24 小时是告诉应用程序不要写入条目的超时时间,如果它收到一个继续,或者 24 小时已到,它是一个INSERT INTO。不知道 pt-osc 中的写入停止,谢谢。
【解决方案2】:

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

与我的其他答案一样,您需要具有新索引的 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 是不明智的。

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

【讨论】:

    【解决方案3】:

    这将归结为您使用的 MySQL 变体和版本,但如果它是每个连接一个线程(my.cnf thread_handling=one-thread-per-connection,这可能是您构建的默认设置),您可以将您的 @987654322 @workload 在一个新的连接中,那么workload就是一个唯一的PID,可以在上面使用ionice/renice

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

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

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

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

    【讨论】:

    • 有趣的解决方案!不幸的是,这种“混乱”破坏了我们的内部报告系统,因为磁盘利用率一直保持在 100%。另外,我注意到其他地方发生了奇怪的事情,但这可能是由于其他进程具有奇怪的优先级造成的。
    • 磁盘利用率为 100% 是该情况的正确输出。理想情况下,您应该 ioniceALTER TABLE 进程使用空闲类,而不要理会您的正常工作负载,但是如果您的系统很忙,ALTER 可能需要很长时间。如果您想设置非常具体的磁盘带宽限制(例如 10MB/秒或一些 IOPS),您可以使用 blkio cgroup 系统,并使用 cgclassify 或类似的方法将进程移动到具有目标限制的 cgroup 中。
    • 我们已经为未来的变化计划了 cgroups(目前他们没有被使用根本),我很难“只添加这个”,但我愿意认为这是个好主意!
    猜你喜欢
    • 2014-11-09
    • 2018-05-25
    • 2017-02-04
    • 2021-10-24
    • 2017-01-16
    • 2019-02-18
    • 2015-10-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多