1.什麼是鎖 鎖(LOCK)用於管理對共用資源的併發控制。通過建立鎖,可以保護資源(數據)的完整性和一致性。 通過一個經典的資料庫問題(丟失更新)就可以看到鎖的意義: 1)針對同一個資料庫,有表 Student,表結構如下: ID、姓名、年齡 2)用戶A登錄到資料庫中,查詢Student表的數據,此 ...
1.什麼是鎖
鎖(LOCK)用於管理對共用資源的併發控制。通過建立鎖,可以保護資源(數據)的完整性和一致性。
通過一個經典的資料庫問題(丟失更新)就可以看到鎖的意義:
1)針對同一個資料庫,有表 Student,表結構如下: ID、姓名、年齡
2)用戶A登錄到資料庫中,查詢Student表的數據,此時標記A的會話為Session1
3)用戶B登錄到資料庫中,查詢Student表的數據,此時標記B的會話為session2
4)用戶A將Student表中姓名為張三的學生姓名修改為李四,並提交
5)用戶B將Student表中姓名為張三的學生的年齡修改為18,並提交
當B提交數據時,他看到的數據是最初提取出來的數據,並做出修改,此時資料庫中會存在一條姓名為張三年齡為18的記錄。但是在操作4中,用戶A明明將張三的姓名修改為了李四,這樣明顯可以看出,用戶A做的修改並沒用生效,這種情況稱之為丟失更新。雖然整個過程僅僅涉及了Select和Update簡單的操作,但產生的問題是很明顯的。為此,我們應該對數據共用進行一定的保護,也就引入了鎖機制。
2.鎖機制
悲觀鎖定
悲觀鎖定就如同他的名字一樣,資料庫“悲觀”地認為併發情況下準備修改的數據隨時有可能已經被他人修改。因此在用戶獲取某些數據並有意做出修改時,資料庫就對這些數據加上鎖,並一直將鎖保持至用戶提交或回滾當前事務,在此期間其他用戶不能對這些數據或數據結構做出修改。正是因為悲觀鎖定的這種特性,用戶必須要保持與資料庫的有效的連接狀態,而且至少在事務生存期中只有一個用戶。一般來說悲觀鎖定對數據的一致性管理是較為有效的,但是其缺點也較為明顯:當併發量較大時,很容易造成阻塞,同時在一些特定的應用環境下,如Web應用,因為很難保證事務生存期中只有一個用戶,當多個用戶進入這個連接,那麼鎖的作用就會失效。
樂觀鎖定
與悲觀鎖定不同,資料庫“樂觀”地認為併發情況下準備被修改的數據不會被他人修改,直到最後做提交修改時才加鎖並驗證數據是否正確。樂觀鎖定事務開始於最終提交修改,因此對連接的要求幾乎沒有。但是因為在事務開始前,有可能修改的數據已經被他人修改過,此時會回滾當前的事務,用戶做的修改將會失敗。採用樂觀鎖定的要求比悲觀鎖定要低很多,並且阻塞的時間也非常的短。但因為有可能事務被回退,就需要為此做其他的彌補措施。
阻塞
阻塞發生在用戶之間對資源的控制權爭奪中,如果一個用戶A已經對某個數據持有了鎖,另一個用戶B又請求獲取這個數據的控制權,那麼就會產生阻塞。在此期間,用戶B的操作會被擱置, 直到用戶A提交或回滾當前事務,用戶B才能獲取到數據的控制權。
死鎖
死鎖同樣發生在資源的控制權爭奪中,但是與阻塞最大的區別是:
阻塞是由於資源不足引起的排隊等待現象。
死鎖是由於兩個對象在擁有一份資源的情況下申請另一份資源,而另一份資源恰好又是這兩對象正持有的,導致兩對象無法完成操作,且所持資源無法釋放。
下麵通過一個簡單的例子介紹死鎖產生的一個原因:
1)資料庫中存在A、B兩表,且兩表都只有一條數據
2)用戶A進入資料庫,此時標記A的會話為Session1
3)用戶B進入資料庫,此時標記B的會話為Session2
4)用戶A修改A表的數據,用戶B修改B表的數據
5)用戶A再修改B表的數據,用戶B再修改A表的數據
進行到操作5時,將產生死鎖現象。用戶A在等待用戶B釋放對A表數據的控制權,而用戶B又在等待用戶A釋放對B表數據的控制權,這就引發了死鎖的現象。
需要註意的是,Oracle資料庫本身幾乎不會發生死鎖現象,一旦你的應用中出現了死鎖的問題,那麼一般是設計上存在Bug(如外鍵中沒有創建索引等)。
因為外鍵上沒有創建索引導致死鎖問題
大部分的死鎖問題都是由外鍵上沒有索引導致,下麵通過一個簡單的案例進行說明。
1.創建兩個簡單的表並插入數據
create table 父表(id VARCHAR2(2) Primary Key); create table 子表(子id VARCHAR2(2) ,父id VARCHAR2(2) References 父表(id)); Insert into 父表 values(1);
Insert into 父表 values(2);
Insert into 父表 values(3); insert into 子表 values(1,1);
insert into 子表 values(2,1);
insert into 子表 values(3,1); insert into 子表 values(4,2);
insert into 子表 values(5,2);
insert into 子表 values(5,3);
2.會話1嘗試修改子表數據
update 子表 set 子id=10 where 子id = 1
3.會話2刪除一條父表數據
delete from 父表 where id=2;
4.查看鎖類型
1 Select b.Object_Name, a.Session_Id, 2 Decode(a.Locked_Mode, 0, 'None', 1, 'Null', 2, 'Row-S (SS)', 3, 'Row-X (SX)', 4, 'Share', 5, 'S/Row-X (SSX)', 6, 3 'Exclusive', To_Char(a.Locked_Mode)) Lock_Type 4 From V$locked_Object A, Dba_Objects B 5 Where b.Object_Id = a.Object_Id
結果如下:
5.查看執行語句的sid
1 select UserENV('sid') sid from dual
--查詢結果:執行修改子表操作會話1的Sid=11,執行刪除父表數據操作的會話2的Sid=199
通過實驗即使子表修改的數據和父表刪除的數據沒有關聯,但是仍產生了死鎖。
原因如下:修改子表數據時,會對子表申請一個Row-X鎖(行級排他鎖),此時刪除父表數據,因為沒有索引,為了檢測是否有關聯數據,會請求子表的一個全表排它鎖,而此時,子表事務未提交,存在一個行級排它鎖,阻止了全表排它鎖的獲取,造成了死鎖的現象。
同理,因為沒有索引,每次對父表數據進行操作,一旦同時有其他會話已經獲得了子表的鎖,都會引發死鎖。