目錄
一、死鎖
二、鎖的區間劃分
1、間隙鎖(Gap Locks)
2、臨鍵鎖(Next-key Locks)
三、鎖的粒度劃分
1、表級鎖(Table-level lock)
2、行級鎖(Record Locks)
3、頁級鎖
四、鎖級別劃分
2、排它鎖 / 獨占鎖(exclusive lock,即X鎖)
3、意向鎖
五、加鎖方式分類
1、自動鎖( Automatic Locks)
2、顯示鎖(LOCK TABLES )
六、鎖的使用方式分類
1、樂觀鎖(Optimistic Lock)
2、悲觀鎖(Pessimistic Lock)
總結
鎖是計算機協調多個進程或線程并發訪問某一個資源的機制,在數據庫中,除傳統的計算資源(CPU、RAM、I/O)的爭用以外,數據也是一種供許多用戶共享的資源。如何保證數據并發訪問的一致性、有效性是所在有數據庫必須解決的一個問題,鎖沖突也是影響數據庫并發訪問性能的一個重要因素。從這個角度來說,鎖對數據庫而言顯得尤其重要,也更加復雜。
一、死鎖
講述之前先簡單介紹第一個鎖:死鎖
如下表
CREATE TABLE `test` (`id` int(20) NOT NULL,`name` varchar(20) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
表中數據有:
mysql> SELECT * FROM test;
+----+------+
| id | name |
+----+------+
| 1 | 1 |
| 5 | 5 |
+----+------+
6 rows in set (0.00 sec)
兩個事務對一個表進行如下操作
session1 session2
begin; begin;select * from test where id = 3 for update; select * from test where id = 4 for update;insert into test(id, name) values(3, "test1"); insert into test(id, name) values(4, "test2");鎖等待中
鎖等待解除死鎖,session 2的事務被回滾
上面兩個并發事務一定會發生死鎖(這里之所以限定RR和Serializable兩個隔離級別,是因為只有這兩個級別下才會有間隙鎖/臨鍵鎖,而這是導致死鎖的根本原因)。
select … for update雖然可以用于解決數據庫的并發操作,但在實際項目中卻不建議使用,原因是當查詢條件對應的記錄不存在時,很容易造成死鎖。而造成死鎖的原因和MySQL的鎖機制有關。
二、鎖的區間劃分
1、間隙鎖(Gap Locks)
實例: (3, 4)
間隙鎖是開區間的,是一個在索引記錄之間的間隙上的鎖。
作用:保證某個間隙內的數據在鎖定情況下不會發生任何變化。比如我mysql默認隔離級別下的可重復讀(RR)。
當使用唯一索引來搜索唯一行的語句時,不需要間隙鎖定。如下面語句的id列有唯一索引,此時只會對id值為10的行使用記錄鎖。
select * from t where id = 10 for update;// 注意:普通查詢是快照讀,不需要加鎖
如果,上面語句中id列沒有建立索引或者是非唯一索引時,則語句會產生間隙鎖。
如果,搜索條件里有多個查詢條件(即使每個列都有唯一索引),也是會有間隙鎖的。
根據檢索條件向下尋找最靠近檢索條件的記錄值A作為左區間,向上尋找最靠近檢索條件的記錄值B作為右區間,即鎖定的間隙為(A,B),并且,不允許其他區間進行修改的值為查詢的值
2、臨鍵鎖(Next-key Locks)
臨鍵鎖是行鎖+間隙鎖,即臨鍵鎖是是一個左開右閉的區間,比如(- ∞, 1 ] |(1, 3 ] |(3, 4 ] | (4, + ∞)。
InnoDB的默認事務隔離級別是RR,在這種級別下,如果使用select … in share mode或者select … for update語句,那么InnoDB會使用臨鍵鎖,因而可以防止幻讀;但即使你的隔離級別是RR,如果你這是使用普通的select語句,那么InnoDB將是快照讀,不會使用任何鎖,因而還是無法防止幻讀。
三、鎖的粒度劃分
1、表級鎖(Table-level lock)
直接給整個表添加鎖:
select * from student where name = 'tom' for update
InnoDB在使用過程中只要不通過索引檢索數據時,全部是表鎖。
開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突的概率最高,并發度最低
MyISAM在執行查詢語句(SELECT)前,會自動給涉及的所有表加讀鎖,在執行更新操作(UPDATE、DELETE、INSERT等)前,會自動給涉及的表加寫鎖,這個過程并不需要用戶干預,因此用戶一般不需要直接用LOCK TABLE命令給MyISAM表顯式加鎖。
2、行級鎖(Record Locks)
InnoDB中給指定的行添加鎖:
select * from student where id > 10 for update
1
InnoDB行鎖是通過給索引上的索引項加鎖來實現的,這一點,MySQL于Oracle不同,后者是通過在數據塊中對相應的數據行加鎖來實現的,InnoDB只有通過索引條件檢索數據,InnoDB才使用行級鎖
行鎖的劣勢:開銷大;加鎖慢;會出現死鎖
行鎖的優勢:鎖的粒度小,發生鎖沖突的概率低;處理并發的能力強
3、頁級鎖
頁級鎖是 MySQL 中比較獨特的一種鎖定級別,在其他數據庫管理軟件中并不常見。
頁級鎖的顆粒度介于行級鎖與表級鎖之間,所以獲取鎖定所需要的資源開銷,以及所能提供的并發處理能力同樣也是介于上面二者之間。另外,頁級鎖和行級鎖一樣,會發生死鎖。
頁級鎖主要應用于 BDB 存儲引擎。
四、鎖級別劃分
1、共享鎖(share lock,即S鎖)
共享鎖(S):又稱讀鎖,允許一個事務去讀取一行,阻止其他事務獲得相同數據集的排它鎖,若事務T對數據對象A加上S鎖,則事務T可以讀A,但不能修改A,其他事務只能對再對A加S鎖,而不能加X鎖,直到T釋放A上的鎖,這保證了其他事務可以讀A,但在釋放A上的S鎖之前不能對A做任何修改。
2、排它鎖 / 獨占鎖(exclusive lock,即X鎖)
排它鎖(X):又稱寫鎖,允許獲取排它鎖的事物更新數據,阻止其他事務取得相同的數據集共享讀鎖和排它寫鎖,若事務T對數據對象A加上X鎖,事物T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T 釋放A上的鎖
3、意向鎖
事物B對一行數據使用行鎖,當有另一個事物A對這個表使用了表鎖,那么這個行鎖就會升級為表鎖,事務A在申請行鎖(寫鎖)之前,數據庫會自動先給事務A申請表的意向排他鎖。當事務B去申請表的寫鎖時就會失敗,因為表上有意向排他鎖之后事務B申請表的寫鎖時會被阻塞。
當一個事務在需要獲取資源的鎖定時,如果該資源已經被排他鎖占用,則數據庫會自動給該事務申請一個該表的意向鎖。如果自己需要一個共享鎖定,就申請一個意向共享鎖。如果需要的是某行(或者某些行)的排他鎖定,則申請一個意向排他鎖。
五、加鎖方式分類
1、自動鎖( Automatic Locks)
當進行一項數據庫操作時,缺省情況下,系統自動為此數據庫操作獲得所有有必要的鎖。
自動鎖分為三種:
DML 鎖:
- 鎖用于控制并發事務中的數據操縱,保證數據的一致性和完整性。
- 保護并發情況下的數據完整性。
- 語句能夠自動地獲得所需的表級鎖(TM)與行級(事務)鎖(TX)。
DDL 鎖:
- 鎖用于保護數據庫對象的結構,如表、索引等的結構定義。
排它 DDL 鎖
- 創建、修改、刪除一個數據庫對象的 DDL 語句獲得操作對象的 排它鎖。
共享 DDL 鎖
- 需在數據庫對象之間建立相互依賴關系的 DDL 語句通常需共享獲得 DDL鎖。
- 如創建一個包,該包中的過程與函數引用了不同的數據庫表,當編譯此包時該事務就獲得了引用表的共享 DDL 鎖。如使用 alter table 語句時,為了維護數據的完成性、一致性、合法性,該事務獲得一排它 DDL 鎖
systemlocks。
2、顯示鎖(LOCK TABLES )
某些情況下,需要用戶顯示的鎖定數據庫操作要用到的數據,才能使數據庫操作執行得更好,顯示鎖是用戶為數據庫對象設定的。
(1) LOCK TABLES
LOCK TABLES tbl_name read|write, tbl_name read|write, ...UNLOCK TABLES #解開全部的鎖,后面不跟表名
施加寫鎖,寫鎖是排他的,不允許別的線程讀和寫,自己施加鎖是不受影響
(2) FLUSH TABLES:將內存中的數據同步到磁盤上,即刷寫操作,但是這個同步過程可以施加鎖,一旦施加鎖的時候,即執行將對應的表同步,關閉,打開,并施加鎖。一旦施加了鎖,此時別的線程讀操作不受影響,但是寫操作將不能被執行,需要解鎖后才能生效
FLUSH TABLES tbl_name,... [WITH READ LOCK];UNLOCK TABLES;鎖住所有的表,注意,可以針對某張表進行上鎖MariaDB [sunny]> flush tables with read lock;Query OK, 0 rows affected (0.00 sec)其他線程,解鎖后才能夠插入數據MariaDB [sunny]> insert into classlist values ("tracy",2,"99");Query OK, 1 row affected (1 min 5.58 sec)
六、鎖的使用方式分類
1、樂觀鎖(Optimistic Lock)
樂觀鎖的特點先進行業務操作,不到萬不得已不去拿鎖。即“樂觀”的認為拿鎖多半是會成功的,因此在進行完業務操作需要實際更新數據的最后一步再去拿一下鎖就好。
樂觀鎖是否在事務中其實都是無所謂的,其底層機制是這樣:在數據庫內部update同一行的時候是不允許并發的,即數據庫每次執行一條update語句時會獲取被update行的寫鎖,直到這一行被成功更新后才釋放。因此在業務操作進行前獲取需要鎖的數據的當前版本號,然后實際更新數據時再次對比版本號確認與之前獲取的相同,并更新版本號,即可確認這之間沒有發生并發的修改。如果更新失敗即可認為老版本的數據已經被并發修改掉而不存在了,此時認為獲取鎖失敗,需要回滾整個業務操作并可根據需要重試整個過程。
2、悲觀鎖(Pessimistic Lock)
悲觀鎖的特點是先獲取鎖,再進行業務操作,即“悲觀”的認為獲取鎖是非常有可能失敗的,因此要先確保獲取鎖成功再進行業務操作。通常所說的“一鎖二查三更新”即指的是使用悲觀鎖。通常來講在數據庫上的悲觀鎖需要數據庫本身提供支持,即通過常用的select … for update操作來實現悲觀鎖。當數據庫執行select for update時會獲取被select中的數據行的行鎖,因此其他并發執行的select for update如果試圖選中同一行則會發生排斥(需要等待行鎖被釋放),因此達到鎖的效果。select for update獲取的行鎖會在當前事務結束時自動釋放,因此必須在事務中使用。
這里需要注意的一點是不同的數據庫對select for update的實現和支持都是有所區別的,例如oracle支持select for update no wait,表示如果拿不到鎖立刻報錯,而不是等待,mysql就沒有no wait這個選項。另外mysql還有個問題是select for update語句執行中所有掃描過的行都會被鎖上,這一點很容易造成問題。因此如果在mysql中用悲觀鎖務必要確定走了索引,而不是全表掃描。
總結
樂觀鎖在不發生取鎖失敗的情況下開銷比悲觀鎖小,但是一旦發生失敗回滾開銷則比較大,因此適合用在取鎖失敗概率比較小的場景,可以提升系統并發性能
樂觀鎖還適用于一些比較特殊的場景,例如在業務操作過程中無法和數據庫保持連接等悲觀鎖無法適用的地方