SelectForupdate语句浅析
Select …语句是我们经常使⽤⼿⼯加锁语句。通常情况下,select语句是不会对数据加锁,妨碍影响其他的DML和DDL操作。同时,在多版本⼀致读机制的⽀持下,select语句也不会被其他语句所阻碍。
借助for update⼦句,我们可以在应⽤程序的层⾯⼿⼯实现数据加锁保护操作。本篇我们就来介绍⼀下这个⼦句的⽤法和功能。
下⾯是采⾃官⽅⽂档《Language Reference》中关于for update⼦句的说明:(请双击点开图⽚查看)
从for update⼦句的语法状态图中,我们可以看出该⼦句分为两个部分:加锁范围⼦句和加锁⾏为⼦句。下⾯我们分别针对两个⽅⾯的进⾏介绍。
加锁范围⼦句
在select…for update之后,可以使⽤of⼦句选择对select的特定数据表进⾏加锁操作。默认情况下,不使⽤of⼦句表⽰在select所有的数据表中加锁。
//采⽤默认格式for update
SQL> select * from emp where rownum<2 for update;
EMPNO ENAME    JOB        MGR HIREDATE        SAL    COMM DEPTNO
----- ---------- --------- ----- ----------- --------- --------- ------
7369 SMITH    CLERK    79021980-12-17    800.00              20
此时,我们观察v$lock和v$locked_object视图,可以看到锁信息。
//事务信息视图
SQL> select addr,xidusn,xidslot,xidsqn from v$transaction;
ADDR        XIDUSN  XIDSLOT    XIDSQN
-------- ---------- ---------- ----------
377DB5D0        7        19      808
//锁对象信息
SQL> select xidusn,xidslot,xidsqn,object_id,session_id, oracle_username from v$locked_object;
XIDUSN  XIDSLOT    XIDSQN OBJECT_ID SESSION_ID ORACLE_USERNAME
---------- ---------- ---------- ---------- ---------- ------------------------------
7        19      808    73181        36 SCOTT
//
SQL> select owner,object_name from dba_objects where object_id=73181;
OWNER                        OBJECT_NAME
------------------------------ ------------------------------------------------------------
SCOTT                        EMP
//
SQL> select addr, sid, type, id1,id2,lmode, request, block from v$lock where sid=36;
ADDR    SID TYPE      ID1      ID2    LMODE  REQUEST BLOCK
-
------- ---------- ---- ---------- ---------- ---------- ---------- ----------
37E808F0  36 AE        100        0        4        0  0
B7DE8A44  36 TM      73181        0        3        0  0
377DB5D0  36 TX      458771      808        6        0  0
从上⾯的情况看,默认情况下的for update语句,效果相当于启动了⼀个会话级别的事务,在对应的数据表(select所涉及的所有数据表)上加⼊⼀个数据表级共享锁(TM,lmode=3)。同时,在对应的数据⾏中加⼊独占锁(TX,lmode=6)。
根据我们以前的知识,如果此时有另⼀个会话视图获取对应数据⾏的独占权限(⽆论是⽤update/delete还是另⼀个for update),都会
以block⽽告终。
SQL> select sid from v$mystat where rownum<2;
SID
-
---------
37
SQL> select * from emp where empno=7369 for update;
//系统blocking
此时系统中状态,切换到另⼀个⽤户下进⾏观察:
SQL> select addr, sid, type, id1,id2,lmode, request, block from v$lock where sid in (36,37);
ADDR  SID TYPE      ID1      ID2    LMODE  REQUEST    BLOCK
-------- ---------- ---- ---------- ---------- ---------- ---------- ----------
37E808F0        36 AE        100        0        4        0  0
37E80ED4        37 AE        100        0        4        0  0
37E80F48        37 TX      458771      808        0        6  0
B7DE8A44        37 TM      73181        0        3        0 0
B7DE8A44        36 TM      73181        0        3        0 0
377DB5D0        36 TX      458771      808        6        0 1
6 rows selected
SQL> select * from dba_waiters;
WAITING_SESSION HOLDING_SESSION
LOCK_TYPE                MODE_HELD                              MODE_REQUESTED                            LOCK_ID1  LOCK_ID2
--------------- --------------- -------------------------- ---------------------------------------- ---------------------------------------- ---------- ----------
37            36Transaction              Exclusive                              Exclusive                                  458771      808
由此,我们可以获取到结论:for update⼦句的默认⾏为就是⾃动启动⼀个事务,借助事务的锁机制将数据进⾏锁定。
Of⼦句是配合for update语句使⽤的⼀个范围说明标记。从官⽅的语法结构看,后⾯可以跟⼀个或者多个数据列列表。这种语法场景常常使⽤在进⾏连接查询的select中,对其中⼀张数据表数据进⾏锁定。
SQL> select empno,ename,job,mgr,sal from emp,dept where emp.deptno=dept.deptno and empno=7369 for update pno;
EMPNO ENAME    JOB        MGR      SAL
----- ---------- --------- ----- ---------
7369 SMITH    CLERK    7902  800.00
SQL> select addr, sid, type, id1,id2,lmode, request, block from v$lock where sid=36;
ADDR      SID TYPE      ID1      ID2    LMODE  REQUEST BLOCK
-------- ---------- ---- ---------- ---------- ---------- ---------- ----------
37E808F0        36 AE        100        0        4        0  0
B7E1C2E8        36 TM      73181        0        3        0  0
377DBC0C        36 TX      65566      747        6      0  0
上⾯的语句中,我们看到使⽤for update of指定数据列之后,锁定的范围限制在了所在的数据表。也就是说,当我们使⽤连接查询配合of⼦句的时候,可以实现有针对性的锁定。
同样在连接查询的时候,如果没有of⼦句,同样采⽤默认的模式,会如何呢?
SQL> select empno,ename,job,mgr,sal from emp,dept where emp.deptno=dept.deptno and empno=7369 for update;
EMPNO ENAME    JOB        MGR      SAL
----- ---------- --------- ----- ---------
7369 SMITH    CLERK    7902  800.00
select语句查询日期
SQL> select addr, sid, type, id1,id2,lmode, request, block from v$lock where sid=36;
ADDR    SID TYPE      ID1      ID2    LMODE  REQUEST BLOCK
-------- ---------- ---- ---------- ---------- ---------- ---------- ----------
37E808F0        36 AE        100        0        4        0    0
B7E1C2E8        36 TM      73179        0        3        0  0
B7E1C2E8        36 TM      73181        0        3        0    0
377DBC0C        36 TX      458777      805        6        0  0
SQL> select owner,object_name from dba_objects where object_id=73179;
OWNER                        OBJECT_NAME
------------------------------ --------------------------------------------------------------------------------
SCOTT                        DEPT
明显可以看到,当我们没有使⽤of⼦句的时候,默认就是对所有select的数据表进⾏lock操作。
加锁⾏为⼦句
加锁⾏为⼦句相对⽐较容易理解。这⾥分别介绍。
Nowait⼦句
当我们进⾏for update的操作时,与普通select存在很⼤不同。⼀般select是不需要考虑数据是否被锁定,最多根据多版本⼀致读的特性读取之前的版本。加⼊for update之后,Oracle就要求启动⼀个新事务,尝试对数据进⾏加锁。如果当前已经被加锁,默认的⾏为必然是block等待。
使⽤nowait⼦句的作⽤就是避免进⾏等待,当发现请求加锁资源被锁定未释放的时候,直接报错返回。
///session1中
SQL> select * from emp for update;
EMPNO ENAME    JOB        MGR HIREDATE        SAL    COMM DEPTNO
----- ---------- --------- ----- ----------- --------- --------- ------
7369 SMITH    CLERK    79021980-12-17    800.00              20
7499 ALLEN    SALESMAN  76981981-2-20    1600.00  300.00    30
7521 WARD      SALESMAN  76981981-2-22    1250.00  500.00    30
7566 JONES    MANAGER  78391981-4-2    2975.00              20
//变换session,进⾏执⾏。
SQL> select * from emp for update nowait;
select * from emp for update nowait
ORA-00054:资源正忙,但指定以NOWAIT⽅式获取资源,或者超时失效