【问题标题】:DELETE performance hit on a large MySQL table with index具有索引的大型 MySQL 表的 DELETE 性能下降
【发布时间】:2014-09-04 18:05:27
【问题描述】:

假设我们有一个带有 MySQL 5.6 数据库的网络论坛应用程序,许多用户可以 24/7 全天候访问。现在有一个这样的表格,用于发送给用户的通知元数据。

| notifications | CREATE TABLE `notifications` (
 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
 `user_id` bigint(20) unsigned NOT NULL,
 `message_store_id` bigint(20) unsigned NOT NULL,
 `status` varchar(10) COLLATE ascii_bin NOT NULL,
 `sent_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 KEY `user_id` (`user_id`,`sent_date`)
) ENGINE=InnoDB AUTO_INCREMENT=736601 DEFAULT CHARSET=ascii COLLATE=ascii_bin |

此表有 100 万行。使用此表,某个 message_store_id 由于某种原因突然变得无效,我计划使用单个删除语句删除所有具有该 message_store_id 的记录,例如

DELETE FROM notifications WHERE message_store_id = 12345;

这条语句影响了 10% 的表,因为这条消息被发送给了这么多用户。同时,成千上万的用户一直在访问此通知表,因此必须存在索引。显然,在删除记录时重新创建索引非常昂贵,所以我害怕这样做并通过最大化服务器资源而导致停机时间。但是,如果我删除索引,删除记录然后重新添加索引,我必须关闭数据库一段时间,很遗憾我们的服务无法使用。

我希望 MySQL 5.6 不会那么愚蠢,以至于这一条语句可以杀死数据库,但我想这很有可能。我的问题是,对于这样的案例,索引重新创建真的是致命的吗?如果是这样,这个操作有什么好的策略不需要我停止数据库进行维护吗?

【问题讨论】:

  • 你有message_store_id的索引吗?

标签: php mysql sql database indexing


【解决方案1】:

根据您的应用程序的详细信息,您可以采用很多技巧/策略。

  1. 如果您计划定期执行这些操作(例如,这不是一次性的),并且 message_store_id 中的不同值很少,则可以使用分区。按message_store_id 的值进行分区,预先创建X 个分区(其中X 是对id 值数量的合理上限),然后您可以通过截断该分区立即删除该分区中的所有记录。几毫秒的事。缺点:message_store_id 必须是主键的一部分。注意:您必须事先创建分区,因为我上次使用它们时,alter table add partition 重新创建了整个表,这对大表来说是一场灾难。
  2. 即使alter table truncate partition 不适合您,您仍然可以从分区中受益。如果您在分区上发出DELETE,通过提供相应的where 条件,表的其余部分将不会受到此DELETE 操作的影响/锁定。
  3. 在不锁定数据库太长时间的情况下删除记录的替代方法:

    while (true) {
      // assuming autocommit mode
      delete from table where {your condition} limit 10000;
      // at this moment locks are released and other transactions have a chance
      // to do some stuff.
      if (affected rows == 0) {
        break;
      }
      // This is a good place to insert sleep(5) to give other transactions
      // more time to do their stuff before the next chunk gets deleted.
    }
    

【讨论】:

    【解决方案2】:

    一种选择是将删除执行为几个较小的操作,而不是一个巨大的操作。

    MySQL 提供了一个LIMIT 子句,它将限制查询匹配的行数。

    例如,您可以只删除 1000 行:

    DELETE FROM notifications WHERE message_store_id = 12345 LIMIT 1000;
    

    您可以重复这一点,为其他操作留出合适的时间窗口(竞争 同一张表上的锁)来完成。要在纯 SQL 中处理这个问题,我们可以使用 MySQL 的 SLEEP() 函数,暂停 2 秒,例如:

    SELECT SLEEP(2);
    

    显然,这可以合并到循环中,例如在 MySQL 过程中,继续循环直到 DELETE 语句影响零行。

    【讨论】:

      猜你喜欢
      • 2013-05-30
      • 2020-04-23
      • 2014-08-22
      • 2011-09-18
      • 1970-01-01
      • 2011-03-24
      • 1970-01-01
      • 2015-08-13
      • 1970-01-01
      相关资源
      最近更新 更多