一条简单的SQL的加锁实现分析

本文详细探讨了MySQL/InnoDB中不同隔离级别下SQL语句的锁行为,特别是在Repeatable Read和Serializable隔离级别中,针对不同类型的索引(如主键、唯一索引和非唯一索引)以及无索引情况下,SQL执行时加锁的不同表现。
从之前笔记中摘录的,当时应该是根据何专家的博客整理的,发表时没有做审核,抱歉!
何专家博客地址:http://hedengcheng.com/?p=771

看一下下面的SQL语句加什么锁

SLQ1:select * from t1 where id = 10;

SQL2:delete from t1 where id = 10;

(1)id 是不是主键

(2)当前系统的隔离级别是什么

(3)id列如果不是主键,那么id列上有索引吗

(4)id列上如果有二级索引,那么这个索引是二级索引吗

(5)两个SQL的执行计划是什么?索引扫描还是全表扫描

实际的执行计划需要根据MySQL的输出为准

  1. 组合一:id列是主键,RC隔离级别
  2. 组合二:id列是二级唯一索引,RC隔离级别
  3. 组合三:id列是二级非唯一索引,RC隔离级别
  4. 组合四:id列没有索引,RC隔离级别
  5. 组合五:id列是主键,RR隔离级别
  6. 组合六:id列是二级唯一索引,RR隔离级别
  7. 组合七:id列是二级非唯一索引,RR隔离级别
  8. 组合八:id列上没有索引,RR隔离级别
  9. Serializable隔离级别

在RR RC隔离级别下,SQL1:select 均不加锁,采用的是快照读;以下仅讨论SQL2:delete操作的加锁

Percona

组合一:id主键+RC

Percona

---TRANSACTION 1286310, ACTIVE 9 sec
2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1
MySQL thread id 341, OS thread handle 0x7f4d540d0700, query id 4510972 localhost root cleaning up
TABLE LOCK table `test`.`t1` trx id 1286310 lock mode IX
RECORD LOCKS space id 29 page no 3 n bits 80 index `PRIMARY` of table `test`.`t1` trx id 1286310 lock_mode X locks rec but not gap

MySQL

---TRANSACTION 5936, ACTIVE 171 sec
2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1
MySQL thread id 2, OS thread handle 0x7f5677201700, query id 364 localhost root
TABLE LOCK table `test`.`t1` trx id 5936 lock mode IX
RECORD LOCKS space id 6 page no 3 n bits 80 index `PRIMARY` of table `test`.`t1` trx id 5936 lock_mode X locks rec but not gap
Record lock, heap no 5 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 000000001730; asc      0;;
 2: len 7; hex 26000001550110; asc &   U  ;;
 3: len 1; hex 61; asc a;;

组合二:id唯一索引+RC

在唯一索引上的更新需要两个X锁,一个对应唯一索引id=10 记录,一个对应于聚簇索引name='d'的记录

Percona

---TRANSACTION 1286327, ACTIVE 3 sec
3 lock struct(s), heap size 360, 2 row lock(s), undo log entries 1
MySQL thread id 344, OS thread handle 0x7f4d5404e700, query id 4510986 localhost root cleaning up
TABLE LOCK table `test`.`t2` trx id 1286327 lock mode IX
RECORD LOCKS space id 30 page no 4 n bits 80 index `id` of table `test`.`t2` trx id 1286327 lock_mode X locks rec but not gap
RECORD LOCKS space id 30 page no 3 n bits 80 index `PRIMARY` of table `test`.`t2` trx id 1286327 lock_mode X locks rec but not gap

MySQL

---TRANSACTION 5938, ACTIVE 3 sec
3 lock struct(s), heap size 360, 2 row lock(s), undo log entries 1
MySQL thread id 2, OS thread handle 0x7f5677201700, query id 374 localhost root
TABLE LOCK table `test`.`t2` trx id 5938 lock mode IX
RECORD LOCKS space id 7 page no 4 n bits 80 index `id` of table `test`.`t2` trx id 5938 lock_mode X locks rec but not gap
Record lock, heap no 7 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 4; hex 8000000a; asc     ;;
 1: len 1; hex 64; asc d;;

RECORD LOCKS space id 7 page no 3 n bits 80 index `PRIMARY` of table `test`.`t2` trx id 5938 lock_mode X locks rec but not gap
Record lock, heap no 7 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 1; hex 64; asc d;;
 1: len 6; hex 000000001732; asc      2;;
 2: len 7; hex 27000001560110; asc '   V  ;;
 3: len 4; hex 8000000a; asc     ;;

组合三:id非唯一索引+RC

ID列为普通索引,那么对应的所有满足SQL查询条件的记录,都会被加锁;同时,这些记录在主键索引上的记录,也会被加锁

Percona

---TRANSACTION 1286339, ACTIVE 9 sec
3 lock struct(s), heap size 360, 4 row lock(s), undo log entries 2
MySQL thread id 347, OS thread handle 0x7f4b67fff700, query id 4511015 localhost root cleaning up
TABLE LOCK table `test`.`t3` trx id 1286339 lock mode IX
RECORD LOCKS space id 31 page no 4 n bits 80 index `idx_key` of table `test`.`t3` trx id 1286339 lock_mode X locks rec but not gap
RECORD LOCKS space id 31 page no 3 n bits 80 index `PRIMARY` of table `test`.`t3` trx id 1286339 lock_mode X locks rec but not gap

MySQL

---TRANSACTION 5940, ACTIVE 3 sec
3 lock struct(s), heap size 360, 4 row lock(s), undo log entries 2
MySQL thread id 2, OS thread handle 0x7f5677201700, query id 378 localhost root
TABLE LOCK table `test`.`t3` trx id 5940 lock mode IX
RECORD LOCKS space id 8 page no 4 n bits 80 index `idx_key` of table `test`.`t3` trx id 5940 lock_mode X locks rec but not gap
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 4; hex 8000000a; asc     ;;
 1: len 1; hex 62; asc b;;

Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 4; hex 8000000a; asc     ;;
 1: len 1; hex 64; asc d;;

RECORD LOCKS space id 8 page no 3 n bits 80 index `PRIMARY` of table `test`.`t3` trx id 5940 lock_mode X locks rec but not gap
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 1; hex 62; asc b;;
 1: len 6; hex 000000001734; asc      4;;
 2: len 7; hex 28000001570110; asc (   W  ;;
 3: len 4; hex 8000000a; asc     ;;

Record lock, heap no 5 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 1; hex 64; asc d;;
 1: len 6; hex 000000001734; asc      4;;
 2: len 7; hex 28000001570132; asc (   W 2;;
 3: len 4; hex 8000000a; asc     ;;

组合四:id无索引+RC

Percona

---TRANSACTION 1286373, ACTIVE 5 sec
2 lock struct(s), heap size 360, 2 row lock(s), undo log entries 2
MySQL thread id 348, OS thread handle 0x7f4d54193700, query id 4511037 localhost root cleaning up
TABLE LOCK table `test`.`t4` trx id 1286373 lock mode IX
RECORD LOCKS space id 33 page no 3 n bits 80 index `PRIMARY` of table `test`.`t4` trx id 1286373 lock_mode X locks rec but not gap

MySQL

---TRANSACTION 5946, ACTIVE 2 sec
2 lock struct(s), heap size 360, 2 row lock(s), undo log entries 2
MySQL thread id 2, OS thread handle 0x7f5677201700, query id 382 localhost root
TABLE LOCK table `test`.`t4` trx id 5946 lock mode IX
RECORD LOCKS space id 9 page no 3 n bits 80 index `PRIMARY` of table `test`.`t4` trx id 5946 lock_mode X locks rec but not gap
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 1; hex 62; asc b;;
 1: len 6; hex 00000000173a; asc      :;;
 2: len 7; hex 2b0000015a0110; asc +   Z  ;;
 3: len 4; hex 8000000a; asc     ;;

Record lock, heap no 5 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 1; hex 64; asc d;;
 1: len 6; hex 00000000173a; asc      :;;
 2: len 7; hex 2b0000015a012c; asc +   Z ,;;
 3: len 4; hex 8000000a; asc     ;;

组合五:id主键+RR

参考 组合一

组合六:id唯一索引+RR

参考 组合二

组合七:id非唯一索引+RR

Percona

---TRANSACTION 1592633, ACTIVE 24 sec
4 lock struct(s), heap size 1184, 5 row lock(s), undo log entries 2
MySQL thread id 794, OS thread handle 0x7f4d5404e700, query id 7801799 localhost root cleaning up
Trx read view will not see trx with id >= 1592634, sees < 1592634
TABLE LOCK table `test`.`t3` trx id 1592633 lock mode IX
RECORD LOCKS space id 31 page no 4 n bits 80 index `idx_key` of table `test`.`t3` trx id 1592633 lock_mode X
RECORD LOCKS space id 31 page no 3 n bits 80 index `PRIMARY` of table `test`.`t3` trx id 1592633 lock_mode X locks rec but not gap
RECORD LOCKS space id 31 page no 4 n bits 80 index `idx_key` of table `test`.`t3` trx id 1592633 lock_mode X locks gap before rec

MySQL

---TRANSACTION 5985, ACTIVE 7 sec
4 lock struct(s), heap size 1184, 5 row lock(s), undo log entries 2
MySQL thread id 12, OS thread handle 0x7f56770fd700, query id 500 localhost root
TABLE LOCK table `test`.`t3` trx id 5985 lock mode IX
RECORD LOCKS space id 8 page no 4 n bits 80 index `idx_key` of table `test`.`t3` trx id 5985 lock_mode X
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 4; hex 8000000a; asc     ;;
 1: len 1; hex 64; asc d;;

Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 4; hex 8000000a; asc     ;;
 1: len 1; hex 62; asc b;;

RECORD LOCKS space id 8 page no 3 n bits 80 index `PRIMARY` of table `test`.`t3` trx id 5985 lock_mode X locks rec but not gap
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 1; hex 64; asc d;;
 1: len 6; hex 000000001761; asc      a;;
 2: len 7; hex 3f0000016d0132; asc ?   m 2;;
 3: len 4; hex 8000000a; asc     ;;

Record lock, heap no 5 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 1; hex 62; asc b;;
 1: len 6; hex 000000001761; asc      a;;
 2: len 7; hex 3f0000016d0110; asc ?   m  ;;
 3: len 4; hex 8000000a; asc     ;;

RECORD LOCKS space id 8 page no 4 n bits 80 index `idx_key` of table `test`.`t3` trx id 5985 lock_mode X locks gap before rec
Record lock, heap no 8 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 8000000b; asc     ;;
 1: len 1; hex 66; asc f;;

组合八:id无索引+RR

Percona

---TRANSACTION 1592639, ACTIVE 4 sec
2 lock struct(s), heap size 360, 7 row lock(s), undo log entries 2
MySQL thread id 794, OS thread handle 0x7f4d5404e700, query id 7801804 localhost root cleaning up
TABLE LOCK table `test`.`t4` trx id 1592639 lock mode IX
RECORD LOCKS space id 33 page no 3 n bits 80 index `PRIMARY` of table `test`.`t4` trx id 1592639 lock_mode X

MySQL

---TRANSACTION 6000, ACTIVE 3 sec
2 lock struct(s), heap size 360, 7 row lock(s), undo log entries 2
MySQL thread id 12, OS thread handle 0x7f56770fd700, query id 546 localhost root
TABLE LOCK table `test`.`t4` trx id 6000 lock mode IX
RECORD LOCKS space id 9 page no 3 n bits 80 index `PRIMARY` of table `test`.`t4` trx id 6000 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 1; hex 61; asc a;;
 1: len 6; hex 000000001722; asc      ";;
 2: len 7; hex 9e0000014e0110; asc     N  ;;
 3: len 4; hex 8000000f; asc     ;;

Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 1; hex 62; asc b;;
 1: len 6; hex 000000001770; asc      p;;
 2: len 7; hex 47000001730110; asc G   s  ;;
 3: len 4; hex 8000000a; asc     ;;

Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 1; hex 63; asc c;;
 1: len 6; hex 000000001722; asc      ";;
 2: len 7; hex 9e0000014e0122; asc     N ";;
 3: len 4; hex 80000006; asc     ;;

Record lock, heap no 5 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 1; hex 64; asc d;;
 1: len 6; hex 000000001770; asc      p;;
 2: len 7; hex 4700000173012c; asc G   s ,;;
 3: len 4; hex 8000000a; asc     ;;

Record lock, heap no 6 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 1; hex 66; asc f;;
 1: len 6; hex 000000001722; asc      ";;
 2: len 7; hex 9e0000014e0134; asc     N 4;;
 3: len 4; hex 8000000b; asc     ;;

Record lock, heap no 7 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 2; hex 7a7a; asc zz;;
 1: len 6; hex 000000001722; asc      ";;
 2: len 7; hex 9e0000014e013d; asc     N =;;
 3: len 4; hex 80000002; asc     ;;

组合九:Serializable

针对前面提到的简单的SQL,最后一个情况:Serializable隔离级别。对于SQL2:delete from t1 where id = 10; 来说,Serializable隔离级别与Repeatable Read隔离级别完全一致,因此不做介绍。

Serializable隔离级别,影响的是SQL1:select * from t1 where id = 10; 这条SQL,在RC,RR隔离级别下,都是快照读,不加锁。但是在Serializable隔离级别,SQL1会加读锁,也就是说快照读不复存在,MVCC并发控制降级为Lock-Based CC。

结论:在MySQL/InnoDB中,所谓的读不加锁,并不适用于所有的情况,而是隔离级别相关的。Serializable隔离级别,读不加锁就不再成立,所有的读操作,都是当前读。

<think>好的,用户的问题是在Oracle中如何仅更新符合特定条件的第一条记录。我需要仔细分析这个问题,并提供正确的方法。 首先,我得回忆一下Oracle的UPDATE语句结构。标准的UPDATE语句可以更新所有符合条件的记录,但用户希望只更新第一条。这时候可能需要用到子查询来限定行数。 接下来,考虑如何选择第一条记录。在Oracle中,通常使用ROW_NUMBER()窗口函数来为行分配序号,然后根据条件筛选。例如,使用ROW_NUMBER() OVER (ORDER BY ...)来排序,然后取序号为1的行。 不过,在UPDATE语句中直接使用ROW_NUMBER()可能有问题,因为需要将子查询与主表关联。这时候可能需要使用一个公共表表达式(CTE)或者内联视图,将需要更新的行的ROWID或主键提取出来,然后根据这个结果进行更新。 另外,需要注意Oracle的UPDATE语句的语法限制。直接使用LIMIT 1这样的语法是不行的,因为Oracle不支持在UPDATE中使用LIMIT。必须通过子查询的方式来限制行数。 比如,可以这样构造查询:先在一个子查询中选择符合条件的记录,按某个顺序排序,然后取第一条的ROWID,最后根据这个ROWID进行更新。例如: UPDATE 表名 SET 列名 = 新值 WHERE ROWID IN ( SELECT ROWID FROM ( SELECT ROWID, ROW_NUMBER() OVER (ORDER BY 排序列) rn FROM 表名 WHERE 条件 ) WHERE rn = 1 ); 这样应该可以只更新第一条符合条件的记录。需要确保排序列的选择是明确的,以确保顺序正确。如果用户没有指定排序,可能需要根据实际情况调整ORDER BY子句,比如使用主键或者时间戳来保证顺序。 另外,还要注意事务处理,确保在更新时使用适当的锁机制,避免并发问题。例如,使用SELECT ... FOR UPDATE来锁定选中的行,但这可能会影响性能,尤其是在高并发环境下。不过,根据引用[3]提到的UMBRA论文,不上锁的读可能有性能提升,但可能牺牲数据正确性。不过,在需要准确性的更新操作中,可能需要使用锁来保证数据的一致性。 此外,还要考虑索引的情况。如果条件中的列有索引,查询性能会更好。如果没有索引,可能需要全表扫描,影响效率,尤其是在大数据量的情况下。 最后,需要验证这个方法的正确性。例如,测试不同情况下的更新是否只影响第一条符合条件的记录,并确保排序逻辑正确。如果有多个符合条件的记录,ROW_NUMBER()函数会根据ORDER BY子句的排序分配序号,正确选择第一条。</think>在Oracle中更新符合特定条件的第一条记录,可以通过以下步骤实现: ### 步骤说明 1. **使用ROW_NUMBER()窗口函数** 通过给符合条件的记录添加行号标记,筛选出第一条记录: ```sql UPDATE your_table SET your_column = 'new_value' WHERE ROWID IN ( SELECT rid FROM ( SELECT ROWID AS rid, ROW_NUMBER() OVER (ORDER BY your_order_column) AS rn FROM your_table WHERE your_condition = 'value' ) WHERE rn = 1 ); ``` *说明*:`your_order_column`决定排序依据(如时间戳或主键),`your_condition`是筛选条件[^1][^3]。 2. **使用FETCH FIRST子句(Oracle 12c及以上)** 更简洁的语法: ```sql UPDATE your_table SET your_column = 'new_value' WHERE your_condition = 'value' ORDER BY your_order_column FETCH FIRST 1 ROW ONLY; ``` ### 注意事项 - **锁机制**:更新操作默认会对目标行加锁,可能影响并发性能。 - **索引优化**:若`your_order_column`或条件字段有索引,可显著提升查询效率[^2]。 - **事务控制**:建议在事务中执行(`BEGIN ... COMMIT;`)以确保原子性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值