一文看懂 MySQL事務隔離級別與鎖
資料庫鎖
共享鎖(Shared lock)
例1:
----------------------------------------
T1: select * from table (請想象它需要執行1個小時之久,後面的sql語句請都這麼想象)
T2: update table set column1='hello'
過程:T1執行 (加共享鎖)
T2執行等待T1執行完之後再執行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也會因此而等待。在大併發情況下,讓大家等待顯得效能就太友好了,所以,這裡引入了更新鎖。
更新鎖(Update lock)
為解決死鎖,引入更新鎖
例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執行,準備加更新鎖,但發現已經有一個更新鎖在那兒了,只好等。(T2加的是更新鎖,更新鎖與更新鎖不相容, 如果加的是共享鎖, 共享鎖和更新鎖可以相容,即T1,T2不可同時進行,但是T3,T4,T5只要不是事務,還是可以正常查詢)
當後來有user3、user4...需要查詢table表中的資料時,並不會因為T1的select在執行就被阻塞,照樣能查詢,相比起例6,這提高了效率
例8:
----------------------------------------
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,發現已有更新鎖,則等待,後面步驟就跟第一種情況一樣了。這個例子是說明:排他鎖與更新鎖是不相容的,它們不能同時加在同一子資源上。
排他鎖(獨佔鎖,Exclusive Locks)
這個簡單,即其它事務既不能讀,又不能改排他鎖鎖定的資源。
例9
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。
例10 (假設id都是自增長且連續的)
T1: update table set column1='hello' where id<1000
T2: update table set column1='world' where id>900
如同例9,T1先達,T2立刻也到,T1加的排他鎖會阻塞T2的update.
意向鎖(Intent Locks)
意向鎖就是說在屋(比如代表一個表)門口設定一個標識,說明屋子裡有人(比如代表某些記錄)被鎖住了。另一個人想知道屋子裡是否有人被鎖,不用進屋子裡一個一個的去查,直接看門口標識就行了。
當一個表中的某一行被加上排他鎖後,該表就不能再被加表鎖。資料庫程式如何知道該表不能被加表鎖?一種方式是逐條的判斷該表的每一條記錄是否已經有排他鎖,另一種方式是直接在表這一層級檢查表本身是否有意向鎖,不需要逐條判斷。顯然後者效率高。
例11:
----------------------------------------
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執行表鎖時,只需要看到這個表已經有意向排他鎖存在,就直接等待,而不需要逐條檢查資源了。
計劃鎖(Schema Locks)
例12:
----------------------------------------
alter table .... (加schema locks,稱之為Schema modification (Sch-M) locks
DDL語句都會加Sch-M鎖,該鎖不允許任何其它session連線該表。連都連不了這個表了,當然更不用說想對該表執行什麼sql語句了。
事務隔離級別(MySQL僅InnoDB引擎支援事務,所以也只有InnoDB有事務隔離級別)
Read Uncommit (未提交讀。允許髒讀)
事例:老闆要給程式設計師發工資,程式設計師的工資是3.6萬/月。但是發工資時老闆不小心按錯了數字,按成3.9萬/月,該錢已經打到程式設計師的戶口,但是事務還沒有提交,就在這時,程式設計師去檢視自己這個月的工資,發現比往常多了3千元,以為漲工資了非常高興。但是老闆及時發現了不對,馬上回滾差點就提交了的事務,將數字改成3.6萬再提交。
分析:實際程式設計師這個月的工資還是3.6萬,但是程式設計師看到的是3.9萬。他看到的是老闆還沒提交事務時的資料。這就是髒讀。
一級封鎖協議:
一級封鎖協議,事務在對需要修改的資料上面(就是在發生修改的瞬間) 對其加共享鎖(其他事務不能更改,但是可以讀取-導致“髒讀”),直到事務結束才釋放。
Read committed(讀提交,顧名思義,就是一個事務要等另一個事務提交後才能讀取資料。)
事例:程式設計師拿著信用卡去享受生活(卡里當然是只有3.6萬),當他埋單時(程式設計師事務開啟),收費系統事先檢測到他的卡里有3.6萬,就在這個時候!!程式設計師的妻子要把錢全部轉出充當家用,並提交。當收費系統準備扣款時,再檢測卡里的金額,發現已經沒錢了(第二次檢測金額當然要等待妻子轉出金額事務提交完)。程式設計師就會很鬱悶,明明卡里是有錢的…
分析:這就是讀提交,若有事務對資料進行更新(UPDATE)操作時,讀操作事務要等待這個更新操作事務提交後才能讀取資料,可以解決髒讀問題。但在這個事例中,出現了一個事務範圍內兩個相同的查詢卻返回了不同資料,這就是不可重複讀。
二級封鎖協議:
1)事務 在對需要更新的資料 上(就是發生更新的瞬間) 加 排他鎖 (直到事務結束) , 防止其他事務讀取未提交的資料,這樣,也就避免了 “髒讀” 的情況。2)事務 對當前被讀取的資料 上面加共享鎖(當讀到時加上共享鎖),一旦讀完該行,立即 釋放該 該行的共享鎖
二級封鎖協議除防止了“髒讀”資料,但是不能避免 丟失更新,不可重複讀,幻讀 。
但在二級封鎖協議中,由於讀完資料後立即 釋放共享鎖,所以它不能避免可重複讀 ,同時它也不能避免 丟失更新 ,如果事務A、B同時獲取資源X,然後事務A先發起更新記錄X,那麼 事務B 將等待事務 A 執行完成,然後獲得記錄X 的排他鎖,進行更改。這樣事務 A 的更新將會被丟失。
具體情況如下:
事務A |
事務B |
讀取X=100(同時上共享鎖) |
讀取X=100(同時上共享鎖) |
讀取成功(釋放共享鎖) |
讀取成功(釋放共享鎖) |
UPDATE X=X+100 (上排他鎖) |
|
|
UPDATING A(等待事務A釋放對X的排他鎖) |
事務成功(釋放排他鎖)X=200 |
|
|
UPDATE X=X+200(成功上排他鎖) |
|
事務成功(釋放排他鎖)X=300 |
由此可以看到,事務A的提交被事務B覆蓋了
Repeatable read(重複讀,就是在開始讀取資料(事務開啟)時,不再允許修改操作)
事例:程式設計師拿著信用卡去享受生活(卡里當然是只有3.6萬),當他埋單時(事務開啟,不允許其他事務的UPDATE修改操作),收費系統事先檢測到他的卡里有3.6萬。這個時候他的妻子不能轉出金額了。接下來收費系統就可以扣款了。
分析:重複讀可以解決不可重複讀問題。寫到這裡,應該明白的一點就是,不可重複讀對應的是修改,即UPDATE操作。但是可能還會有幻讀問題。因為幻讀問題對應的是插入INSERT操作,而不是UPDATE操作。
什麼時候會出現幻讀?
事例:程式設計師某一天去消費,花了2千元,然後他的妻子去檢視他今天的消費記錄(全表掃描FTS,妻子事務開啟),看到確實是花了2千元,就在這個時候,程式設計師花了1萬買了一部電腦,即新增INSERT了一條消費記錄,並提交。當妻子列印程式設計師的消費記錄清單時(妻子事務提交),發現花了1.2萬元,似乎出現了幻覺,這就是幻讀。
三級封鎖協議:
三級封鎖協議是:二級封鎖協議加上事務 在讀取資料的瞬間 必須先對其加 共享鎖 ,但是 直到事務結束才釋放 ,這樣保證了可重複讀(既是其他的事務職能讀取該資料,但是不能更新該資料)。
三級封鎖協議除防止了“髒”資料 和不可重複讀 。但是這種情況不能避免 幻讀 和 丟失更新 的情況,在事務 A 沒有完成之前,事務 B 可以新增資料,那麼 當事務 A 再次讀取的時候,事務B 新增的資料會被讀取到
進階:repeatable read 導致死鎖的情況,參考上面講解共享鎖的文章
Serializable 序列化
Serializable 是最高的事務隔離級別,在該級別下,事務序列化順序執行,可以避免髒讀、不可重複讀與幻讀。但是這種事務隔離級別效率低下,比較耗資料庫效能,一般不使用。
四級封鎖協議:
四級封鎖協議是對三級封鎖協議的增強,其實現機制也最為簡單,直接對 事務中 所 讀取 或者 更改的資料所在的表加表鎖,也就是說,其他事務不能 讀寫 該表中的任何資料。這樣所有的 髒讀,不可重複讀,幻讀 ,都得以避免!
值得一提的是:大多數資料庫預設的事務隔離級別是Read committed,比如Sql Server , Oracle。Mysql的預設隔離級別是Repeatable read。
樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖機制採取了更加寬鬆的加鎖機制。悲觀鎖大多數情況下依靠資料庫的鎖機制實現,以保證操作最大程度的獨佔性。但隨之而來的就是資料庫效能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。而樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基於資料版本( Version )記錄機制實現。何謂資料版本?即為資料增加一個版本標識,在基於資料庫表的版本解決方案中,一般是通過為資料庫表增加一個 “version” 欄位來實現。讀取出資料時,將此版本號一同讀出,之後更新時,對此版本號加一。此時,將提交資料的版本資料與資料庫表對應記錄的當前版本資訊進行比對,如果提交的資料版本號大於資料庫表當前版本號,則予以更新,否則認為是過期資料。
悲觀鎖(Pessimistic Lock),正如其名,具有強烈的獨佔和排他特性。它指的是對資料被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個資料處理過程中,將資料處於鎖定狀態。悲觀鎖的實現,往往依靠資料庫提供的鎖機制(也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改資料)。
事務的特性:
1.原子性 事務是資料庫邏輯的工作單元,事務包括的所有操作,要麼都做,要麼都不做。
2.一致性 事務執行的結果是使資料庫從一個一致性狀態變成另一個一致性狀態。一致性與原子性是密切相關的。
3.隔離性 一個事務的執行不能被其他事務干擾。
4.永續性 一個事務一旦提交,它對資料庫中資料的改變應該是永久性的。
MyISAM 和 InnoDB 區別:
1、MyISAM型別的表強調的是效能,但是不支援事務、及外部鍵等高階功能。
- MySQL預設採用的是MyISAM。
- MyISAM不支援事務,所以MyISAM就不存在事務隔離級別了,而InnoDB支援,所有有4種事務隔離級別,InnoDB的AUTOCOMMIT預設是開啟的,即每條SQL語句會預設被封裝成一個事務,自動提交,這樣會影響速度,所以最好是把多條SQL語句顯示放在begin和commit之間,組成一個事務去提交。
- InnoDB支援資料行鎖定,MyISAM不支援行鎖定,只支援鎖定整個表。即MyISAM同一個表上的讀鎖和寫鎖是互斥的,MyISAM併發讀寫時如果等待佇列中既有讀請求又有寫請求,預設寫請求的優先順序高,即使讀請求先到,所以MyISAM不適合於有大量查詢和修改並存的情況,那樣查詢程序會長時間阻塞。因為MyISAM是鎖表,所以某項讀操作比較耗時會使其他寫程序餓死。
- InnoDB支援外來鍵,MyISAM不支援。
- InnoDB的主鍵範圍更大,最大是MyISAM的2倍。
- InnoDB不支援全文索引,而MyISAM支援。全文索引是指對char、varchar和text中的每個詞(停用詞除外)建立倒排序索引。MyISAM的全文索引其實沒啥用,因為它不支援中文分詞,必須由使用者分詞後加入空格再寫到資料表裡,而且少於4個漢字的詞會和停用詞一樣被忽略掉。
- MyISAM支援GIS資料,InnoDB不支援。即MyISAM支援以下空間資料物件:Point,Line,Polygon,Surface等。
- 沒有where的count(*)使用MyISAM要比InnoDB快得多。因為MyISAM內建了一個計數器,count(*)時它直接從計數器中讀,而InnoDB必須掃描全表。所以在InnoDB上執行count(*)時一般要伴隨where,且where中要包含主鍵以外的索引列。為什麼這裡特別強調“主鍵以外”?因為InnoDB中primary index是和raw data存放在一起的,而secondary index則是單獨存放,然後有個指標指向primary key。所以只是count(*)的話使用secondary index掃描更快,而primary key則主要在掃描索引同時要返回raw data時的作用較大。
- MyISAM和InnoDB鎖的不同: https://blog.csdn.net/zhanghongzheng3213/article/details/51753189
2、併發
MyISAM讀寫互相阻塞:不僅會在寫入的時候阻塞讀取,MyISAM還會在讀取的時候阻塞寫入,但讀本身並不會阻塞另外的讀
InnoDB 讀寫阻塞與事務隔離級別相關
3、場景選擇
MyISAM
- 不需要事務支援(不支援)
- 併發相對較低(鎖定機制問題)
- 資料修改相對較少(阻塞問題),以讀為主
- 資料一致性要求不是非常高
- 儘量索引(快取機制)
- 調整讀寫優先順序,根據實際需求確保重要操作更優先
- 啟用延遲插入改善大批量寫入效能
- 儘量順序操作讓insert資料都寫入到尾部,減少阻塞
- 分解大的操作,降低單個操作的阻塞時間
- 降低併發數,某些高併發場景通過應用來進行排隊機制
- 對於相對靜態的資料,充分利用Query Cache可以極大的提高訪問效率
- MyISAM的Count只有在全表掃描的時候特別高效,帶有其他條件的count都需要進行實際的資料訪問
InnoDB
- 需要事務支援(具有較好的事務特性)
- 行級鎖定對高併發有很好的適應能力,但需要確保查詢是通過索引完成
- 資料更新較為頻繁的場景
- 資料一致性要求較高
- 硬體裝置記憶體較大,可以利用InnoDB較好的快取能力來提高記憶體利用率,儘可能減少磁碟 IO
- 主鍵儘可能小,避免給Secondary index帶來過大的空間負擔
- 避免全表掃描,因為會使用表鎖
- 儘可能快取所有的索引和資料,提高響應速度
- 在大批量小插入的時候,儘量自己控制事務而不要使用autocommit自動提交
- 合理設定innodb_flush_log_at_trx_commit引數值,不要過度追求安全性
- 避免主鍵更新,因為這會帶來大量的資料移動
4、其它細節
1)InnoDB 中不儲存表的具體行數,注意的是,當count(*)語句包含 where條件時,兩種表的操作是一樣的
2)對於AUTO_INCREMENT型別的欄位,InnoDB中必須包含只有該欄位的索引,但是在MyISAM表中,可以和其他欄位一起建立聯合索引, 如果你為一個表指定AUTO_INCREMENT列,在資料詞典裡的InnoDB表控制代碼包含一個名為自動增長計數器的計數器,它被用在為該列賦新值。自動增長計數器僅被儲存在主記憶體中,而不是存在磁碟
3)DELETE FROM table時,InnoDB不會重新建立表,而是一行一行的刪除
4)LOAD TABLE FROM MASTER操作對InnoDB是不起作用的,解決方法是首先把InnoDB表改成MyISAM表,匯入資料後再改成InnoDB表,但是對於使用的額外的InnoDB特性(例如外來鍵)的表不適用
5)如果執行大量的SELECT,MyISAM是更好的選擇,如果你的資料執行大量的INSERT或UPDATE,出於效能方面的考慮,應該使用InnoDB表
5、為什麼MyISAM會比Innodb 的查詢速度快
InnoDB 在做SELECT的時候,要維護的東西比MYISAM引擎多很多;
1)InnoDB 要快取資料和索引,MyISAM只快取索引塊,這中間還有換進換出的減少
2)innodb定址要對映到塊,再到行,MyISAM記錄的直接是檔案的OFFSET,定位比INNODB要快
3)InnoDB 還需要維護MVCC一致;雖然你的場景沒有,但他還是需要去檢查和維護
MVCC ( Multi-Version Concurrency Control )多版本併發控制
InnoDB :通過為每一行記錄新增兩個額外的隱藏的值來實現MVCC,這兩個值一個記錄這行資料何時被建立,另外一個記錄這行資料何時過期(或者被刪除)。但是InnoDB並不儲存這些事件發生時的實際時間,相反它只儲存這些事件發生時的系統版本號。這是一個隨著事務的建立而不斷增長的數字。每個事務在事務開始時會記錄它自己的系統版本號。每個查詢必須去檢查每行資料的版本號與事務的版本號是否相同。讓我們來看看當隔離級別是REPEATABLE READ時這種策略是如何應用到特定的操作的
SELECT InnoDB必須每行資料來保證它符合兩個條件
1、InnoDB必須找到一個行的版本,它至少要和事務的版本一樣老(也即它的版本號不大於事務的版本號)。這保證了不管是事務開始之前,或者事務建立時,或者修改了這行資料的時候,這行資料是存在的。
2、這行資料的刪除版本必須是未定義的或者比事務版本要大。這可以保證在事務開始之前這行資料沒有被刪除。
資料庫鎖
共享鎖(Shared lock)
例1:
----------------------------------------
T1: select * from table (請想象它需要執行1個小時之久,後面的sql語句請都這麼想象)
T2: update table set column1='hello'
過程:T1執行 (加共享鎖)
T2執行等待T1執行完之後再執行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也會因此而等待。在大併發情況下,讓大家等待顯得效能就太友好了,所以,這裡引入了更新鎖。
更新鎖(Update lock)
為解決死鎖,引入更新鎖
例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執行,準備加更新鎖,但發現已經有一個更新鎖在那兒了,只好等。(T2加的是更新鎖,更新鎖與更新鎖不相容, 如果加的是共享鎖, 共享鎖和更新鎖可以相容,即T1,T2不可同時進行,但是T3,T4,T5只要不是事務,還是可以正常查詢)
當後來有user3、user4...需要查詢table表中的資料時,並不會因為T1的select在執行就被阻塞,照樣能查詢,相比起例6,這提高了效率
例8:
----------------------------------------
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,發現已有更新鎖,則等待,後面步驟就跟第一種情況一樣了。這個例子是說明:排他鎖與更新鎖是不相容的,它們不能同時加在同一子資源上。
排他鎖(獨佔鎖,Exclusive Locks)
這個簡單,即其它事務既不能讀,又不能改排他鎖鎖定的資源。
例9
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。
例10 (假設id都是自增長且連續的)
T1: update table set column1='hello' where id<1000
T2: update table set column1='world' where id>900
如同例9,T1先達,T2立刻也到,T1加的排他鎖會阻塞T2的update.
意向鎖(Intent Locks)
意向鎖就是說在屋(比如代表一個表)門口設定一個標識,說明屋子裡有人(比如代表某些記錄)被鎖住了。另一個人想知道屋子裡是否有人被鎖,不用進屋子裡一個一個的去查,直接看門口標識就行了。
當一個表中的某一行被加上排他鎖後,該表就不能再被加表鎖。資料庫程式如何知道該表不能被加表鎖?一種方式是逐條的判斷該表的每一條記錄是否已經有排他鎖,另一種方式是直接在表這一層級檢查表本身是否有意向鎖,不需要逐條判斷。顯然後者效率高。
例11:
----------------------------------------
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執行表鎖時,只需要看到這個表已經有意向排他鎖存在,就直接等待,而不需要逐條檢查資源了。
計劃鎖(Schema Locks)
例12: