设为首页 - 加入收藏  
您的当前位置:首页 >数据库 >我一个INSERT 还能被你 UPDATE 给卡住? 正文

我一个INSERT 还能被你 UPDATE 给卡住?

来源:汇智坊编辑:数据库时间:2025-11-05 04:58:52

许多语言和工具都通过锁,来保证并发场景下数据和逻辑的正确性,MySQL 也不例外。除了行锁、表锁这种范围粒度外,还有这种针对读和写的 S锁共享锁 和 X锁独占锁。

随着锁定范围的不同,锁与锁之间的互相影响也差异很大,这一点很好理解。比如一个操作加了表锁之后,另一个想加行锁就得等待;而一个行锁一般并不会影响锁另一行的行锁。

除了书本上和八股文,你有没有遇到过这些锁相关的问题呢?

我先来说一个最近遇到的。

现象

某天,项目出现几条监控报警,都是在写库的时候获取锁超时导致。业务会在某种特定的场景下,出现如下的 MySQL 获取锁超时,事务回滚的异常。

复制

org.springframework.dao.CannotAcquireLockException

:

### Error updating database

.

Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: xxxLock wait timeout exceeded; try restarting transaction1.2.3.4.5.

看了下错误对应的日志,发现当时 MySQL 要执行一条 INSERT 操作,网站模板等了50秒超时事务回滚了。同样的代码时好时坏,那就一定和触发条件有关系了。

复制

对应正在执行的是一个 INSERT 的操作2022-xx-xx 15:x:x.380 [elapse:50674] [sql:INSERT INTO xxx_table ....]1.2.3.

按照前面的固有思路,即将执行 INSERT 的一行数据,理论上和别人没什么的冲突,为啥会拿不到锁呢?

在代码逻辑里也不能明确定位问题,只能求助 DBA 帮忙 dump 事务日志相关信息。

但内容里也没有死锁信息,事务日志里也仅有 Transaction 在等待锁的信息,像这个样子,大概看了一眼,不像死锁日志里有一个 Hold Locks 信息,这种普通的情况,具体锁在谁手里,还是两眼一抹眼。

复制---------------------TRANSACTION 13934594, ACTIVE 41 sec insertingmysql tables in use 1, locked 1LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1MySQL thread id 6695850, OS thread handle 0x7ef74b2c0700, query id 123 xxx abc updateINSERT INTO xxx_table(col,col1,...

)

------- TRX HAS BEEN WAITING 41 SEC FOR THIS LOCK TO BE GRANTED

:

RECORD LOCKS space id 1057 page no 3724 n bits 312 index `xxx_id_idx` of table `test`.`xxx_table` trx id 13934594 lock_mode X locks gap before rec insert intention waitingRecord lock, heap no 241 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 8; hex 800000008d8faf7d; asc

};;

1: len 5; hex ....123; asc 12_34

;;

2: len 17; hex 111111111111113732333338; asc 111111111111111272338

;;

3: len 1; hex 80; asc

;;

4: len 8; hex 8000000000012adf; asc *

;;

------------------1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.

没有其它办法,只好回过来仔细看事务日志。仔细看这里的 WAITING xx for this lock 下面,会提到这个等待锁的类型:

复制RECORD LOCKS index `xxx_id_idx` of table `test`.`xxx_table` lock_mode X locks gap before rec insert intention waiting1.

期中提到了

复制lock_mode X locks gap before rec insert intention waiting1.

是 GAP 锁,免费信息发布网那这个间隙有多大?

再向后看,提到了索引。是因为表里的这个索引,而后面的 Recod Lock 刚好就是这个索引对应的各个字段,那对应到索引的定义,发现里面有一个字段刚好是某个业务属性的 id。

之前对事务日志不熟悉,这算一个比较重要的发现,根据这个 id 继续去查库时,发现这条记录是在前一刻刚刚写到库里。

一个刚写到库里的字段,和新的要 INSERT 的数据有什么关联呢?

此时仔细回想了一下业务逻辑,想起我们有一个异步的操作,会在数据执行之后,在某些条件下,去做更新这条记录的操作。因为这个更新操作涉及到更新多个表,还加了个事务。亿华云计算只是因为不是用户请求,不曾放在一起统一看过。

而我们前面的 INSERT 这个,也是在一个事务里,先执行 INSERT 再执行一个 UPDATE 的操作,可以这样理解:

会话 1先执行:

复制BEGIN

;

1. UPDATE xxx_table SET update_time=xxx WHERE id = 123

;

3.再执行一个其他操作1.2.3.

会话2 执行:

复制BEGIN

;

2.INSERT INTO `xxx_table` (col1,col2)...4.再执行一个操作1.2.3.

此时,我们看到两个因为锁的交叉使用,导致谁都没法完成,最终直到超时。

为什么?

那为什么一个 INSERT 会受到前面不相关的 UPDATE 操作的影响呢?

这就不得不提 MySQL 里的间隙锁 (GAP Lock)。业务里的 id,就是在索引 里使用到的那个,是通过某个服务生成的。而已经写入到库里的那条,id 要比我们新 INSERT 的这条,要大。GAP Lock 刚好锁定的是新写的 id 到成功写入的这条 ID。而这个写入成功的 ID,在前面正在被 UPDATE,所以两个操作就冲突了。

在线下模拟的话,可以通过 MySQL 自带的几个表,来查看锁的占用信息,可以清晰的看出来,两个操作的 lock data

是同一个数据,不冲突才怪呢。

复制mysql> SELECT * FROM `information_schema`.INNODB_LOCKS\G

;

0.4103s , 11711.328125 kb

Copyright © 2025 Powered by 我一个INSERT 还能被你 UPDATE 给卡住?,汇智坊  滇ICP备2023006006号-2

sitemap

Top