索引下 update 只锁住符合条件的记录吗?

在MySQL的RR隔离级别下,UPDATE语句并不只锁定匹配条件的记录,而是会使用next-key lock锁定记录及其间隙。这种行为导致了更新不交集数据时的锁冲突。实验表明,即使索引存在,UPDATE也可能锁住不满足条件的记录,影响并发性能。在高并发写场景下,调整隔离级别如改为RC级别可提升并发能力。

起步

我着手一个需要高并发 update 的功能,进行压测时,发现了大量锁超时。于是所有矛头就指向我了,认为我做任务分发时重复分发了两个或两个以上相同的子任务。

这一假设基于现有的子任务 update 范围,已知更新语句走索引 c,则有:子任务 A 更新 c=[1,10] ;子任务 B 更新 c=[11,20] … 子任务 N 更新 c=[n-9, n]。所以同事认为,只有可能会出现相同的 A 任务(B、C … N 都行),才可能引发锁超时。

假设同事是正确的,那就意味着 update 只锁主符合条件的记录。但果真如此吗?

环境准备

压测环境隔离级别查看:

show variables like 'transaction_isolation'

得压测环境的隔离为 RR 级别,也就是:REPEATABLE-READ

准备实验数据,方便研究 update 走索引时的行为:

CREATE TABLE `u` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `c` (`c`)
) ENGINE=InnoDB;

insert into u(id, c) values(null, 1), (null, 2), (null, 3), (null, 4), (null, 5), 
<think>嗯,用户问的是在执行update操作时,如果修改条件不成立的话,数据库会不会加行锁。这个问题看起来涉及到数据库的锁机制,特别是行锁的部分。我需要先回忆一下数据库事务和锁的相关知识。 首先,根据之前学过的内容,数据库的锁有不同的粒度,比如全局锁、表锁、行锁等。用户的问题是关于行锁的,也就是在update语句执行时,当条件不匹配任何行的情况下,数据库是否还会加锁。这时候可能需要考虑具体的数据库实现,比如MySQL的不同存储引擎,比如InnoDB和MyISAM的行为可能不同,不过现在大多数情况下都是用InnoDB,因为它支持行锁。 根据引用[2]里的内容,行锁的类型包括记录锁、间隙锁、临键锁等。InnoDB在加锁的时候,会根据事务的隔离级别和查询条件来决定锁的范围。比如在可重复读(Repeatable Read)隔离级别下,为了防止幻读,可能会使用间隙锁或者临键锁。而在读已提交(Read Committed)隔离级别下,可能只会锁住实际存在的记录。 那当update的条件不成立,也就是没有匹配到任何行的时候,这时候数据库会不会加锁呢?比如,假设执行update table set col=1 where id=100,而id=100的记录不存在。这时候InnoDB会如何处理? 根据引用[2]中的例子,间隙锁会锁定一个范围,但不包含记录本身。比如如果id=100不存在,但存在id=90和id=110的记录,那么间隙锁可能会锁定90到110之间的间隙,防止其他事务插入id=100的记录,从而避免幻读。这种情况下,即使update条件不成立,InnoDB仍然会加上间隙锁,特别是在可重复读隔离级别下。 另外,引用[3]提到可以使用select ... for update来显式加锁,这可能会影响锁的范围。同样,update语句本身也会自动加排他锁,不管有没有符合条件的行。 所以,可能结论是,当update条件不成立时,是否加锁取决于隔离级别。在可重复读级别下,会加间隙锁;而在读已提交下,可能不会加锁,因为没有实际的行被修改。不过需要确认这一点是否正确。 再查看引用[4],其中提到可以通过performance_schema.data_locks来查看加锁情况。可以举一个例子,比如在可重复读隔离级别下执行一个不匹配任何行的update,然后查看锁的情况,可能会发现间隙锁的存在。例如,如果表中有id为5和10的记录,执行update where id=7,这时候可能会在5到10之间加间隙锁。 不过,如果是在主键或者唯一索引的情况下,可能加锁的方式不同。比如,如果where条件是唯一索引且值不存在,间隙锁的范围可能会根据邻近的值来确定。如果是非唯一索引,则可能锁的范围更大。 总结一下,在InnoDB的可重复读隔离级别下,即使update条件不成立,也可能加间隙锁,以防止其他事务插入符合条件的数据,从而保证事务的隔离性。而在读已提交级别下,可能不会加间隙锁,因为没有实际的行被锁定。因此,是否加行锁(包括间隙锁)取决于数据库的隔离级别和具体的查询条件。 不过,用户的问题可能更关注于是否会加行锁,这里的行锁可能指的是记录锁。如果条件不成立,没有记录被修改,那么记录锁可能不会被加上。但间隙锁属于行锁的一种吗?根据引用[2],行锁包括记录锁、间隙锁、临键锁,所以间隙锁也是行级锁的一种。因此,在条件不成立时,可能加的是间隙锁,属于行锁的一种,所以答案是会加行锁,但具体类型是间隙锁。 需要确保回答中明确这一点,并引用相关的参考资料来支持结论。比如引用[2]中提到的行锁类型,以及隔离级别的影响。</think>### Update条件不成立时是否加行锁? 在数据库事务中,执行 `UPDATE` 操作时是否加行锁,取决于以下两个关键因素: --- #### 1. **隔离级别的影响** - **可重复读(Repeatable Read)隔离级别**: 即使 `UPDATE` 条件不成立(即未命中任何行),InnoDB 存储引擎仍可能通过 **间隙锁(Gap Lock)** 锁定符合条件的范围。 **示例**:若表中存在 `id=5` 和 `id=10` 的记录,执行 `UPDATE t SET col=1 WHERE id=7` 时,即使 `id=7` 不存在,InnoDB 也会锁定 `(5,10)` 的间隙,阻止其他事务插入 `id=7` 的数据[^2][^4]。 - **读已提交(Read Committed)隔离级别**: 若条件不成立,通常 **不会添加间隙锁**,仅锁实际存在的行。因此未命中时不会加锁[^2]。 --- #### 2. **查询条件类型** - **主键或唯一索引**: 若 `WHERE` 条件使用主键或唯一索引,且值不存在时,InnoDB 会根据邻近键值范围添加间隙锁[^2][^4]。 **示例**:`UPDATE user SET name='a' WHERE id=100`(假设 `id=100` 不存在),可能锁定 `(max_id, +∞)` 的间隙。 - **非唯一索引或无索引**: 若条件使用非唯一索引或无索引,InnoDB 可能扩大锁范围(如临键锁),甚至退化为表锁,导致更高的锁冲突风险[^2][^4]。 --- #### **验证方法** 通过以下 SQL 可查看加锁情况(需 MySQL 5.7+): ```sql SELECT object_schema, object_name, index_name, lock_type, lock_mode, lock_data FROM performance_schema.data_locks; ``` 在可重复读隔离级别下,未命中条件的 `UPDATE` 操作会显示 `lock_mode` 为 `X,GAP`,表示间隙锁[^4]。 --- #### **结论** - **条件不成立时是否加锁?** 在 **可重复读隔离级别下会加间隙锁**,在读已提交隔离级别下通常不加锁。 - **锁的类型**: 行锁可能表现为间隙锁或临键锁,具体由索引类型和隔离级别决定[^2][^4]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值