Mysql 添加主键时死锁增加。为什么?

Mysql 添加主键时死锁增加。为什么?,mysql,sql,database,Mysql,Sql,Database,首先,一点必要的背景知识(请耐心听我说)。我是一名web应用程序的开发人员,使用MySQL实现持久性。我们通过为每个数据表创建审计跟踪表来实现审计日志记录。例如,我们可以为客户实体提供以下表定义: -- Data table definition. CREATE TABLE my_database.customers ( CustomerId INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, FirstName VARCHAR(255) NOT N

首先,一点必要的背景知识(请耐心听我说)。我是一名web应用程序的开发人员,使用MySQL实现持久性。我们通过为每个数据表创建审计跟踪表来实现审计日志记录。例如,我们可以为客户实体提供以下表定义:

-- 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
);
这已经足够有效了。但是,最近出于各种原因,我们需要向审计表中添加一个主键列,将审计表定义更改为类似于以下内容:

-- 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应用程序,具有许多夜间批处理作业。死锁的增加并没有体现在我们的网络用户对系统的日常使用中。然而,夜间批处理作业确实会受到死锁的影响,因为它们在一些数据库表上进行大量的工作。我们的“解决方案”是添加一个死锁重试策略(几乎没有争议),虽然这似乎很好,但我非常想理解为什么上述更改会增加死锁的风险那么多(如果我们能够以某种方式解决问题)

进一步资料:

  • 我们的夜间批处理作业对数据表进行插入、更新和删除。仅对审核表执行插入
  • 我们在out数据库事务上使用可重复读取隔离级别
  • 在此更改之前,我们在运行夜间批处理作业时没有看到任何死锁
更新:选中“显示引擎INNODB状态”以确定死锁的原因,并发现以下问题:

*** WAITING FOR THIS LOCK TO BE GRANTED:
TABLE LOCK table `my_database_audittrail`.`customers` trx id 24972756464 lock mode AUTO-INC waiting
我的印象是,为了避免在不同的事务中使用相同的自动增量值,自动增量是在任何事务之外处理的?但我想我们介绍的主键上的自动增量属性似乎是问题所在?

这是推测

在具有索引的表中插入或更新不仅会锁定数据页,还会锁定索引页,包括更高级别的索引。当多个线程同时影响记录时,它们可能会锁定索引的不同部分

这通常不会出现在单记录插入中。但是,正在更新多个记录的两条语句可能会开始获取索引上的锁,并发现它们彼此处于死锁状态。重试可能足以解决此问题。另一方面,似乎“太多”可能同时运行,你可能想考虑每晚更新工作是如何安排的。

< P>这是猜测。

在具有索引的表中插入或更新不仅会锁定数据页,还会锁定索引页,包括更高级别的索引。当多个线程同时影响记录时,它们可能会锁定索引的不同部分


这通常不会出现在单记录插入中。但是,正在更新多个记录的两条语句可能会开始获取索引上的锁,并发现它们彼此处于死锁状态。重试可能足以解决此问题。另一方面,似乎“太多”可能同时运行,您可能想考虑每晚更新工作是如何安排的。

插入到具有自动增量列的表中时,MySQL使用不同的策略来获取自动增量列的值,这取决于插入的类型,在insert语句和MySQL如何配置以处理自动增量列上,insert可能会导致一个完整的表锁

使用“简单插入”,即MySQL可以事先确定要插入到表中的行数的插入(例如插入到表中(col1,col2)值(val1,val2);)自动增量列值是使用自动增量计数器上的轻量级锁获取的。一旦获得自动增量值,就会释放此轻量锁,这样就不必等到实际插入完成。也就是说,没有桌子锁

但是,对于“批量插入”,MySQL无法确定之前插入的行数(例如,插入到表中(col1,col2)从表2中选择col1,col2,其中…;),将创建一个表锁以获取自动增量列值,并且在插入完成之前不会放弃


以上是MySQL的默认配置。MySQL可以配置为在大容量插入时不使用表锁,但这可能会导致自动增量列在主列和从列上具有不同的值(如果设置了复制),因此可能是可行的选项,也可能不是可行的选项。

插入具有自动增量列的表时,MySQL使用不同的策略获取自动递增列的值,具体取决于插入的类型、insert语句以及MySQL处理自动递增列的配置方式,insert可能会导致完整的表锁

使用“简单插入”,即MySQL可以事先确定要插入到表中的行数的插入(例如插入到表中(col1,col2)值(val1,val2);)自动增量列值是使用自动增量计数器上的轻量级锁获取的。一旦获得自动增量值,就会释放此轻量锁,这样就不必等到实际插入完成。也就是说,没有桌子锁

但是,对于“批量插入”,MySQL无法确定之前插入的行数(例如,插入到表中(col1,col2)从表2中选择col1,col2,其中…;)会为