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 UPDATE
或 SELECT...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 可以处理普通日常工作的提交率。当您需要快速连续地进行大量更改时,我建议进行批处理。
我有一个 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 UPDATE
或 SELECT...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 可以处理普通日常工作的提交率。当您需要快速连续地进行大量更改时,我建议进行批处理。