加鎖是實現數據庫併發控制的一個非常重要的技術。當事務在對某個數據對象進行操作前,先向系統發出請求,對其加鎖。加鎖後事務就對該數據對象有了一定的控制,在該事務釋放鎖之前,其他的事務不能對此數據對象進行更新操作。
在數據庫中有兩種基本的鎖類型:排它鎖(Exclusive Locks,即X鎖)和共享鎖(Share Locks,即S鎖)。當數據對象被加上排它鎖時,其他的事務不能對它讀取和修改。加了共享鎖的數據對象可以被其他事務讀取,但不能修改。數據庫利用這兩種基本的鎖類型來對數據庫的事務進行併發控制。
Oracle數據庫的鎖類型
根據保護的對象不同,Oracle數據庫鎖可以分爲以下幾大類:DML鎖(data locks,數據鎖),用於保護數據的完整性;DDL鎖(dictionary locks,字典鎖),用於保護數據庫對象的結構,如表、索引等的結構定義;內部鎖和閂(internal locks and latches),保護 數據庫的內部結構。
DML鎖的目的在於保證併發情況下的數據完整性,。在Oracle數據庫中,DML鎖主要包括TM鎖和TX鎖,其中TM鎖稱爲表級鎖,TX鎖稱爲事務鎖或行級鎖。
當Oracle執行DML語句時,系統自動在所要操作的表上申請TM類型的鎖。當TM鎖獲得後,系統再自動申請TX類型的鎖,並將實際鎖定的數據行的鎖標誌位進行置位。這樣在事務加鎖前檢查TX鎖相容性時就不用再逐行檢查鎖標誌,而只需檢查TM鎖模式的相容性即可,大大提高了系統的效率。TM鎖包括了SS、SX、S、X 等多種模式,在數據庫中用0-6來表示。不同的SQL操作產生不同類型的TM鎖。
在數據行上只有X鎖(排他鎖)。在Oracle數據庫中,當一個事務首次發起一個DML語句時就獲得一個TX鎖,該鎖保持到事務被提交或回滾。當兩個或多個會話在表的同一條記錄上執行 DML語句時,第一個會話在該條記錄上加鎖,其他的會話處於等待狀態。當第一個會話提交後,TX鎖被釋放,其他會話纔可以加鎖。
當Oracle數據庫發生TX鎖等待時,如果不及時處理常常會引起Oracle數據庫掛起,或導致死鎖的發生,產生ORA-60的錯誤。這些現象都會對實際應用產生極大的危害,如長時間未響應,大量事務失敗等。
悲觀封鎖和樂觀封鎖
一、悲觀封鎖
鎖在用戶修改之前就發揮作用:
Select ..for update(nowait)
Select * from tab1 for update
用戶發出這條命令之後,oracle將會對返回集中的數據建立行級封鎖,以防止其他用戶的修改。
如果此時其他用戶對上面返回結果集的數據進行dml或ddl操作都會返回一個錯誤信息或發生阻塞。
1:對返回結果集進行update或delete操作會發生阻塞。
2:對該表進行ddl操作將會報:Ora-00054:resource busy and acquire with nowait specified.
原因分析
此時Oracle已經對返回的結果集上加了排它的行級鎖,所有其他對這些數據進行的修改或刪除操作都必須等待這個鎖的釋放,產生的外在現象就是其他的操作將發生阻塞,這個這個操作commit或rollback.
同樣這個查詢的事務將會對該表加表級鎖,不允許對該表的任何ddl操作,否則將會報出ora-00054錯誤::resource busy and acquire with nowait specified.
二、樂觀封鎖
樂觀的認爲數據在select出來到update進取並提交的這段時間數據不會被更改。這裏面有一種潛在的危險就是由於被選出的結果集並沒有被鎖定,是存在一種可能被其他用戶更改的可能。因此Oracle仍然建議是用悲觀封鎖,因爲這樣會更安全。
阻塞
定義:
當一個會話保持另一個會話正在請求的資源上的鎖定時,就會發生阻塞。被阻塞的會話將一直掛起,直到持有鎖的會話放棄鎖定的資源爲止。4個常見的dml語句會產生阻塞
INSERT
UPDATE
DELETE
SELECT…FOR UPDATE
INSERT
Insert發生阻塞的唯一情況就是用戶擁有一個建有主鍵約束的表。當2個的會話同時試圖向表中插入相同的數據時,其中的一個會話將被阻塞,直到另外一個會話提交或會滾。一個會話提交時,另一個會話將收到主鍵重複的錯誤。回滾時,被阻塞的會話將繼續執行。
UPDATE 和DELETE當執行Update和delete操作的數據行已經被另外的會話鎖定時,將會發生阻塞,直到另一個會話提交或會滾。
Select …for update
當一個用戶發出select..for update的錯作準備對返回的結果集進行修改時,如果結果集已經被另一個會話鎖定,就是發生阻塞。需要等另一個會話結束之後纔可繼續執行。可以通過發出 select… for update nowait的語句來避免發生阻塞,如果資源已經被另一個會話鎖定,則會返回以下錯誤:Ora-00054:resource busy and acquire with nowait specified.
死鎖-deadlock
定義:當兩個用戶希望持有對方的資源時就會發生死鎖.
即兩個用戶互相等待對方釋放資源時,oracle認定爲產生了死鎖,在這種情況下,將以犧牲一個用戶作爲代價,另一個用戶繼續執行,犧牲的用戶的事務將回滾.
例子:
1:用戶1對A表進行Update,沒有提交。
2:用戶2對B表進行Update,沒有提交。
此時雙反不存在資源共享的問題。
3:如果用戶2此時對A表作update,則會發生阻塞,需要等到用戶一的事物結束。
4:如果此時用戶1又對B表作update,則產生死鎖。此時Oracle會選擇其中一個用戶進行會滾,使另一個用戶繼續執行操作。
起因:
Oracle的死鎖問題實際上很少見,如果發生,基本上都是不正確的程序設計造成的,經過調整後,基本上都會避免死鎖的發生。
DML鎖分類表
表1Oracle的TM鎖類型
鎖模式 鎖描述 解釋 SQL操作
0 none
1 NULL 空 Select
2 SS(Row-S) 行級共享鎖,其他對象
只能查詢這些數據行 Select for update、Lock for
update、Lock row share
3 SX(Row-X) 行級排它鎖,
在提交前不允許做DML操作 Insert、Update、
Delete、Lock row share
4 S(Share) 共享鎖 Create index、Lock share
5 SSX(S/Row-X) 共享行級排它鎖 Lock share row exclusive
6 X(Exclusive) 排它鎖 Alter table、Drop able、Drop index、Truncate table 、Lock exclusive
oracle 鎖問題的解決
可以用Spotlight軟件對數據庫的運行狀態進行監控。
當出現session鎖時,我們要及時進行處理.
1. 查看哪些session鎖:
SQL語句:select 'alter system kill session '''||sid||','||serial#||''';' from v$session where sid in (select sid from v$lock where block = 1);
SQL> select 'alter system kill session '''||sid||','||serial#||''';' from v$session where sid in (select sid from v$lock where block = 1);
'ALTERSYSTEMKILLSESSION'''||SID||','||SERIAL#||''';'
--------------------------------------------------------------------------------
alter system kill session '132,731';
alter system kill session '275,15205';
alter system kill session '308,206';
alter system kill session '407,3510';
2. 查看session鎖.
sql語句:select s.sid, q.sql_text from v$sqltext q, v$session s
where q.address = s.sql_address
and s.sid = &sid
order by piece;
SQL> select s.sid,q.sql_text from v$sqltext q, v$session s where q.address = s.sql_address and s.sid in (select sid from v$lock where block = 1) order by piece;
SID SQL_TEXT
---------- ----------------------------------------------------------------
77 UPDATE PROFILE_USER SET ID=1,COMPANY_ID=2,CUSTOMER_ID=3,NAMED
77 _INSURED_ID=4,LOGIN=5,ROLE_ID=6,PASSWORD=7,EMAIL=8,TIME_ZON
77 E=9 WHERE PROFILE_USER.ID=:34
3 rows selected.
3. kill鎖的進程.
SQL語句:alter system kill session '77,22198';
SQL> alter system kill session '391,48398';
System altered.
對for update的使用
在日常中,我們對for update的使用還是比較普遍的,特別是在如pl/sql developer中手工修改數據。此時只是覺得方便,而對for update真正的含義缺乏理解。
For update是Oracle提供的手工提高鎖級別和範圍的特例語句。Oracle的鎖機制是目前各類型數據庫鎖機制中比較優秀的。所以,Oracle認爲一般不需要用戶和應用直接進行鎖的控制和提升。甚至認爲死鎖這類鎖相關問題的出現場景,大都與手工提升鎖有關。所以,Oracle並不推薦使用for update作爲日常開發使用。而且,在平時開發和運維中,使用了for update卻忘記提交,會引起很多鎖表故障。
那麼,什麼時候需要使用for update?就是那些需要業務層面數據獨佔時,可以考慮使用for update。場景上,比如火車票訂票,在屏幕上顯示郵票,而真正進行出票時,需要重新確定一下這個數據沒有被其他客戶端修改。所以,在這個確認過程中,可以使用for update。這是統一的解決方案方案問題,需要前期有所準備
Select …forupdate語句是我們經常使用手工加鎖語句。通常情況下,select語句是不會對數據加鎖,妨礙影響其他的DML和DDL操作。同時,在多版本一致讀機制的支持下,select語句也不會被其他類型語句所阻礙。
藉助for update子句,我們可以在應用程序的層面手工實現數據加鎖保護操作。本篇我們就來介紹一下這個子句的用法和功能。
下面是採自Oracle官方文檔《SQLLanguage 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 of emp.empno;
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
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方式獲取資源,或者超時失效
對應的還有就是wait子句,也就是默認的for update行爲。一旦發現對應資源被鎖定,就等待blocking,直到資源被釋放或者用戶強制終止命令。
對wait子句還存在一個數據參數位,表示當出現blocking等待的時候最多等待多長時間。單位是秒級別。
//接上面的案例
SQL> select * from emp for update wait 3;
select * from emp for update wait 3
ORA-30006:資源已被佔用;執行操作時出現WAIT超時
Skip locked參數
Skip locked參數是最新引入到for update語句中的一個參數。簡單的說,就是在對數據行進行加鎖操作時,如果發現數據行被鎖定,就跳過處理。這樣for update就只針對未加鎖的數據行進行處理加鎖。
//session1中,對一部分數據加鎖;
SQL> select * from emp where rownum<4 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
//在session2中;
SQL> select * from emp for update skip locked;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
----- ---------- --------- ----- ----------- --------- --------- ------
(篇幅原因,省略)
7934 MILLER CLERK 77821982-1-23 1300.00 10
11 rows selected
總數據一共14行。Session1中,先lock住了3行數據。之後的seesion2中,由於使用的skip locked子句參數,將剩下的11條數據進行讀取到並且加鎖。