我有一个 MySQL 数据库(InnoDB,如果重要的话),我想添加很多行。我想在生产数据库上执行此操作,这样就不会停机。每次(大约每天一次)我想向数据库添加大约 100 万行,每批 10k(从我运行的一些测试来看,这似乎是最小化时间的最佳批量大小)。当我在做这些插入时,表格需要可读。这样做的“正确”方法是什么?对于初学者,您可以假设没有索引。
选项 A:https://dev.mysql.com/doc/refman/5.7/en/commit.html
开始交易;
INSERT INTO my_table(等批量插入);
INSERT INTO my_table(等批量插入);
INSERT INTO my_table(等批量插入);
INSERT INTO my_table(等批量插入);
(更多的)
犯罪;
设置自动提交 = 0;
选项B
将 my_table 复制到 my_table_temp
INSERT INTO my_table_temp(等批量插入);
INSERT INTO my_table_temp(等批量插入);
INSERT INTO my_table_temp(等批量插入);
INSERT INTO my_table_temp(等批量插入);
(更多的)
将 my_table 重命名为 my_table_old;
将 my_table_temp 重命名为 my_table;
我以前用过第二种方法,效果很好。只有很少的时间会出现问题,即重命名表所花费的时间。
但我的困惑是:如果这是最好的解决方案,那么 START TRANSACTION
/COMMIT
有什么意义?这肯定是为了解决我所描述的问题而发明的,不是吗?
额外的问题:如果我们有索引怎么办?我的情况很容易适应,只需关闭临时表中的索引,然后在插入完成后和重命名之前重新打开它们。选项A呢?似乎很难与使用索引进行插入相协调。
最佳答案
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 ,作者永远不会阻止读者。您不必担心批量插入会阻塞读者。
异常(exception)情况是,如果您使用 SELECT...FOR UPDATE
或 SELECT...LOCK IN SHARE MODE
进行锁定读取 .这些可能会与 INSERT 冲突,具体取决于您选择的数据,以及它是否需要插入新数据的位置的间隙锁。
同样,LOAD DATA INFILE
不会阻止表的非锁定读取器。
您可能希望在我的演示文稿 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 进行表交换,但就其值(value)而言,如果您确实需要这样做,您可以在一个语句中进行多个表重命名。该操作是原子操作,因此任何并发事务都不可能潜入其中。
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 花费更长的时间而不是禁用索引。
如果禁用索引,则所有 并发客户端都无法使用它。其他查询会变慢。此外,当您重新启用索引时,这将在重建索引时锁定表并阻止其他查询。避免这种情况。
why do I need to wrap the thing in "START TRANSACTION/COMMIT"?
事务的主要目的是将应该作为一个更改提交的更改分组,以便其他并发查询不会看到处于部分完成状态的更改。理想情况下,我们会在一次事务中为您的批量加载执行所有 INSERT。
事务的次要目的是减少开销。如果您依赖自动提交而不是显式启动和提交,您仍在使用事务——但自动提交会隐式启动并为每个 INSERT 语句提交一个事务。启动和提交的开销很小,但如果执行 100 万次,开销就会加起来。
减少单笔交易的数量也有实际的物理原因。 InnoDB 默认情况下在每次提交后进行文件系统同步,以确保数据安全地存储在磁盘上。如果发生崩溃,这对于防止数据丢失很重要。但是文件系统同步不是免费的。您每秒只能进行有限数量的同步(这取决于您使用的磁盘类型)。因此,如果您尝试为单个事务执行 100 万次同步,但您的磁盘每秒只能进行 100 次物理同步(这对于非 SSD 类型的单个硬盘来说是典型的),那么您的批量加载至少需要10,000 秒。这是将批量 INSERT 分组的一个很好的理由。
因此,出于原子更新的逻辑原因和善待硬件的物理原因,当您有一些批量工作要做时,请使用事务。
但是,我不想吓唬你使用事务来不恰本地分组。在您执行某些其他类型的 UPDATE 之后,请务必立即提交您的工作。让交易无限期地挂起也不是一个好主意。 MySQL 可以处理普通日常工作的提交率。当您需要快速连续地进行大量更改时,我建议进行批处理。
关于MySQL:执行这些多批处理 INSERT 的最佳方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47953202/