【資料庫】併發控制
資料庫是一個共享資源,可以供多個使用者共享使用.
以事務為單位管理使用者程式的併發訪問,提高資源共享效率。資料併發性意味著多個使用者可以同時訪問資料。
併發訪問存在衝突嗎?如何控制?
事務:使用者定義的一個數據庫操作序列.這些操作要麼全做要麼全不做,是一個不可分割的工作單元
.
事務具有ACID
原子性(atomicity):資料庫的邏輯單位,要麼都做,要麼都不做
一致性(consistency):一致性指的就是當資料庫只從某種一致性狀態到另一種一致性狀態,資料庫只包含成功事務提交的結果。
隔離性(Isolation):一個事務執行不受其他事務干擾。
持續性(Durability):一個事務一旦提交,它對資料庫中的資料的改變就是永久的。
併發衝突帶來的不一致性:
(1)丟失修改:兩個事務同時讀取同一資料並修改,結果後提交事務提交的修改覆蓋了前提交事務提交的修改,導致前一個事務的修改丟失;效果等同於序列執行事務.
- 關鍵:事務要識別提交修改時刻庫中的資料是不是與原讀取時刻庫中的資料一致.
(2)不可重複讀:同一事務前後兩次讀取的資料不一致
- 前後兩次讀取同一資料,資料不一致
- 前後兩次按照同一條件讀取資料,所得記錄個數不同——-幻讀
(3)髒讀
讀髒資料:一個事務讀取了另一個事務未提交的資料.
資料不一致的主要原因是併發操作破壞了事務的隔離性.
兩面性:完全隔離事務就成了序列排程事務,系統併發訪問的效能很低。我們適當較低事務的隔離性
併發控制:
併發控制中主要技術有:封鎖locking、時間戳timestamp、樂觀控制法optimistic scheduler、多版本併發控制multi-version concurrency control
單處理機中,事務並行
執行實際上就是交叉這併發
執行
封鎖協議:
1.一級封鎖協議,事物T在修改資料R之前要加上排他鎖,直到事物結束時才釋放。
2.二級封鎖協議,在一級封鎖協議的基礎上增加事務T在讀取資料R之前要必須先對其增加S鎖,讀完後即可釋放S鎖
3.三級封鎖協議,在一級封鎖協議的基礎上增加事務T在讀物資料R之之前必須先對其加上S鎖,直到事務結束才釋放
活鎖與死鎖
避免活鎖的簡單方法是先來先服務(避免餓死的現象)
避免死鎖:1)一次封鎖法
2)順序封鎖
事務兩段鎖協議:
第一階段獲得封鎖協議
第二階段就是釋放封鎖
事務遵循兩段鎖是事務可序列化的充分條件,但是不必要;
封鎖的粒度
封鎖粒度越大,系統開銷越大,效能越低;
多種粒度封鎖
多粒度封鎖意味著允許多粒度樹
中的每一個結點的都被獨立的加鎖。
而對每一個結點加鎖,同時也表明這個結點所有的後裔結點也被加一同型別的鎖。
意向鎖(這個部分了解的還很少,後期總結)
ORACLE的鎖為行級鎖,封鎖粒度小。
Oracle的讀與寫互不阻塞
。
READ COMMITTED隔離級別可提供更多的併發性,但會給某些事務帶來幻讀和不可重複讀的風險。
事務吞吐量 大,要求響應時間短
事務吞吐量不大,幻讀和不可重複讀導致不正確結果的風險低。
本級別不需要使用者檢測鎖衝突
SERIALIZABLE
- 修改行數少的短事務
- 以讀為主的長事務
- 其它公司的系統在實現本隔離級別時會鎖定讀、寫的快,但Oracle提供非阻塞的讀和細粒度的行級鎖,較少了讀與寫的競爭,故在序列級別仍然提供很好的吞吐量。
- 本級別需要使用者處理潛在的衝突–Cannot serialize access error 。
事務吞吐量大,修改機會多的事務
採用READ COMMITTED隔離級別;
採用合適的併發控制措施:悲觀鎖、樂觀鎖
悲觀鎖定:對併發衝突採取一種悲觀的態度,認為相關業務的併發衝突度高,把本事務擬將更新的行在查詢時就加鎖,從而阻止其它事務更新這些行;
在SELECT語句後加上FOR UPDATE
也可以在select子句後用for update nowait
用在C/S架構
樂觀鎖定:對併發衝突採取樂觀的態度,認為事務併發衝突度不高,對擬將更新的記錄在查詢時不鎖定,在真正更新時再作檢查,檢查通過後方鎖定。
【說明】悲觀的鎖定可以讓使用者知道他們的資料更新不會被阻塞,當他們更細資料庫中的資料是不會遇到衝突
樂觀鎖定則會帶來倘若使用者執行資料更新操作時,如果資料在操作執行已經改變了,系統就必須通知終端使用者操作不能執行。
實施樂觀鎖的方法
①更新前在應用中儲存所要操作行的“前映像”,更新時對操作行的當前值與儲存的舊記錄進行比對,如果資料一致則表明沒有併發衝突,就提交更新;否則則需要根據業務邏輯來作進一步處理。
Selec col1,col2 from table into old_col1,old_col2
where primary_key =primary_key
Update table
Set col1 = :new_col1, col2 = :new_col2, ….
Where primary_key = primary_key
And col1 = :old_col1
And col2 = :old_col2
If SQL% NOTFOUND
//更新失敗,進一步處理
END IF
②在基表上使用一個能夠追蹤資料版本的特殊列。
- 新增一個版本控制的欄位
- 新增一個時間戳欄位,每次需要寫入時對比時間戳;併發事務在更新提交的時候檢查當前資料庫中資料的時間戳和自己更新前取到的時間戳的一致性,如果一致則就提交,否則就回滾放棄 。
③使用ORA_ROWSCN偽列。
在用CREATE TABLE建立基表時附加 ROWDEPENDENCIES引數,則Oracle在每次提交資料時都會依據系統時鐘SCN建立該行的ORA_ROWSCN偽列〔預設情況下Oracle在塊級別維護SCN,為同一塊上的多行共享一個SCN〕。
在提交資料修改時,將當前的ORA_ROWSCN與修改資料前得到的ORA_ROWSCN進行比較,如果一致說明資料沒有被其他事務更新,則提交;否則說明資料已經被其它事務更新,則回滾,放棄修改。
create table dept (
Deptno char(4),
dname varchar2(50),
loc varchar2(50),
Data number(7,2),
constraint dept_pk primary key(deptno)
) ROWDEPENDENCIES
select ORA_ROWSCN from scott.dept where deptno=‘0001’;
【離線樂觀鎖和離線悲觀鎖】
課上彭老師的案列
考慮一個訂單修改的例子
查詢資料庫,結果返回給表示層顯示;
使用者編輯資料〔可能花費較長時間〕
提交修改資料
由於整個業務持續的時間較長,如果把上述三步操作放在一個事務中處理,就會出現所謂的長事務,影響系統的吞吐量。
一個處理辦法是把業務分為下述三步,用兩個事務處理:
事務1:查詢資料庫,結果返回給表示層顯示;
使用者編輯資料〔可能花費較長時間〕
事務2:提交修改資料
上述跨多個事務〔獲取訂單用一個事務,更新訂單用另一個事務〕併發業務,傳統的序列化隔離級別、樂觀鎖、悲觀所均失效,需要使用所謂:
離線樂觀鎖
離線悲觀鎖
離線樂觀鎖實現方案:
查詢時把版本號存放到一個Sesion這樣的容器中,更新時把當前版本號與Session中的版本號作對比。
與樂觀鎖的方案差不多。
適用於如下情況:
資料讀出和資料更新在兩個事務中進行;
併發衝突的可能性小,〔事務回滾導致的〕重做修改的代價小。