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

时间:2010-04-14 20:44:13

标签: mysql select insert locking innodb

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

INSERT INTO TemporaryTable
    (SELECT FROM HighlyContentiousTableInInnoDb
     WHERE allKindsOfComplexConditions are true)

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

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

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

9 个答案:

答案 0 :(得分:23)

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

您遇到的锁定消失了。

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

答案 1 :(得分:5)

  

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

这通常是正确的,但是有一个值得注意的例外 - INSERT INTO table1 SELECT * FROM table2。此语句将对table2表执行锁定读取(共享锁定)。它也适用于where子句和连接的类似表。正在读取的表是Innodb非常重要 - 即使写入是在MyISAM表中完成的。

  

那么为什么要这样做,对MySQL性能而言非常糟糕   并发?

原因是 - 复制。在5.1之前的MySQL中,复制是基于语句的,这意味着在主服务器上回复的语句应该产生与从服务器相同的效果。如果Innodb不会锁定源表中的行,则其他事务可以在运行INSERT .. SELECT语句的事务之前修改行并提交。这将使该事务在INSERT ... SELECT语句之前应用于从属,并且可能导致与master不同的数据。在读取它们时锁定源表中的行可以防止这种影响,因为其他事务在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。这是这样做的   启用二进制日志不会导致锁定的意外更改   行为和性能问题。您也可以使用此选项   有时复制,如果你真的知道你在做什么。我会   不推荐它,除非你真的需要,因为你可能不知道   在未来的版本中将放松哪些其他锁定以及它将如何   影响你的复制。

答案 2 :(得分:5)

您可以设置binlog格式:

SET GLOBAL binlog_format = 'ROW';

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

[mysqld]
binlog_format=ROW

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

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
INSERT INTO t1 SELECT ....;

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

SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

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

[mysqld]
transaction-isolation = READ-UNCOMMITTED

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

答案 3 :(得分:1)

您可以使用“创建视图”命令(请参阅Create View Syntax)。 例如,

Create View temp as SELECT FROM HighlyContentiousTableInInnoDb WHERE allKindsOfComplexConditions are true

之后,您可以在此视图中使用insert语句。 像这样的东西

INSERT INTO TemporaryTable (SELECT * FROM temp)

这只是我的建议。

答案 4 :(得分:1)

免责声明:我对数据库不是很熟悉,而且我不确定这个想法是否可行。如果不是,请纠正我。

如何在第一个表中设置辅助等效表HighlyContentiousTableInInnoDb2,并创建AFTER INSERT等触发器,以便使用相同的数据更新新表。现在您应该能够锁定HighlyContentiousTableInInnoDb2,并且只会减慢主表的触发器,而不是所有查询。

潜在问题:

  • 存储2 x数据
  • 所有插入,更新和删除的附加工作
  • 可能没有交易声音

答案 5 :(得分:1)

如果你可以允许一些异常,你可以将ISOLATION LEVEL改为最不严格的一个 - READ UNCOMMITTED。但在此期间,有人可以从您的目的地表中读取。或者您可以手动锁定目标表(我假设mysql提供此功能?)。

或者您可以使用READ COMMITTED,它也不应该锁定源表。但它也会锁定目标表中的插入行,直到提交。

我会选择第二个。

答案 6 :(得分:0)

锁定(readlock)的原因是确保您的读取事务不会读取并行事务当前正在编写的“脏”数据。 大多数DBMS提供用户可以设置和撤销读取和放大的设置。手动写锁。如果在您的情况下读取脏数据不是问题,这可能对您有意义。

我认为没有安全的方法可以从没有任何锁定的表中读取多个事务的DBS。

但以下是一些头脑风暴: 如果空间没有问题,您可以考虑运行同一个表的两个实例。 HighlyContentiousTableInInnoDb2用于您的持续读/写事务,HighlyContentiousTableInInnoDb2_shadow用于您的批量访问。 也许你可以通过DBMS内部的触发器/例程自动填充影子表,这比在任何地方进行额外的写入事务更快更聪明。

另一个想法是问题:所有交易都需要访问整个表吗? 否则,您可以使用视图仅锁定必要的列。如果连续访问和批量访问关于列是不相交的,那么它们可能不会相互锁定!

答案 7 :(得分:0)

我不熟悉MySQL,但希望SQL Server中的事务隔离级别SnapshotRead committed snapshot等效。使用其中任何一个都可以解决您的问题。

答案 8 :(得分:0)

我使用CREATE TEMPORARY TABLE ... SELECT ... SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction时遇到了同样的问题。

根据您的初始查询,我的问题通过在开始查询之前锁定HighlyContentiousTableInInnoDb来解决。

LOCK TABLES HighlyContentiousTableInInnoDb READ;
INSERT INTO TemporaryTable
    (SELECT FROM HighlyContentiousTableInInnoDb
    WHERE allKindsOfComplexConditions are true)
UNLOCK TABLES;