如何改进INSERT INTO ... SELECT锁定行为

Art*_*tem 39 mysql select innodb locking insert

在我们的生产数据库中,我们运行以下每小时运行的伪代码SQL批处理查询:

INSERT INTO TemporaryTable
    (SELECT FROM HighlyContentiousTableInInnoDb
     WHERE allKindsOfComplexConditions are true)
Run Code Online (Sandbox Code Playgroud)

现在这个查询本身并不需要很快,但我注意到它是锁定的HighlyContentiousTableInInnoDb,即使它只是从它读取.其他一些非常简单的查询需要大约25秒(这是其他查询需要多长时间).

然后我发现在这种情况下InnoDB表实际上是被SELECT锁定的!http://www.mysqlperformanceblog.com/2006/07/12/insert-into-select-performance-with-innodb-tables/

但我真的不喜欢选择OUTFILE的文章中的解决方案,它似乎是一个黑客(文件系统上的临时文件看起来很糟糕).还有其他想法吗?有没有办法制作InnoDB表的完整副本,而不会在复制过程中以这种方式锁定它.然后我可以将其复制HighlyContentiousTable到另一个表并在那里进行查询.

Mor*_*ker 23

现在,这个问题的答案要容易得多: - 使用基于行的复制和读取提交的隔离级别.

您遇到的锁定消失了.

更长的解释:http://harrison-fisk.blogspot.com/2009/02/my-favorite-new-feature-of-mysql-51.html

  • 'now'表示5.1 (7认同)
  • 刚刚在这个问题上添加了 +50 赏金,以获得上述问题的更详细、逐步的答案。 (2认同)

Vip*_*ain 5

每个使用Innodb表的人都可能会习惯于Innodb表执行非锁定读取的事实,这意味着除非您使用某些修饰符,例如LOCK IN SHARE MODE或FOR UPDATE,否则SELECT语句在运行时不会锁定任何行。

通常这是正确的,但是有一个明显的例外– INSERT INTO table1 SELECT * FROM table2。该语句将对table2表执行锁定读取(共享锁)。它也适用于带有where子句和联接的类似表。对于正在读取的表来说,将其读为Innodb是很重要的-即使在MyISAM表中完成写操作。

那么,为什么这样做会给MySQL性能和并发性带来不利影响呢?

原因是–复制。在MySQL中,在5.1之前的复制是基于语句的,这意味着在主服务器上复制的语句应引起与从服务器上相同的效果。如果Innodb不会锁定源表中的行,则其他事务可以修改该行并在运行INSERT .. SELECT语句的事务之前提交。这将使此事务在INSERT…SELECT语句之前应用到从属服务器上,并可能导致与主服务器上的数据不同。在读取源表时将其锁定在表中可以防止这种影响,因为其他事务会在INSERT之前修改行。SELECT有机会访问它,它也会在从属服务器上以相同顺序进行修改。如果事务在访问该行并试图被INSERT…SELECT锁定后试图修改该行,事务必须等待语句完成,以确保它将以正确的顺序在从站上执行。变得很复杂吗?好吧,您需要知道的所有内容都必须完成,然后复制才能在5.1之前的MySQL中正常工作。

在MySQL 5.1中,这个问题以及其他一些问题都应该通过基于行的复制来解决。但是,我尚未对其进行真正的压力测试,以查看其性能如何:)

需要考虑的另一件事– INSERT…SELECT实际上以锁定模式执行读取,因此部分绕过版本控制并检索最新的提交行。因此,即使您在REPEATABLE-READ模式下操作,该操作也将在READ-COMMITTED模式下执行,与纯粹的SELECT所提供的结果可能不同。顺便说一下,这也适用于SELECT .. LOCK IN SHARE MODE和SELECT…FOR UPDATE。

我的一个问题是,如果我不使用复制功能并且禁用了二进制日志,那该怎么办?如果不使用复制,则可以启用innodb_locks_unsafe_for_binlog选项,该选项将放宽Innodb在语句执行时设置的锁,这通常会提供更好的并发性。但是,顾名思义,它会使锁不安全地进行复制和时间点恢复,因此请谨慎使用innodb_locks_unsafe_for_binlog选项。

请注意,禁用二进制日志不足以触发宽松的锁。您还必须设置innodb_locks_unsafe_for_binlog = 1。这样做是为了启用二进制日志不会导致锁定行为和性能问题的意外更改。如果您确实知道自己在做什么,有时还可以将此选项与复制一起使用。除非确实需要,否则我不建议您这样做,因为您可能不知道将来的版本中哪些其他锁将被放宽,以及它将如何影响您的复制。


dia*_*dog 5

您可以像这样设置binlog格式:

SET GLOBAL binlog_format = 'ROW';
Run Code Online (Sandbox Code Playgroud)

编辑my.cnf如果你想永久制作:

[mysqld]
binlog_format=ROW
Run Code Online (Sandbox Code Playgroud)

在运行查询之前设置当前会话的隔离级别:

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
INSERT INTO t1 SELECT ....;
Run Code Online (Sandbox Code Playgroud)

如果这没有帮助,您应该尝试在服务器范围内设置隔离级别,而不仅仅是当前会话:

SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Run Code Online (Sandbox Code Playgroud)

编辑my.cnf如果你想永久制作:

[mysqld]
transaction-isolation = READ-UNCOMMITTED
Run Code Online (Sandbox Code Playgroud)

您可以将READ-UNCOMMITTED更改为READ-COMMITTED,这是一个更好的隔离级别.