??最近在研究Oracle鎖機制的時候發現網上的資料魚龍混雜將,很多將問題復雜化,讓人感覺沒有條理性。經過查詢原始理論資料,總結如下:
???????? 在數據庫理論中,我們知道。我們在執行并發訪問數據庫表時,如果沒有任何一致性控制措施,那么會出現以下幾種數據不一致的情況:1)提交被覆蓋;2)不可重復讀(其中包括了幻讀);3)讀“臟數據”。經過仔細分析,發現引起這些情況的根本原因是:在對數據庫公共資源訪問時,出現了事務交叉的情況。因此,在數據庫的理論里有“可串行化”這種衡量標準,就是說:如果我們在并發情況下通過對并發事務的控制得到的最終結果和我們按順序一個個執行事務是一樣的結果,那么我們就認為這種控制是“可串行化”的,是正確的。
????????? 鎖機制是當前解決這些問題的最有效的辦法,以下的兩個概念“基本鎖”和“意向鎖”是最基本的兩個概念,是構成所有其他鎖的基礎。
????????? 1)為了在并發性和一致性這對矛盾中合理取舍,提供了兩種最基本的鎖:共享鎖(讀鎖)、排它鎖(寫鎖)。
?????????共享鎖(S):如果事務T對表A加上共享鎖,則事務T可以讀該表,但是不能修改該表數據。其他事務也只能對該對象加上共享鎖,但是不能加上排他鎖。這就保證了其他事務可以讀A表數據,但是不能修改A表數據(這就不會影響到讀該表的事務了,有利于并發操作)。
???????? 排他鎖(X):如果事務T對對象A加上排他鎖,則只允許T對A對象讀取和修改,其他事務不能對A增加任何鎖,只到T釋放加載A上的排他鎖。
???????? 那么如何使用這些鎖解決問題呢?數據庫的基本理論提出了三種級別的封鎖協議,封的越高,控制的范圍越大,一致性也越好,并發性也越差。具體的大家可以查資料,也很簡單,這里為了節約篇幅不再敘述。
????????? 2)可是動輒就給整個表加鎖,這有點魯莽草率。我們知道,在一個并發的環境下,我們的程序(事務)操作的常常并不是表中的同一樣數據,而這樣大范圍的控制表數據很顯然不利于并發,那么我們可以針對需要縮小控制范圍,于是提出了“多粒度封鎖”的概念。也就是說,程序(事務)既可以封鎖整個表,也可以只封鎖某些和我們有關的行,這樣就不會影響其他程序(事務)到該表中其他的行。可是新的問題又來了,我們如果要對某個表進行某種操作,比如刪除整個表的數據,那么數據庫程序就要遍歷整個表的每個行,這很顯然過于消耗資源,那咋辦?于是,“意向鎖”應運而生。意向鎖的概念基本上是這個意思:如果我們要封鎖表的某個行,則也在該行所在的表上加上鎖。這樣的話在對整個表進行操作時,就不用遍歷表里的所有行了。通過和前邊提到的共享鎖和排他鎖,我們組合出三種鎖:
?????????? 意向共享鎖(IS):如果我們對表加IS鎖,表示該表中的某個行被加上了S鎖。
?????????? 意向排他鎖(IX):如果我們對表加IX鎖,表時該表中的某個行上被加上X鎖。
?????????? 共享意向排他鎖(ISX)。表示先對某個表加上S鎖,然后再加上X鎖,表示要讀取整個表的數據,但是只對其中的一部分行做修改。
????????那么最終一共得到5種鎖:共享鎖(S)、排他鎖(T)、意向共享鎖(IS)、意向排他鎖(IX)、共享意向排他鎖(SIX)。
??????? 好,談完理論,我們和具體的數據庫Oracle結合起來,Oracle一共提供了共享鎖(S)、排他鎖(T)、行級共享鎖(RS)、行級排他鎖(RX)、共享行級排他鎖(SRX)。和上一句話一對比,我們就知道了,它們是一一對應的。可是,Oracle作為一個成功的數據庫,總是有自己別出心裁的地方。可能Oracle認為讀數據的時候加鎖這種機制,在很大程度上是浪費,比如用戶修改的數據大多數是自己的不會影響到別的用戶(只局限于某些行,不會影響其他行)。所以,默認情況下,Oracle在讀取表中數據的時候并沒有加鎖,而是采用了回滾段的策略解決了這個問題,具體的原理在稍后再說。這樣的確是提高了并發性,但是我認為,這其實也是種冒險行為,一旦出現用戶B必須要讀取用戶A的數據,而且要參考讀取的結果進行一些重要的操作時,這個時候就需要我們自己手動去加鎖了(Oracle的確提供了這種功能,讓我們自己用語句去加鎖)。下邊,我們詳細談下Oracle自己鎖的具體行為:
??????? 共享鎖:通過lock table in share mode命令添加該S鎖。在該鎖定模式下,不允許任何用戶更新表。但是允許其他用戶發出select …from for update命令對表添加RS鎖。
??????? 排他鎖:通過lock table in exclusive mode命令添加X鎖。在該鎖定模式下,其他用戶不能對表進行任何的DML和DDL操作,該表上只能進行查詢。
??????? 行級共享鎖:通常是通過select … from for update語句添加的,同時該方法也是我們用來手工鎖定某些記錄的主要方法。比如,當我們在查詢某些記錄的過程中,不希望其他用戶對查詢的記錄進行更新操作,則可以發出這樣的語句。當數據使用完畢以后,直接發出rollback命令將鎖定解除。當表上添加了RS鎖定以后,不允許其他事務對相同的表添加排他鎖,但是允許其他的事務通過DML語句或lock命令鎖定相同表里的其他數據行。
?????? 行級排他鎖:當我們進行DML時會自動在被更新的表上添加RX鎖,或者也可以通過執行lock命令顯式的在表上添加RX鎖。在該鎖定模式下,允許其他的事務通過DML語句修改相同表里的其他數據行,或通過lock命令對相同表添加RX鎖定,但是不允許其他事務對相同的表添加排他鎖(X鎖)。
?????? 共享行級排他鎖:通過lock table in share row exclusive mode命令添加SRX鎖。該鎖定模式比行級排他鎖和共享鎖的級別都要高,這時不能對相同的表進行DML操作,也不能添加共享鎖。
??????? 好了,引用別人的一個例子來描述Oracle如何通過回滾段來解決重復讀和幻讀的問題:
??????? 如果有一個事務A執行以下語句:update employees set last_name='HanSijie'? where employee_id=100;如果當前有一個用戶(假設為B)發出SQL語句,檢索employee_id為100的記錄信息,這時服務器進程發現被檢索的記錄有鎖定標記,說明當前該記錄已經被其他用戶修改了,但是還沒提交。于是根據數據行頭部記錄的ITL槽的槽號,在數據塊頭部找到該ITL槽,并根據其中記錄的undo數據塊的地址,找到該undo 數據塊,將其中所保存的改變前的舊值取出,并據此構建CR(Consistent Read一致性讀)塊,該CR塊中的數據就是被更新的數據塊(也就是58號數據塊)在更新前的內容。于是根據該CR塊的內容,將用戶所需要的信息返回給C。
??????? 以上的例子,可以證明之前我所說的擔心的地方,也就是說,假如我們這個用戶B要參照這個數據來決定下一步程序重要的走向,那么我們在做這件事情的時候,必須也要對該記錄加鎖,因為Oracle并沒有對我們的讀取采取加鎖的行為。我們必須要控制我們在做下一步動作前的這個“決定因素”不能被別人修改。
??????? 另外,還有一點總結,由于我沒有深入研究過多線程編程,只是在研究Struts2時,對Struts2處理Action的多線程問題時研究了下ThreadLocal模式。得出以下總結,可能不完善。個人認為,資源分為兩種,一種是:多個線程必須交互的資源,比如表中的數據,再比如必須參考的某個成員變量。還有一種是:多線程可以不用交互的資源,比如我們Struts2中的成員變量。第一種情況,相當于是個十字路口,在不允許“修天橋”的情況下,多個線程必須要走這個交叉點。第二種情況是,可以通過“修天橋”來避免在同一個空間里的沖突。ThreadLocal就是基于這種思想,是為了降低線程沖突,也是為了節約資源等待的時間,本質上是計算機中的真理“空間換時間”。
??????? 以上內容有很多借鑒了數據庫基本理論,但是將抽象的對象具體到表和表中的行,這樣有助于理解。主要是為了澄清一些概念,比如有人將“幻讀”單獨作為一種不一致情況,而省去了“提交被覆蓋”這種情況。我個人認為,很顯然,幻讀只是不可重復讀的一種情況(幻讀強調的是集合)。中間也摘錄了其他人文章中的部分內容。如果中間有錯誤,希望留言評論。