1、樂觀鎖
樂觀鎖不是數據庫自帶的,需要我們自己去實現。
樂觀鎖是指操作數據庫時(更新操作),想法很樂觀,認爲這次的操作不會導致衝突。
在操作數據時,並不進行任何其他的特殊處理(也就是不加鎖),而在進行更新後,再去判斷是否有衝突了。
通常實現是這樣的:👇
在表中的數據進行操作時(更新),先給數據表加一個版本(version)字段,每操作一次,將那條記錄的版本號加1。
也就是先查詢出那條記錄,獲取出version字段,如果要對那條記錄進行操作(更新),則先判斷此刻version的值是否與剛剛查詢出來時的version的值相等;
如果相等,則說明這段期間,沒有其他程序對其進行操作,則可以執行更新,將version字段的值加1;
如果更新時發現此刻的version值與剛剛獲取出來的version的值不相等,則說明這段期間已經有其他程序對其進行操作了,則不進行更新操作。
舉例:👇
update t_goods set status=2,version=version+1 where id=#{id} and version=#{version};
除了自己手動實現樂觀鎖之外,現在網上許多框架已經封裝好了樂觀鎖的實現,如hibernate,需要時,可以自行搜索"hiberate 樂觀鎖"試試看。
2、悲觀鎖
與樂觀鎖相對應的就是悲觀鎖了。
悲觀鎖就是在操作數據時,認爲此操作會出現數據衝突,所以在進行每次操作時都要通過獲取鎖才能進行對相同數據的操作,這點跟java中的synchronized很相似,所以悲觀鎖需要耗費較多的時間。
另外與樂觀鎖相對應的,悲觀鎖是由數據庫自己實現了的,要用的時候,我們直接調用數據庫的相關語句就可以了。
說到這裏,由悲觀鎖涉及到的另外兩個鎖概念就出來了,它們就是共享鎖與排它鎖。
共享鎖和排它鎖是悲觀鎖的不同的實現,它倆都屬於悲觀鎖的範疇。
2.1:共享鎖
共享鎖指的就是對於多個不同的事務,對同一個資源共享同一個鎖。
都能訪問到數據,但是隻能讀不能修改。
相當於對於同一把門,它擁有多個鑰匙一樣。
就像這樣,你家有一個大門,大門的鑰匙有好幾把,你有一把,你女朋友有一把,你們都可能通過這把鑰匙進入你們家,進去啪啪啪啥的,一下理解了哈,沒錯,這個就是所謂的共享鎖。
剛剛說了,對於悲觀鎖,一般數據庫已經實現了,共享鎖也屬於悲觀鎖的一種,那麼共享鎖在mysql中是通過什麼命令來調用呢。
通過查詢資料,瞭解到通過在執行語句後面加上 lock in share mode就代表對某些資源加上共享鎖了。
比如,我這裏通過mysql打開兩個查詢編輯器,在其中開啓一個事務,並不執行commit語句。
city 表DDL如下:
CREATE TABLE city ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(255) DEFAULT NULL, state varchar(255) DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
begin;
SELECT * from city where id = "1" lock in share mode;
然後在另一個查詢窗口中,對id爲1的數據進行更新
update city set name="666" where id ="1";
此時,操作界面進入了卡頓狀態,過幾秒後,也提示錯誤信息
[SQL]update city set name="666" where id ="1"; [Err] 1205 - Lock wait timeout exceeded; try restarting transaction
那麼證明,對於id=1的記錄加鎖成功了,在上一條記錄還沒有commit之前,這條id=1的記錄被鎖住了,只有在上一個事務釋放掉鎖後才能進行操作,或用共享鎖才能對此數據進行操作。
再實驗一下:
update city set name="666" where id ="1" lock in share mode;
報錯:
[Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'lock in share mode' at line 1
加上共享鎖後,也提示錯誤信息了,通過查詢資料才知道,對於 update,insert,delete 語句會自動加排它鎖的原因
於是,我又試了試 SELECT * from city where id = "1" lock in share mode; 這下成功了。
2.2:排他鎖
排他鎖又稱爲寫鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其他所並存,如一個事務獲取了一個數據行的排他鎖;
其他事務就不能再獲取該行的其他鎖,包括共享鎖和排他鎖,但是獲取排他鎖的事務是可以對數據就行讀取和修改。
與共享鎖類型,在需要執行的語句後面加上 for update 就可以了
3、行鎖
行鎖,由字面意思理解,就是給某一行加上鎖,也就是一條記錄加上鎖。
InnoDB 行鎖是通過給索引上的索引項加鎖來實現的。
所以,只有通過索引條件檢索數據,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖。
其他注意事項:
~ 在不通過索引條件查詢的時候,InnoDB使用的是表鎖,而不是行鎖。
~ 由於MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以即使是訪問不同行的記錄,如果使用了相同的索引鍵,也是會出現鎖衝突的。
~ 當表有多個索引的時候,不同的事務可以使用不同的索引鎖定不同的行,另外,不論是使用主鍵索引、唯一索引或普通索引,InnoDB都會使用行鎖來對數據加鎖。
~ 即便在條件中使用了索引字段,但具體是否使用索引來檢索數據是由MySQL通過判斷不同執行計劃的代價來決定的;
如果MySQL認爲全表掃描效率更高,比如對一些很小的表,它就不會使用索引,這種情況下InnoDB將使用表鎖,而不是行鎖。
因此,在分析鎖衝突時,別忘了檢查SQL的執行計劃,以確認是否真正使用了索引。
隱式加鎖:
~ InnoDB自動加意向鎖。
~ 對於 UPDATE、DELETE 和 INSERT 語句,InnoDB會自動給涉及數據集加排他鎖(X);
~ 對於普通SELECT語句,InnoDB不會加任何鎖;
顯示加鎖:
~ 共享鎖(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
~ 排他鎖(X) :SELECT * FROM table_name WHERE ... FOR UPDATE
4、表鎖
表鎖,和行鎖相對應,給這個表加上鎖。
5、間隙鎖
間隙鎖定義:
nnodb的鎖定規則是通過在指向數據記錄的第一個索引鍵之前和最後一個索引鍵之後的空域空間上標記鎖定信息而實現的。
Innodb的這種鎖定實現方式被稱爲間隙鎖,因爲Query執行過程中通過範圍查找的話,它會鎖定整個範圍內所有的索引鍵值,即使這個鍵值並不存在。
例:假如emp表中只有101條記錄,其empid的值分別是 1,2,…,100,101,下面的SQL:
select * from emp where empid > 100 for update;
是一個範圍條件的檢索,InnoDB不僅會對符合條件的empid值爲101的記錄加鎖,也會對empid大於101(這些記錄並不存在)的“間隙”加鎖。
間隙鎖的缺點:
~ 間隙鎖有一個比較致命的弱點,就是當鎖定一個範圍鍵值之後,即使某些不存在的鍵值也會被無辜的鎖定,而造成在鎖定的時候無法插入鎖定鍵值範圍內的任何數據。
~ 在某些場景下這可能會對性能造成很大的危害 當Query無法利用索引的時候, Innodb會放棄使用行級別鎖定而改用表級別的鎖定,造成併發性能的降低;
~ 當Quuery使用的索引並不包含所有過濾條件的時候,數據檢索使用到的索引鍵所指向的數據可能有部分並不屬於該Query的結果集的行列,但是也會被鎖定,因爲間隙鎖鎖定的是一個範圍,而不是具體的索引鍵;
~ 當Query在使用索引定位數據的時候,如果使用的索引鍵一樣但訪問的數據行不同的時候(索引只是過濾條件的一部分),一樣會被鎖定。
間隙鎖的作用:
防止幻讀,以滿足相關隔離級別的要求。 爲了數據恢復和複製的需要。
注意:
在實際應用開發中,尤其是併發插入比較多的應用,我們要儘量優化業務邏輯,儘量使用相等條件來訪問更新數據,避免使用範圍條件。
InnoDB除了通過範圍條件加鎖時使用間隙鎖外,如果使用相等條件請求給一個不存在的記錄加鎖,InnoDB也會使用間隙鎖。
人生無常大腸包小腸