范围查询都是临建锁,只有当等值查询没有命中时才会退化为间隙锁,其他时候都为临建锁
时间: 2025-07-30 15:20:02 AIGC 浏览: 24
<think>嗯,用户这次的问题很聚焦,是在确认范围查询和间隙锁的触发条件关系。看来ta对锁机制的理解已经有一定基础,但需要更精确的区分边界。
从ta引用的资料来看,用户明显研究过索引类型对锁的影响,特别是唯一索引和非唯一索引的区别。不过ta可能忽略了“等值查询未命中”这个特殊场景在唯一索引和非唯一索引下的不同表现。
用户上次提问时我详细解释过RR隔离级别下的锁机制,这次需要更精准地对比三种场景:
1. 范围查询(无论是否命中记录)在RR下永远用临键锁——这是防止幻读的核心机制
2. 等值查询未命中时,唯一索引会退化成间隙锁(如引用[3]的案例),但非唯一索引反而可能扩大锁范围(如引用[1]的id=6案例)
3. 等值查询命中时,唯一索引只加记录锁,非唯一索引却要加临键锁
特别要注意用户引用的案例中那个精妙区别:当查询id>4 and id<7时,由于7是存在的记录,实际会形成(4,7]的临键锁,而不仅仅是(4,7)的间隙锁。这点在引用[4]的区间示意图里体现得很清楚。
用户可能被“未命中才用间隙锁”的简化说法误导了。实际上在RR级别下,MySQL的加锁策略远比这复杂:
- 临键锁是基础模板
- 间隙锁是退化形态(当不涉及具体记录时)
- 记录锁是特殊形态(仅锁定已存在记录)
最后那个对比表格很重要,要突出“索引类型”和“查询命中情况”这两个维度对锁类型的叠加影响。用户引用的资料[2]里非唯一索引的案例正好能佐证这个逻辑链。</think>在 MySQL 的可重复读(RR)隔离级别下,**范围查询并非总是使用临键锁**,而**等值查询未命中时也未必只用间隙锁**。具体机制取决于索引类型和查询条件,以下是关键规则:
---
### 一、锁类型决策机制
| **查询类型** | **索引类型** | **是否命中记录** | **锁定方式** | **示例** |
|--------------------|--------------|------------------|-----------------------|------------------------------|
| **范围查询** | 唯一索引 | 部分命中 | 临键锁(默认) | `WHERE id > 5 AND id < 10` |
| | 非唯一索引 | 部分命中 | 临键锁(默认) | `WHERE age BETWEEN 20 AND 30` |
| | 无索引 | - | **表级锁** | `WHERE age > 20`(无索引) |
| **等值查询未命中** | 唯一索引 | 未命中 | **退化为间隙锁** | `WHERE id = 6`(数据:4,7)[^3] |
| | 非唯一索引 | 未命中 | 间隙锁 | `WHERE age=25`(数据:20,30) |
| | 无索引 | 未命中 | **表级锁** | `WHERE age=25`(无索引) |
---
### 二、核心规则详解
#### 1. 范围查询的锁类型
- **默认使用临键锁**(Next-Key Lock)
范围查询(如 `id > 5 AND id <= 7`)会锁定**记录+间隙**,形成左开右闭区间:
- 示例:数据为 `id: 4,7,10` 时,`WHERE id > 5 AND id <= 7` 锁定 `(4,7]` 和 `(7,10]`[^4]
- 原因:防止在 `(5,7]` 区间插入新记录(如 `id=6`),避免幻读。
- **退化场景**
- 当查询不涉及具体记录时(如 `id > 20` 且无 `id>20` 的记录),锁定 `(max_id, +∞)` 的间隙锁[^1]
- 无索引时直接退化为表锁。
#### 2. 等值查询未命中的锁类型
- **唯一索引**:退化为**间隙锁**
```sql
-- 数据:id=4,7,10
SELECT * FROM t WHERE id = 6 FOR UPDATE; -- 锁定 (4,7) 间隙[^3]
```
- **非唯一索引**:使用**间隙锁**
```sql
-- 数据:age=20,30 (非唯一索引)
SELECT * FROM users WHERE age=25 FOR UPDATE; -- 锁定 (20,30) 间隙[^1]
```
- **无索引**:退化为**表级锁**(全表扫描)。
#### 3. 特殊边界行为
- **命中索引右边界**时可能退化:
```sql
-- 数据:id=4,7,10
SELECT * FROM t WHERE id > 8 AND id <= 10 FOR UPDATE;
-- 锁定 (7,10](临键锁)和 (10, +∞)(间隙锁)[^4]
```
---
### 三、锁范围验证(RR隔离级别)
通过 `performance_schema.data_locks` 查看实际加锁:
```sql
-- 事务A执行范围查询
BEGIN;
SELECT * FROM users WHERE age > 20 AND age < 30 FOR UPDATE;
-- 查看锁信息
SELECT
lock_type,
lock_mode,
lock_data
FROM performance_schema.data_locks;
```
**输出示例**:
```
+-----------+------------------------+-----------+
| lock_type | lock_mode | lock_data |
+-----------+------------------------+-----------+
| RECORD | X(临键锁) | 25 | -- 锁定记录25
| RECORD | X,GAP(间隙锁) | 30 | -- 锁定间隙(25,30)
+-----------+------------------------+-----------+
```
---
### 四、总结对比
| **场景** | **锁类型** | **触发条件** |
|------------------------|---------------|----------------------------------|
| 范围查询(有索引) | 临键锁(默认)| 查询涉及记录+间隙 |
| 范围查询(无索引) | 表锁 | 全表扫描 |
| **等值查询未命中** | | |
| ∟ 唯一索引 | 间隙锁 | 退化为间隙锁[^3] |
| ∟ 非唯一索引 | 间隙锁 | 直接加间隙锁[^1] |
| ∟ 无索引 | 表锁 | 全表扫描 |
> ⚠️ 关键结论:
> 1. **范围查询默认用临键锁**,但无索引时退化为表锁。
> 2. **等值查询未命中时**:
> - 唯一索引 → **退化为间隙锁**
> - 非唯一索引 → **直接用间隙锁**
> - 无索引 → **表锁**
阅读全文
相关推荐




















