數據庫大併發操做要考慮死鎖和鎖的性能問題。這裏作個簡明解釋,爲下面描述方便,這裏用T1表明一個數據庫執行請求,T2表明另外一個請求,也能夠理解爲T1爲一個線程,T2 爲另外一個線程。T3,T4以此類推。下面以SQL Server爲例。html
例1: ---------------------------------------- T1: select * from table (請想象它須要執行1個小時之久,後面的sql語句請都這麼想象) T2: update table set column1='hello' 過程: T1運行 (加共享鎖) T2運行 If T1 還沒執行完 T2等...... else 鎖被釋放 T2執行 endif T2之因此要等,是由於T2在執行update前,試圖對table表加一個排他鎖, 而數據庫規定同一資源上不能同時共存共享鎖和排他鎖。因此T2必須等T1 執行完,釋放了共享鎖,才能加上排他鎖,而後才能開始執行update語句。 例2: ---------------------------------------- T1: select * from table T2: select * from table 這裏T2不用等待T1執行完,而是能夠立刻執行。 分析: T1運行,則table被加鎖,好比叫lockA T2運行,再對table加一個共享鎖,好比叫lockB。 兩個鎖是能夠同時存在於同一資源上的(好比同一個表上)。這被稱爲共 享鎖與共享鎖兼容。這意味着共享鎖不阻止其它session同時讀資源,但阻 止其它session update 例3: ---------------------------------------- T1: select * from table T2: select * from table T3: update table set column1='hello' 此次,T2不用等T1運行完就能運行,T3卻要等T1和T2都運行完才能運行。 由於T3必須等T1和T2的共享鎖所有釋放才能進行加排他鎖而後執行update 操做。 例4:(死鎖的發生) ---------------------------------------- T1: begin tran select * from table (holdlock) (holdlock意思是加共享鎖,直到事務結束才釋放) update table set column1='hello' T2: begin tran select * from table(holdlock) update table set column1='world' 假設T1和T2同時達到select,T1對table加共享鎖,T2也對加共享鎖,當 T1的select執行完,準備執行update時,根據鎖機制,T1的共享鎖須要升 級到排他鎖才能執行接下來的update.在升級排他鎖前,必須等table上的 其它共享鎖釋放,但由於holdlock這樣的共享鎖只有等事務結束後才釋放, 因此由於T2的共享鎖不釋放而致使T1等(等T2釋放共享鎖,本身好升級成排 他鎖),同理,也由於T1的共享鎖不釋放而致使T2等。死鎖產生了。 例5: ---------------------------------------- T1: begin tran update table set column1='hello' where id=10 T2: begin tran update table set column1='world' where id=20 這種語句雖然最爲常見,不少人以爲它有機會產生死鎖,但實際上要看情 況,若是id是主鍵上面有索引,那麼T1會一會兒找到該條記錄(id=10的記 錄),而後對該條記錄加排他鎖,T2,一樣,一會兒經過索引定位到記錄, 而後對id=20的記錄加排他鎖,這樣T1和T2各更新各的,互不影響。T2也不 須要等。 但若是id是普通的一列,沒有索引。那麼當T1對id=10這一行加排他鎖後, T2爲了找到id=20,須要對全表掃描,那麼就會預先對錶加上共享鎖或更新 鎖或排他鎖(依賴於數據庫執行策略和方式,好比第一次執行和第二次執行 數據庫執行策略就會不一樣)。但由於T1已經爲一條記錄加了排他鎖,致使 T2的全表掃描進行不下去,就致使T2等待。 死鎖怎麼解決呢?一種辦法是,以下: 例6: ---------------------------------------- T1: begin tran select * from table(xlock) (xlock意思是直接對錶加排他鎖) update table set column1='hello' T2: begin tran select * from table(xlock) update table set column1='world' 這樣,當T1的select 執行時,直接對錶加上了排他鎖,T2在執行select時,就須要等T1事物徹底執行完才能執行。排除了死鎖發生。 但當第三個user過來想執行一個查詢語句時,也由於排他鎖的存在而不得不等待,第四個、第五個user也會所以而等待。在大併發 狀況下,讓你們等待效果可想而知,因此,這裏引入了更新鎖。
爲解決死鎖,引入更新鎖。 例7: ---------------------------------------- T1: begin tran select * from table(updlock) (加更新鎖) update table set column1='hello' T2: begin tran select * from table(updlock) update table set column1='world' 更新鎖的意思是:「我如今只想讀,大家別人也能夠讀,但我未來可能會作更新操做,我已經獲取了從共享鎖(用來讀)到排他鎖 (用來更新)的資格」。一個事務只能有一個更新鎖獲此資格。 T1執行select,加更新鎖。 T2運行,準備加更新鎖,但發現已經有一個更新鎖在那兒了,只好等。 當後來有user三、user4...須要查詢table表中的數據時,並不會由於T1的select在執行就被阻塞,照樣能查詢,相比起例6,這提升 了效率。 例8: ---------------------------------------- T1: select * from table(updlock) (加更新鎖) T2: select * from table(updlock) (等待,直到T1釋放更新鎖,由於同一時間不能在同一資源上有兩個更新鎖) T3: select * from table (加共享鎖,但不用等updlock釋放,就能夠讀) 這個例子是說明:共享鎖和更新鎖能夠同時在同一個資源上。這被稱爲共享鎖和更新鎖是兼容的。 例9: ---------------------------------------- T1: begin select * from table(updlock) (加更新鎖) update table set column1='hello' (重點:這裏T1作update時,不須要等T2釋放什麼,而是直接把更新鎖升級爲排他鎖,而後執行update) T2: begin select * from table (T1加的更新鎖不影響T2讀取) update table set column1='world' (T2的update須要等T1的update作完才能執行) 咱們以這個例子來加深更新鎖的理解, 第一種狀況:T1先達,T2緊接到達;在這種狀況中,T1先對錶加更新鎖,T2對錶加共享鎖,假設T2的select先執行完,準備執行update, 發現已有更新鎖存在,T2等。T1執行這時才執行完select,準備執行update,更新鎖升級爲排他鎖,而後執行update,執行完成,事務 結束,釋放鎖,T2才輪到執行update。 第二種狀況:T2先達,T1緊接達;在這種狀況,T2先對錶加共享鎖,T1達後,T1對錶加更新鎖,假設T2 select先結束,準備 update,發現已有更新鎖,則等待,後面步驟就跟第一種狀況同樣了。 這個例子是說明:排他鎖與更新鎖是不兼容的,它們不能同時加在同一子資源上。
這個簡單,即其它事務既不能讀,又不能改排他鎖鎖定的資源。 例10 T1: update table set column1='hello' where id<1000 T2: update table set column1='world' where id>1000 假設T1先達,T2隨後至,這個過程當中T1會對id<1000的記錄施加排他鎖.但不會阻塞T2的update。 例11 (假設id都是自增加且連續的) T1: update table set column1='hello' where id<1000 T2: update table set column1='world' where id>900 如同例10,T1先達,T2馬上也到,T1加的排他鎖會阻塞T2的update.
意向鎖就是說在屋(好比表明一個表)門口設置一個標識,說明屋子裏有人(好比表明某些記錄)被鎖住了。另外一我的想知道屋子 裏是否有人被鎖,不用進屋子裏一個一個的去查,直接看門口標識就好了。 當一個表中的某一行被加上排他鎖後,該表就不能再被加表鎖。數據庫程序如何知道該表不能被加表鎖?一種方式是逐條的判斷該 表的每一條記錄是否已經有排他鎖,另外一種方式是直接在表這一層級檢查表自己是否有意向鎖,不須要逐條判斷。顯而後者效率高。 例12: ---------------------------------------- T1: begin tran select * from table (xlock) where id=10 --意思是對id=10這一行強加排他鎖 T2: begin tran select * from table (tablock) --意思是要加表級鎖 假設T1先執行,T2後執行,T2執行時,欲加表鎖,爲判斷是否能夠加表鎖,數據庫系統要逐條判斷table表每行記錄是否已有排他鎖, 若是發現其中一行已經有排他鎖了,就不容許再加表鎖了。只是這樣逐條判斷效率過低了。 實際上,數據庫系統不是這樣工做的。當T1的select執行時,系統對錶table的id=10的這一行加了排他鎖,還同時悄悄的對整個表 加了意向排他鎖(IX),當T2執行表鎖時,只須要看到這個表已經有意向排他鎖存在,就直接等待,而不須要逐條檢查資源了。 例13: ---------------------------------------- T1: begin tran update table set column1='hello' where id=1 T2: begin tran update table set column1='world' where id=1 這個例子和上面的例子實際效果相同,T1執行,系統對table同時對行加排他鎖、對頁加意向排他鎖、對錶加意向排他鎖。
例14: ---------------------------------------- alter table .... (加schema locks,稱之爲Schema modification (Sch-M) locks DDL語句都會加Sch-M鎖 該鎖不容許任何其它session鏈接該表。連都連不了這個表了,固然更不用說想對該表執行什麼sql語句了。 例15: ---------------------------------------- 用jdbc向數據庫發送了一條新的sql語句,數據庫要先對之進行編譯,在編譯期間,也會加鎖,稱之爲:Schema stability (Sch-S) locks select * from tableA 編譯這條語句過程當中,其它session能夠對錶tableA作任何操做(update,delete,加排他鎖等等),但不能作DDL(好比alter table)操做。
如何加鎖,什麼時候加鎖,加什麼鎖,你能夠經過hint手工強行指定,但大可能是數據庫系統自動決定的。這就是爲何咱們能夠不懂鎖也可 以高高興興的寫SQL。 例15: ---------------------------------------- T1: begin tran update table set column1='hello' where id=1 T2: SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED -- 事務隔離級別爲容許髒讀 go select * from table where id=1 這裏,T2的select能夠查出結果。若是事務隔離級別不設爲髒讀,則T2會等T1事物執行完才能讀出結果。 數據庫如何自動加鎖的? 1) T1執行,數據庫自動加排他鎖 2) T2執行,數據庫發現事務隔離級別容許髒讀,便不加共享鎖。不加共享鎖,則不會與已有的排他鎖衝突,因此能夠髒讀。 例16: ---------------------------------------- T1: begin tran update table set column1='hello' where id=1 T2: select * from table where id=1 --爲指定隔離級別,則使用系統默認隔離級別,它不容許髒讀 若是事務級別不設爲髒讀,則: 1) T1執行,數據庫自動加排他鎖 2) T2執行,數據庫發現事務隔離級別不容許髒讀,便準備爲這次select過程加共享鎖,但發現加不上,由於已經有排他鎖了,因此就 等啊等。直到T1執行完,釋放了排他鎖,T2才加上了共享鎖,而後開始讀....
鎖的粒度就是指鎖的生效範圍,就是說是行鎖,仍是頁鎖,仍是整表鎖. 鎖的粒度一樣既能夠由數據庫自動管理,也能夠經過手工指定hint來管理。程序員
例17: ---------------------------------------- T1: select * from table (paglock) T2: update table set column1='hello' where id>10 T1執行時,會先對第一頁加鎖,讀完第一頁後,釋放鎖,再對第二頁加鎖,依此類推。假設前10行記錄剛好是一頁(固然,通常不可能 一頁只有10行記錄),那麼T1執行到第一頁查詢時,並不會阻塞T2的更新。 例18: ---------------------------------------- T1: select * from table (rowlock) T2: update table set column1='hello' where id=10 T1執行時,對每行加共享鎖,讀取,而後釋放,再對下一行加鎖;T2執行時,會對id=10的那一行試圖加鎖,只要該行沒有被T1加上行鎖, T2就能夠順利執行update操做。 例19: ---------------------------------------- T1: select * from table (tablock) T2: update table set column1='hello' where id = 10 T1執行,對整個表加共享鎖. T1必須徹底查詢完,T2才能夠容許加鎖,並開始更新。 以上3例是手工指定鎖的粒度,也能夠經過設定事務隔離級別,讓數據庫自動設置鎖的粒度。不一樣的事務隔離級別,數據庫會有不一樣的 加鎖策略(好比加什麼類型的鎖,加什麼粒度的鎖)。具體請查聯機手冊。
手工指定的鎖優先, 例20: ---------------------------------------- T1: GO SET TRANSACTION ISOLATION LEVEL SERIALIZABLE GO BEGIN TRANSACTION SELECT * FROM table (NOLOCK) GO T2: update table set column1='hello' where id=10 T1是事物隔離級別爲最高級,串行鎖,數據庫系統本應對後面的select語句自動加表級鎖,但由於手工指定了NOLOCK,因此該select 語句不會加任何鎖,因此T2也就不會有任何阻塞。
1) holdlock 對錶加共享鎖,且事務不完成,共享鎖不釋放。 2) tablock 對錶加共享鎖,只要statement不完成,共享鎖不釋放。 與holdlock區別,見下例: 例21 ---------------------------------------- T1: begin tran select * from table (tablock) T2: begin tran update table set column1='hello' where id = 10 T1執行完select,就會釋放共享鎖,而後T2就能夠執行update. 此之謂tablock. 下面咱們看holdlock 例22 ---------------------------------------- T1: begin tran select * from table (holdlock) T2: begin tran update table set column1='hello' where id = 10 T1執行完select,共享鎖仍然不會釋放,仍然會被hold(持有),T2也所以必須等待而不能update. 當T1最後執行了commit或 rollback說明這一個事務結束了,T2才取得執行權。 3) TABLOCKX 對錶加排他鎖 例23: ---------------------------------------- T1: select * from table(tablockx) (強行加排他鎖) 其它session就沒法對這個表進行讀和更新了,除非T1執行完了,就會自動釋放排他鎖。 例24: ---------------------------------------- T1: begin tran select * from table(tablockx) 此次,單單select執行完還不行,必須整個事務完成(執行了commit或rollback後)纔會釋放排他鎖。 4) xlock 加排他鎖 那它跟tablockx有何區別呢? 它能夠這樣用, 例25: ---------------------------------------- select * from table(xlock paglock) 對page加排他鎖 而TABLELOCX不能這麼用。 xlock還可這麼用:select * from table(xlock tablock) 效果等同於select * from table(tablockx)
例26sql
SET LOCK_TIMEOUT 4000 用來設置鎖等待時間,單位是毫秒,4000意味着等待 4秒能夠用select @@LOCK_TIMEOUT查看當前session的鎖超時設置。-1 意味着 永遠等待。 T1: begin tran udpate table set column1='hello' where id = 10 T2: set lock_timeout 4000 select * from table wehre id = 10
T2執行時,會等待T1釋放排他鎖,等了4秒鐘,若是T1尚未釋放排他鎖,T2就會拋出異常: Lock request time out period exceeded.數據庫
| Requested mode | IS | S | U | IX | SIX | X | | Intent shared (IS) | Yes | Yes | Yes | Yes | Yes | No | | Shared (S) | Yes | Yes | Yes | No | No | No | | Update (U) | Yes | Yes | No | No | No | No | | Intent exclusive (IX) | Yes | No | No | Yes | No | No | | Shared with intent exclusive (SIX) | Yes | No | No | No | No | No | | Exclusive (X) | No | No | No | No | No | No |
不管是數據庫系統自己的鎖機制,仍是樂觀鎖這種業務數據級別上的鎖機制,本質上都是對狀態位的讀、寫、判斷。session
轉載:http://www.cnblogs.com/zhouqianhua/archive/2011/04/15/2017049.html併發