引言
假設(shè)進(jìn)程A和進(jìn)程B從Customer
表中讀取了同一行,在改變了數(shù)據(jù)后哟玷,同時(shí)把新的版本寫(xiě)回?cái)?shù)據(jù)庫(kù)郑临,這時(shí)哪個(gè)改動(dòng)會(huì)生效呢咖气?進(jìn)程A?進(jìn)程B?還是同時(shí)生效拦焚?類(lèi)似的铡羡,如果同時(shí)對(duì)一個(gè)名為Customer
的共享對(duì)象做出改變积蔚,會(huì)發(fā)生什么呢?
在多人同時(shí)訪問(wèn)共享實(shí)體(無(wú)論是對(duì)象數(shù)據(jù)記錄還是其他形式)時(shí)烦周,會(huì)涉及到并發(fā)控制的問(wèn)題尽爆。本文將會(huì)從實(shí)踐的角度來(lái)概述并發(fā)訪問(wèn)控制的方式怎顾。
要了解如何在系統(tǒng)中實(shí)現(xiàn)并發(fā)控制,首先就必須要了解沖突漱贱,我們可以避免沖突槐雾,或者檢測(cè)沖突然后解決它。然后是了解事務(wù)幅狮,它們是可能修改兩個(gè)或多個(gè)實(shí)體的操作集合募强。在現(xiàn)代軟件的開(kāi)發(fā)項(xiàng)目中,并發(fā)控制和事務(wù)不僅僅在數(shù)據(jù)庫(kù)的領(lǐng)域存在崇摄,而是在所有的架構(gòu)層都存在相關(guān)的問(wèn)題擎值。
沖突
在Implementing Referential Integrity and Shared Business Logic中,我討論了由于存在映射到數(shù)據(jù)模式的對(duì)象模式逐抑,從而導(dǎo)致的引用完整性問(wèn)題鸠儿,可以簡(jiǎn)單叫做跨模式引用的完整性問(wèn)題。具體到?jīng)_突厕氨,只需要關(guān)心記錄系統(tǒng)中數(shù)據(jù)實(shí)體的一致性問(wèn)題进每。記錄系統(tǒng)是實(shí)體的官方版本所在的位置。記錄通常是存儲(chǔ)在關(guān)系數(shù)據(jù)庫(kù)中的數(shù)據(jù)命斧,可以用XML格式田晚,對(duì)象或者其他形式來(lái)表示。
當(dāng)兩個(gè)活動(dòng)(可能是不完全成熟的事務(wù))嘗試更改記錄系統(tǒng)中的實(shí)體時(shí)国葬,會(huì)發(fā)生沖突贤徒。在三種情況下兩個(gè)活動(dòng)會(huì)互相干擾。
- 臟讀汇四∨⒗颍活動(dòng)1(A1)從記錄系統(tǒng)中讀取實(shí)體,然后更新記錄系統(tǒng)船殉,但是不提交更改(例如鲫趁,更改尚未完成)。這時(shí)活動(dòng)2(A2)讀取實(shí)體利虫,不知不覺(jué)制作了未提交版本的副本挨厚。A1回滾了更改,將實(shí)體恢復(fù)到原始狀態(tài)糠惫。此時(shí)A2讀到的實(shí)體版本因?yàn)閺奈刺峤灰咛辏虼瞬槐徽J(rèn)為實(shí)際存在。
- 不可重復(fù)讀硼讽。A1從記錄系統(tǒng)中讀取一個(gè)實(shí)體并創(chuàng)建它的副本巢价。此時(shí)A2從記錄系統(tǒng)中刪除改實(shí)體。那么A1現(xiàn)在有一個(gè)沒(méi)有正式存在的實(shí)體的副本。
- 幻影讀壤躲。A1從記錄系統(tǒng)中檢索實(shí)體集合城菊,然后根據(jù)某種搜索條件(例如“所有名為Bill的客戶”)來(lái)記錄它們的副本。然后A2創(chuàng)建新的實(shí)體碉克,新的實(shí)體正好滿足搜索條件(例如凌唬,將“Bill Klassen”插入數(shù)據(jù)庫(kù)),并保存到記錄系統(tǒng)漏麦。如果A1重新應(yīng)用搜索條件客税,將會(huì)獲得不同的結(jié)果集。
如果允許緩存中的過(guò)時(shí)數(shù)據(jù)存在撕贞,并發(fā)的用戶/線程越多更耻,發(fā)生沖突的可能性越大。
鎖策略
那么捏膨,我們能做什么呢酥夭?首先,我們可以采用一種悲觀鎖定方法來(lái)避免沖突脊奋,但是這樣會(huì)以降低系統(tǒng)性能作為代價(jià)。其次疙描,我們可以使用樂(lè)觀鎖定策略诚隙,這種策略可以檢測(cè)沖突,然后解決沖突起胰。第三久又,可以采取一種過(guò)于樂(lè)觀的鎖策略,完全忽視沖突效五。
悲觀鎖
悲觀鎖指的是實(shí)體在應(yīng)用中存儲(chǔ)(通常是以對(duì)象的形式)的整個(gè)生命周期內(nèi)地消,在數(shù)據(jù)庫(kù)中被鎖定。鎖定限制或者阻止其他用戶使用數(shù)據(jù)庫(kù)中的這個(gè)實(shí)體畏妖。寫(xiě)鎖表示鎖的持有者打算更新實(shí)體脉执,在此期間禁止任何人讀取,更新或者刪除實(shí)體戒劫。讀鎖表示鎖的持有者不希望實(shí)體在鎖定期間被改變半夷,它允許其他人讀取實(shí)體,但是不能更新或刪除該實(shí)體迅细。鎖的范圍可能是整個(gè)數(shù)據(jù)庫(kù)巫橄,表,多行或者單行茵典。這些鎖分別被稱(chēng)為數(shù)據(jù)庫(kù)鎖湘换,表鎖,頁(yè)鎖和行鎖。
悲觀鎖定的優(yōu)點(diǎn)是易于實(shí)現(xiàn)彩倚,并且保證對(duì)數(shù)據(jù)庫(kù)的更改是一致和安全的筹我。主要的缺點(diǎn)是此方法不可擴(kuò)展。當(dāng)系統(tǒng)有許多用戶時(shí)署恍,或者當(dāng)事務(wù)涉及更多數(shù)量的實(shí)體時(shí)崎溃,或者當(dāng)事務(wù)長(zhǎng)時(shí)間存在時(shí),不得不等待鎖釋放的情況會(huì)大大增加盯质,因此會(huì)限制系統(tǒng)實(shí)際可以同時(shí)支持的用戶數(shù)量袁串。
樂(lè)觀鎖
在多用戶系統(tǒng)中,沖突不頻繁的情況是很常見(jiàn)的呼巷。雖然我們兩個(gè)人都在使用Customer
對(duì)象囱修,但是當(dāng)我使用John Berg對(duì)象時(shí),你正在使用Wayne Miller對(duì)象王悍,因此不會(huì)發(fā)生沖突破镰。在這樣的情況下,樂(lè)觀鎖定會(huì)成為可行的并發(fā)控制策略压储。解決的思路是鲜漩,程序員在知道發(fā)生沖突的概率很低的情況下,不選擇試圖阻止它們集惋,而是選擇檢測(cè)沖突孕似,并且在沖突發(fā)生的時(shí)候解決它。
圖一描述了在使用樂(lè)觀鎖定時(shí)更新對(duì)象的邏輯刮刑。應(yīng)用程序?qū)?duì)象讀入內(nèi)存的過(guò)程中喉祭,對(duì)數(shù)據(jù)添加讀鎖并在讀完后釋放。在該時(shí)間點(diǎn)雷绢,可以對(duì)該行進(jìn)行標(biāo)記以便檢測(cè)沖突(后面會(huì)更詳細(xì)地說(shuō)明)泛烙。然后應(yīng)用程序操作對(duì)象,在要更新數(shù)據(jù)的時(shí)候翘紊,先獲得對(duì)數(shù)據(jù)的寫(xiě)鎖定蔽氨,并讀取數(shù)據(jù)源,以便確定是否有沖突帆疟。在確定沒(méi)有沖突的情況下孵滞,程序更新數(shù)據(jù)并釋放寫(xiě)鎖。如果檢測(cè)到?jīng)_突鸯匹,比如數(shù)據(jù)在最初被讀入內(nèi)存后被另一個(gè)進(jìn)程更新坊饶,那么沖突將需要被解決。
確定是否發(fā)生沖突有兩種基本策略殴蓬。
- 使用唯一標(biāo)志符標(biāo)記源匿级。源數(shù)據(jù)在每次更新時(shí)都會(huì)被唯一標(biāo)識(shí)蟋滴。在更新的時(shí)候檢查標(biāo)識(shí),如果和最初的值不同痘绎,那么說(shuō)明數(shù)據(jù)源被改了津函。以下是不同類(lèi)型的并發(fā)標(biāo)識(shí)。
- 日期時(shí)間戳(這個(gè)值應(yīng)該由數(shù)據(jù)庫(kù)服務(wù)器來(lái)分配孤页,因?yàn)椴荒苤竿杏?jì)算機(jī)的時(shí)鐘都同步)
- 增量計(jì)數(shù)器
- 用戶ID(只有每個(gè)人都有唯一的ID尔苦,并且只登陸一臺(tái)機(jī)器,并且應(yīng)用程序確定在內(nèi)存中只存在一個(gè)對(duì)象的副本時(shí)行施,這種方法才有效)允坚。
- 由全局唯一代理鍵生成器生成的值。
- 保留源數(shù)據(jù)的副本蛾号。在更新操作時(shí)檢索源數(shù)據(jù)稠项,并與最初檢索的值進(jìn)行比較。如果值不一樣鲜结,那就說(shuō)明發(fā)生了沖突展运。如果無(wú)法向數(shù)據(jù)庫(kù)schema添加足夠的列來(lái)維護(hù)并發(fā)標(biāo)記,這個(gè)策略將是唯一的選擇精刷。
圖一描述了一種樸素的方法拗胜,事實(shí)上有一些方法可以用來(lái)減少數(shù)據(jù)庫(kù)交互的次數(shù)。對(duì)數(shù)據(jù)庫(kù)的前三個(gè)請(qǐng)求--初始鎖定怒允,標(biāo)記源數(shù)據(jù)埂软,解鎖--可以作為單個(gè)事務(wù)執(zhí)行。接下來(lái)的兩個(gè)交互误算,鎖定和獲取源數(shù)據(jù)的副本,也可以合并成一次數(shù)據(jù)庫(kù)請(qǐng)求迷殿。此外儿礼,更新和解鎖可以類(lèi)似地組合。另外一種改進(jìn)的方式是將最后四個(gè)交互組合成單個(gè)事務(wù)庆寺,在數(shù)據(jù)庫(kù)服務(wù)器而不是應(yīng)用服務(wù)器上執(zhí)行沖突檢測(cè)蚊夫。
過(guò)度樂(lè)觀鎖定
這種策略假設(shè)沖突永遠(yuǎn)不會(huì)發(fā)生,既不會(huì)試圖避免也不會(huì)檢測(cè)沖突懦尝。此策略適用于單用戶系統(tǒng)知纷,和系統(tǒng)一次只能由一個(gè)用戶或者進(jìn)程訪問(wèn)的系統(tǒng)。這樣的情況確實(shí)是會(huì)發(fā)生的陵霉。重要的是要認(rèn)識(shí)到琅轧,這個(gè)策略完全不適合多用戶系統(tǒng)。
沖突解決策略
在解決沖突的時(shí)候有五種基本策略:
- 放棄
- 展示問(wèn)題讓用戶決定
- 合并改動(dòng)
- 記錄沖突讓后來(lái)的人決定
- 無(wú)視沖突踊挠,直接覆蓋乍桂。
要知道沖突的粒度也很重要冲杀。假設(shè)兩個(gè)人操作同一Customer
實(shí)體的副本。一個(gè)人更新了客戶的姓名睹酌,另一個(gè)人更新了他們的購(gòu)物偏好設(shè)置权谁,那么可以從這次的沖突中恢復(fù)。實(shí)際上憋沿,更新相同的用戶時(shí)旺芽,沖突發(fā)生在實(shí)體級(jí)別,但是不是在屬性級(jí)別辐啄。在實(shí)體級(jí)別檢測(cè)到潛在沖突采章,然后在屬性級(jí)別解決是很常見(jiàn)的。
策略的選擇
為了簡(jiǎn)單起見(jiàn)则披,許多項(xiàng)目團(tuán)隊(duì)選擇單一的鎖定策略并將其應(yīng)用到所有表共缕。當(dāng)應(yīng)用程序中所有表或至少大多數(shù)表具有相同的訪問(wèn)特性時(shí),此方法是很有效的士复。然而图谷,對(duì)于更復(fù)雜的應(yīng)用程序,可能需要基于各個(gè)表的訪問(wèn)特性實(shí)現(xiàn)幾個(gè)鎖定策略阱洪。Willem Bogaerts建議的一種方法是按類(lèi)型對(duì)每個(gè)表進(jìn)行分類(lèi)便贵,來(lái)為其提供鎖定策略的指導(dǎo)。如下表
表類(lèi)型 | 例子 | 推薦的策略 |
---|---|---|
實(shí)時(shí)高并發(fā) | <ul><li>賬號(hào)</li></ul> | <ul><li>樂(lè)觀鎖(第一選擇)</li><li>悲觀鎖(第二選擇)</li></ul> |
實(shí)時(shí)低并發(fā) | <ul><li>顧客</li><li>保單</li></ul> | <ul><li>悲觀鎖(第一選擇)</li><li>樂(lè)觀鎖(第二選擇)</li></ul> |
日志(通常是附加日志) | <ul><li>訪問(wèn)日志</li><li>賬戶歷史</li><li>事務(wù)記錄</li></ul> | <ul><li>過(guò)度樂(lè)觀鎖</li></ul> |
查找/引用(通常只讀) | <ul><li>州</li><li>付款方式</li></ul> | <ul><li>過(guò)度樂(lè)觀鎖</li></ul> |