首先,有一些必要的背景(拜托,请耐心等待)。我是一名使用MySQL进行持久性的Web应用程序的开发人员。我们通过为每个数据表创建审计跟踪表来实现审计日志记录。例如,我们可以为Customer实体提供以下表定义:
-- Data table definition.
CREATE TABLE my_database.customers (
CustomerId INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
FirstName VARCHAR(255) NOT NULL,
LastName VARCHAR(255) NOT NULL,
-- More data columns, removed for simplicity.
...
);
-- Audit table definition in separate schema.
CREATE TABLE my_database_audittrail.customers (
CustomerId INT(11) DEFAULT NULL,
FirstName VARCHAR(255) DEFAULT NULL,
LastName VARCHAR(255) DEFAULT NULL,
-- More data columns.
...
-- Audit meta data columns.
ChangeTime DATETIME NOT NULL,
ChangeByUser VARCHAR(255) NOT NULL
);
如您所见,审计表只是数据表的副本加上一些元数据。请注意,审计表没有任何键。例如,当我们更新客户时,我们的ORM会生成类似于以下内容的SQL:
-- Insert a copy of the customer entity, before the update, into the audit table.
INSERT INTO my_database_audittrail.customers (
CustomerId,
FirstName,
LastName,
...
ChangeTime,
ChangeByUser)
)
SELECT
CustomerId,
FirstName,
LastName,
...
NOW(),
@ChangeByUser
FROM my_database.customers
WHERE CustomerId = @CustomerId;
-- Then update the data table.
UPDATE
my_database.customers
SET
FirstName = @FirstName,
LastName = @LastName,
...
WHERE CustomerId = @CustomerId;
这已经足够好了。但是,最近,出于各种原因,我们需要将一个主键列添加到审计表中,将审计表定义更改为类似于以下内容:
CREATE TABLE my_database_audittrail.customers (
__auditId INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
CustomerId INT(11) DEFAULT NULL,
FirstName VARCHAR(255) DEFAULT NULL,
LastName VARCHAR(255) DEFAULT NULL,
...
ChangeTime DATETIME NOT NULL,
ChangeByUser VARCHAR(255) NOT NULL
);
我们的ORM在更新数据表时生成的SQL尚未修改。这种变化似乎极大地增加了死锁的风险。有问题的系统是一个带有大量夜间批处理作业的Web应用程序。我们的网络用户在日常使用系统时未显示死锁的增加。然而,夜间批处理作业确实遭受了死锁,因为他们在一些数据库表上做了大量工作。我们的“解决方案”一直是添加一个重试死锁策略(几乎没有争议),虽然这似乎工作正常,但我非常希望理解为什么上述变化会增加死锁的风险(如果我们可以的话)以某种方式解决问题)。
更多信息:
更新:选中SHOW ENGINE INNODB STATUS
以确定死锁的原因并找到了:
*** WAITING FOR THIS LOCK TO BE GRANTED:
TABLE LOCK table `my_database_audittrail`.`customers` trx id 24972756464 lock mode AUTO-INC waiting
我认为自动增量是在任何交易之外处理的,以避免在不同交易中使用相同的自动增量值?但我想我们引入的主键上的AUTO_INCREMENT属性似乎是问题吗?
答案 0 :(得分:0)
这是推测。
使用索引插入或更新到表中不仅会锁定数据页,还会锁定索引页,包括索引的更高级别。当多个线程同时影响记录时,它们可能会锁定索引的不同部分。
这通常不会显示单个记录插入。但是,更新多个记录的两个语句可能会开始获取索引上的锁,并发现它们相互死锁。重试可能足以解决此问题。或者,似乎"太多"可能正在运行,您可能想要考虑每晚更新工作的布局。
答案 1 :(得分:0)
当插入具有自动增量列的表时,MySQL使用不同的策略来获取自动增量列的值,具体取决于插入的类型,插入语句以及MySQL如何配置为处理自动增量列,插入可能会导致完整的表锁定。
使用"简单插入",即插入,MySQL可以预先确定将插入表中的行数(例如INSERT INTO表(col1,col2)VALUES(val1,val2); )使用自动增量计数器上的轻量级锁定获取自动增量列值。一旦获得自动增量值,就会释放这种轻量级锁定,因此不必等到实际插入完成。我没有桌子锁。
然而,使用"批量插入",其中MySQL无法确定手前插入的行数(例如INSERT INTO表(col1,col2)SELECT col1,col2 FROM table2 WHERE ...;)a创建表锁以获取自动增量列值,并且在插入完成之前不会放弃。
以上是MySQL的默认配置。可以将MySQL配置为不对批量插入使用表锁,但这可能导致自动增量列在主服务器和从服务器上具有不同的值(如果已设置复制),因此可能是也可能不是可行选项。