INSERT INTO SELECT 非常慢,但是 INSERT 或 SELECT 单独运行时很快

Mar*_*cel 8 sql database query-optimization mariadb sql-insert

我有一个这样的SQL:

INSERT INTO table1 (column1, column2) (
    SELECT column3, column4 FROM table2 WHERE column5 = 'value'
);
Run Code Online (Sandbox Code Playgroud)
  • table13,500,000 行。
  • table2900,000 行。
  • SELECT column3, column4 FROM table2 WHERE column5 = 'value'返回 NO 寄存器(零)并且需要大约 0.004 秒。
  • INSERT INTO table1 (column1, column2) VALUES ('value', 'value')也需要约 0.004 秒。

但是,当我将两者结合在一个INSERT INTO SELECT语句中时(如上所示),大约需要 7.7 秒。有解释吗?有解决办法吗?

Geo*_*ter 5

性能下降的原因很简单。这是 InnoDB 对表 t2 执行锁定以防止(基于语句的)复制中出现不一致的少数情况之一。

来自 MySQL手册

INSERT INTO T SELECT ... FROM S WHERE ... 在插入 T 的每一行上设置独占索引记录锁(不带间隙锁)。如果事务隔离级别是 READ COMMITTED,InnoDB 将在 S 上进行一致搜索读取(无锁)。否则,InnoDB 对 S 中的行设置共享下一个键锁。在后一种情况下,InnoDB 必须设置锁:在使用基于语句的二进制日志进行前滚恢复期间,每个 SQL 语句必须以完全相同的方式执行原来做的。

可能的解决方法(另请参阅 Harrison Fisk 的博客,写于 14 年前):

如果您使用基于语句的复制,可能唯一的解决方法是

SELECT * FROM table2 INTO outfile 'tmp.txt';
LOAD DATA INFILE 'tmp.txt' INTO TABLE table1;
Run Code Online (Sandbox Code Playgroud)

如果您使用基于行的复制,请将事务隔离级别设置为 READ COMMITTED。

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
Run Code Online (Sandbox Code Playgroud)

另一个(不安全)选项是启用服务器变量innodb_locks_unsafe_for_binlog


小智 1

有时,当您使用 SELECT 执行 INSERT 时,性能会变慢,因为源表中的字段类型与目标表中的字段类型不同,因此当执行 INSERT-SELECT 时,会对 SELECT 中的值进行隐式强制转换,因此它们可以被坚持在命运表中。这种隐式转换并不是以最佳性能方式进行的,并且有时从值直接插入会更慢。