mariadb - UPDATE 引用更新表中的字段会导致死锁吗?
问题描述
我有一个这样的查询:
UPDATE loginlogs SET rxbytes = rxbytes + ?, txbytes = txbytes + ? WHERE logid = ?
有时,我的数据库会陷入死锁并SELECT * FROM sys.innodb_lock_waits
显示如下挂起的锁:
wait_started wait_age wait_age_secs locked_table locked_index locked_type waiting_trx_id waiting_trx_started waiting_trx_age waiting_trx_rows_locked waiting_trx_rows_modified waiting_pid waiting_query waiting_lock_id waiting_lock_mode blocking_trx_id blocking_pid blocking_query blocking_lock_id blocking_lock_mode blocking_trx_started blocking_trx_age blocking_trx_rows_locked blocking_trx_rows_modified sql_kill_blocking_query sql_kill_blocking_connection
2020-12-27 07:43:32 00:00:04 4 `db`.`loginlogs` PRIMARY RECORD 37075679818 2020-12-27 07:43:32 00:00:04 1 0 19194139 UPDATE loginlogs SET ... HERE logid = 64634225227638257 37075679818:921:15944673:61 X 37075617021 19191704 UPDATE loginlogs SET ... HERE logid = 64634225227638257 37075617021:921:15944673:61 X 2020-12-27 07:43:07 00:00:29 1 0 KILL QUERY 19191704 KILL 19191704
如您所见,两个相同的查询似乎同时运行。第二个正在等待第一个完成。
我认为 MySQL 应该像这样处理简单的 UPDATE 查询。rxbytes
我是否需要先选择字节,然后在不引用txbytes
新值的情况下进行更新?
顺便说一句,这在从 MariaDB 10.4.2 更新到 10.4.17 后开始发生,因此我也怀疑 MariaDB 错误并打开了错误报告。
解决方案
锁等待不是死锁!
我经常看到这种误解。
您所展示的是锁定等待。也就是说,一个事务正在等待另一个事务。每个 UPDATE 都会锁定它检查的行,并且任何并发的 UPDATE 都必须等待第一个 COMMIT 来释放这些行锁。这是一个锁定等待。这是正常和常见的。
僵局是不同的。这是两个事务进入相互锁定等待的地方。UPDATE1 锁定一些行,但尚未提交。然后 UPDATE2 尝试更新相同的行,并开始等待。然后 UPDATE1 的事务尝试另一个锁定语句,该语句需要锁定 UPDATE2 的事务已经持有的某些行,可能来自它运行的先前语句。因此,两个事务都在等待另一个事务,并且不会提交释放它们持有的锁,因为它们正在等待。死锁之所以如此命名,是因为没有办法解决相互等待。
https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks.html说:
死锁是一种不同事务无法继续进行的情况,因为每个事务都持有另一个需要的锁。因为两个事务都在等待资源变得可用,所以它们都不会释放它持有的锁。
您不会遇到真正死锁的延迟。MySQL 监视这些循环锁定等待并强制其中一个回滚其事务。这几乎是立即发生的,因此几乎不需要等待。
那么,当您从 MariaDB 10.4.2 升级到 10.4.17 时,为什么会发生这种情况。显然,某些更改会影响锁定的行数或事务的持续时间,以使您更有可能以这种方式发生并发事务冲突。
否则,软件并没有改变任何与锁定或事务相关的内容,但巧合的是,当您升级到新版本的 MariaDB 时,您的流量发生了变化。