MySQL:执行这些多批插入的最佳方法是什么?

MySQL: What is the best way to do these multiple batch INSERTs?

我有一个 MySQL 数据库(InnoDB,如果重要的话),我想添加很多行。我想在生产数据库上执行此操作,这样就不会停机。每次(大约每天一次)我想向数据库添加大约 100 万行,每批 10k(从一些测试我 运行 这似乎是最小化时间的最佳批量大小)。在我进行这些插入时,table 需要可读。 "correct" 的方法是什么?对于初学者,您可以假设没有索引。

选项 A:https://dev.mysql.com/doc/refman/5.7/en/commit.html

START TRANSACTION; INSERT INTO my_table (etc etc batch insert); INSERT INTO my_table (etc etc batch insert); INSERT INTO my_table (etc etc batch insert); INSERT INTO my_table (etc etc batch insert); (more) COMMIT; SET autocommit = 0;

选项 B

copy my_table into my_table_temp INSERT INTO my_table_temp (etc etc batch insert); INSERT INTO my_table_temp (etc etc batch insert); INSERT INTO my_table_temp (etc etc batch insert); INSERT INTO my_table_temp (etc etc batch insert); (more) RENAME my_table TO my_table_old; RENAME my_table_temp TO my_table;

我以前用过第二种方法,很管用。只有很短的时间可能会出现问题,即重命名 tables 所需的时间。

但我的困惑是:如果这是最好的解决方案,那么 START TRANSACTION/COMMIT 的意义何在?这肯定是为了解决我所描述的问题而发明的,不是吗?

额外问题:如果我们有索引怎么办?我的情况很容易适应 table,只需关闭临时 table 中的索引,然后在插入完成后和重命名之前重新打开它们。选项A呢?似乎很难与使用索引进行插入相协调。

我认为最好的方法是LOAD DATA IN FILE

then what's the point of START TRANSACTION/COMMIT? Surely that was invented to take care of the thing I'm describing, no?

是的,没错。在 InnoDB 中,由于它的 MVCC architecture,写入器永远不会阻塞读取器。您不必担心批量插入会阻塞读取器。

如果您使用 SELECT...FOR UPDATESELECT...LOCK IN SHARE MODE 进行 锁定读取 ,则例外。这些可能与 INSERT 冲突,具体取决于您选择的数据,以及它是否需要插入新数据的位置的间隙锁。

同样,LOAD DATA INFILE 不会阻止 table.

的非锁定读者

您可能希望在我的演示文稿中看到我获得的批量加载数据的结果,Load Data Fast!

There's only a tiny amount of time where something might be wrong which is the time it takes to rename the tables.

没有必要为批量 INSERT 进行 table 交换,但就其价值而言,如果您确实需要这样做,您可以在一条语句中进行多次 table 重命名.该操作是原子操作,因此任何并发事务都不可能潜入其中。

RENAME my_table TO my_table_old, my_table_temp TO my_table;

回复您的评论:

what if I have indexes?

让索引在您执行 INSERT 或 LOAD DATA INFILE 时增量更新。 InnoDB 将在其他并发读取使用索引时执行此操作。

在 INSERT 期间更新索引会产生开销,但通常最好让 INSERT 花费更长的时间而不是禁用索引。

如果禁用索引,则 所有 个并发客户端将无法使用它。其他查询会变慢。此外,当您重新启用索引时,这将在重建索引时锁定 table 并阻止其他查询。避免这种情况。

why do I need to wrap the thing in "START TRANSACTION/COMMIT"?

事务的主要目的是将应作为一个更改提交的更改分组,以便其他并发查询不会看到处于部分完成状态的更改。理想情况下,我们会在一次事务中为您的批量加载执行所有 INSERT。

事务的次要目的是减少开销。如果您依赖自动提交而不是显式启动和提交,您仍在使用事务——但是自动提交隐式启动并为每个 INSERT 语句提交一个事务。启动和提交的开销很小,但是如果你执行 100 万次就会加起来。

减少单笔交易的数量也有实际的物理原因。 InnoDB 默认情况下在每次提交后进行文件系统同步,以确保数据安全地存储在磁盘上。如果发生崩溃,这对于防止数据丢失很重要。但是文件系统同步不是免费的。您每秒只能进行有限数量的同步(这取决于您使用的磁盘类型)。因此,如果您尝试为单个事务执行 100 万次同步,但您的磁盘每秒只能进行 100 次物理同步(这对于非 SSD 类型的单个硬盘来说是典型的),那么您的批量加载至少需要10,000 秒。这是将批量 INSERT 分组的一个很好的理由。

因此,出于原子更新的逻辑原因和善待硬件的物理原因,当您有一些批量工作要做时,请使用事务。

但是,我不想吓唬你使用事务来不恰当地分组。在您执行某些其他类型的 UPDATE 之后,请务必立即提交您的工作。让交易无限期地挂起也不是一个好主意。 MySQL 可以处理普通日常工作的提交率。当您需要快速连续地进行大量更改时,我建议进行批处理。