繁体   English   中英

批量更新到大型InnoDB表中的未索引列

[英]bulk update to an unindexed column in a large InnoDB table

我在Mysql 5.1数据库中有一个InnoDB表,该表大约有2700万行。 该表具有三个我希望能够定期全局索引重置为“ 0”的未索引的mediumint unsigned列。 例如:

update myTable set countA = 0;

这个非常简单的更新查询正在解决InnoDB的行级锁定问题。 锁定过多的行后,更新查询将失败,并记录错误:

ERROR 1206 (HY000): The total number of locks exceeds the lock table size

问题在于,使用如此大的表,单个行锁的数量已超过为存储锁分配的空间。

我发现了一些有关如何处理此问题的建议:

锁定整个表以关闭行锁定
这似乎是最好,最干净的解决方案,并且在这些不常用的操作过程中,这个特殊的表被锁定了几分钟没有问题。 问题是,给定的解决方案实际上不适用于我。 也许它可以用于旧版本的Mysql?

增加锁定缓冲区的大小
通过增加Mysql变量innodb_buffer_pool_size的值,我们可以为行锁innodb_buffer_pool_size更多空间。 我对这种解决方案感到非常不舒服,因为即使我可以分配足够的空间,但随着表的增长,我都会为失败做准备。 同样,这似乎是一个糟糕的设置,需要创建千兆字节的无用锁。

索引受影响的列 (请参阅注释)
如果我们正在对相应索引支持的单个列进行批量更新,那么InnoDB可以避免锁定所有行。 通过使用索引,它只能锁定受影响的行。 我实际上进行了尝试,但是发现管理这三个索引使我的增量更新慢得多。 由于我将有数千万的更新查询针对需要重置计数的每个实例调整这三个计数,因此我不想牺牲增量更新的效率。

批量更新列
源文档将其描述为一种变通方法,但我发现它在一定程度上非常有效:

update myTable set countA = 0 where countA != 0 limit 500000;

通过重复执行此操作直到受影响的行数小于指定的limit ,所有行都将得到更新。 对于我来说,这种解决方案在特别大的表上不可行,因为一次迭代可以更新的行数急剧下降,因为Mysql必须进一步寻找匹配的行。 到更新1,000行时,一次执行的行太多了,我仍然有数百万个非零值需要更新。

那我还有什么可能性呢?

  1. 停止使用InnoDB:这将需要对我当前的流程进行其他重组,但这是我要考虑的事情。
  2. 将计数列移出主表:如果我有CountA表,则可以通过使用delete from CountA来重置计数,并且可以使用针对主表的内部delete from CountA来检索计数。 这将减慢我对单个计数的更新,因为在有条件地更新或在CountA表中插入一行之前,我必须从主表获取ID。 不太好,但是我会考虑的。
  3. 还有什么既是干净的解决方案,又可以期望在我的桌子上合理增长的解决方案?

更新:在已接受的响应的帮助下,我现在有了一个批处理实现,该实现可以在大约五分钟内完成。 虽然我宁愿批处理就没有必要,直到一个更直接的解决方案来临时它似乎是。 如果它可以帮助下一个人迷失这个问题,这是我相关的Java JDBC代码。 (建议您阅读接受的答案链接的博客文章。)

    int batchsize = 10_000;
    PreparedStatement pstmt = connection.prepareStatement
            ("UPDATE tableName SET countA = 0, countB = 0, countC = 0 "
                       + "WHERE id BETWEEN ? AND ?");
    for (int left = 0; left < maxId; left += batchsize) {
        pstmt.setInt(1, left + 1);
        pstmt.setInt(2, left + batchsize);
        pstmt.executeUpdate();
    }
    pstmt.close();

计划A

我喜欢分块。 但是,您的代码草图不是很有效。 添加OFFSET无济于事。 取而代之的是, 请参阅我的博客,了解如何仔细地浏览表格 即找到“下一个” 100-1000行; 执行UPDATE ; 环。 (注意:每个块应该是自己的事务。)

“查找下N行并记住您离开的位置”的技术取决于PRIMARY KEY 我的博客涵盖了大多数情况(数字,字符串,稀疏等)。 (该博客讨论了DELETE ,但应该易于适应UPDATE 。)

InnoDB对于分块很有用,因为PRIMARY KEY是集群的。 因此,每个块将必须读取最少数量的块。

计划B

使用并行表(“将计数列从主表中移出”)可能是一个好主意,因为要触摸的磁盘块数量会更少,因此可以类似于计划A,但速度更快。 使用相同的PRIMARY KEY (不使用AUTO_INCREMENT )。

C计划

(1)并行表(如Plan B),再加上(2)缺少的行表示值= 0。 然后,通过TRUNCATE TABLE实现清除(与Plan A不同)。 由于您有三列需要清除,因此规则是

  • 当任何值更改为非零时,请确保该行存在于并行表中,并根据需要设置该值(其他值加零)。 可能是INSERT ... ON DUPLICATE KEY UPDATE...是最佳的。
  • 查找值( SELECT )时,请执行LEFT JOINIFNULL(col, 0)以获得值或0。

计划X(非入门级)

为列建立索引会很麻烦-当您更新已索引的列时,必须同时更改数据和索引。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM