删除带有索引的大型MySQL表的性能

时间:2014-07-14 18:12:13

标签: php mysql sql database indexing

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

| 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不是那么愚蠢,以至于这个单一语句可以杀死数据库,但我想这很可能。我的问题是,对于像这样的案例,索引娱乐真的是致命的吗?如果是这样,这个操作有什么好的策略,不要求我停止维护数据库吗?

2 个答案:

答案 0 :(得分:3)

根据申请的详细信息,您可以采用许多技巧/策略。

  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.
    }
    

答案 1 :(得分:0)

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

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

例如,您只能删除1000行:

DELETE FROM notifications WHERE message_store_id = 12345 LIMIT 1000;

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

SELECT SLEEP(2);

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