加鎖是實現數據庫併發控制的一個很是重要的技術。當事務在對某個數據對象進行操做前,先向系統發出請求,對其加鎖。加鎖後事務就對該數據對象有了必定的控制,在該事務釋放鎖以前,其餘的事務不能對此數據對象進行更新操做。html
鎖是計算機協調多個進程或線程併發訪問某一資源的機制。mysql
鎖保證數據併發訪問的一致性、有效性;算法
鎖衝突也是影響數據庫併發訪問性能的一個重要因素。sql
鎖是Mysql在服務器層和存儲引擎層的的併發控制。數據庫
數據庫是一個多用戶使用的共享資源。當多個用戶併發地存取數據時,在數據庫中就會產生多個事務同時存取同一數據的狀況。若對併發操做不加控制就可能會讀取和存儲不正確的數據,破壞數據庫的一致性。安全
鎖是用於管理對公共資源的併發控制。也就是說在併發的狀況下,會出現資源競爭,因此須要加鎖。bash
加鎖解決了 多用戶環境下保證數據庫完整性和一致性。服務器
Lock的對象是事務,用來鎖定的是數據庫中的對象,如表、頁、行。而且通常lock的對象僅在事務commit或rollback後進行釋放(不一樣事務隔離級別釋放的時間可能不一樣)。session
共享鎖||讀鎖||S 鎖(share lock):其餘事務能夠讀,但不能寫。容許一個事務去讀一行,阻止其餘事務得到相同數據集的排他鎖。數據結構
排他鎖||寫鎖||X 鎖(exclusive) :其餘事務不能讀取,也不能寫。容許得到排他鎖的事務更新數據,阻止其餘事務取得相同數據集的共享讀鎖和排他寫鎖。
類型細分:
意向共享鎖(IS Lock/intent share lock)
意向排他鎖||互斥鎖(IX Lock/intent exclusive lock)
悲觀鎖||保守鎖(pessimistic locking):假定會發生併發衝突,屏蔽一切可能違反數據完整性的操做。
悲觀鎖是數據庫層面加鎖,都會阻塞去等待鎖。
樂觀鎖(optimistic locking):假設不會發生併發衝突,只在提交操做時檢查是否違反數據完整性。
樂觀鎖是一種思想,具體實現是,表中有一個版本字段,第一次讀的時候,獲取到這個字段。處理完業務邏輯開始更新的時候,須要再次查看該字段的值是否和第一次的同樣。若是同樣更新,反之拒絕。之因此叫樂觀,由於這個模式沒有從數據庫加鎖,等到更新的時候再判斷是否能夠更新。
缺點:併發很高的時候,多了不少無用的重試。樂觀鎖,不能解決髒讀的問題。
鎖的開銷是較爲昂貴的,鎖策略其實就是保證了線程安全的同時獲取最大的性能之間的平衡策略。
行鎖:即只容許事務讀一行數據。行鎖的粒度實在每一條行數據,固然也帶來了最大開銷,可是行鎖能夠最大限度的支持併發處理。
開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的機率最低,併發度也最高。
最大程度的支持併發,同時也帶來了最大的鎖開銷。
行級鎖更適合於有大量按索引條件併發更新少許不一樣數據,同時又有併發查詢的應用,如一些在線事務處理(OLTP)系統
在 InnoDB 中,除單個 SQL 組成的事務外,鎖是逐步得到的,這就決定了在 InnoDB 中發生死鎖是可能的。
行級鎖只在存儲引擎層實現,而Mysql服務器層沒有實現。
表鎖:容許事務在行級上的鎖和表級上的鎖同時存在。鎖定整個表,開銷最小,可是也阻塞了整個表。
開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的機率最高,併發度最低。
這些存儲引擎經過老是一次性同時獲取全部須要的鎖以及老是按相同的順序獲取表鎖來避免死鎖。
表級鎖更適合於以查詢爲主,併發用戶少,只有少許按索引條件更新數據的應用,如Web 應用。
若一個用戶正在執行寫操做,會獲取排他的「寫鎖」,這可能會鎖定整個表,阻塞其餘用戶的讀、寫操做;
若一個用戶正在執行讀操做,會先獲取共享鎖「讀鎖」,這個鎖運行其餘讀鎖併發的對這個表進行讀取,互不干擾。只要沒有寫鎖的進入,讀鎖能夠是併發讀取統一資源的。
Mysql的表級別鎖分爲兩類:元數據鎖(Metadata Lock,MDL)、表鎖。
元數據鎖(MDL) 不須要顯式使用,在訪問一個表的時候會被自動加上。這個特性須要MySQL5.5版本以上纔會支持,
當對一個表作增刪改查的時候,該表會被加MDL讀鎖
當對錶作結構變動的時候,加MDL寫鎖
讀鎖之間不互斥,因此能夠多線程多同一張表進行增刪改查。
讀寫鎖、寫鎖之間是互斥的,爲了保證表結構變動的安全性,因此若是要多線程對同一個表加字段等表結構操做,就會變成串行化,須要進行鎖等待。
MDL的寫鎖優先級比MDL讀鎖的優先級,可是能夠設置max_write_lock_count系統變量來改變這種狀況,當寫鎖請求超過這個變量設置的數後,MDL讀鎖的優先級會比MDL寫鎖的優先級高。(默認狀況下,這個數字會很大,因此不用擔憂寫鎖的優先級降低)
MDL的鎖釋放必需要等到事務結束纔會釋放
頁面鎖(page-level locking)
頁級鎖定是 MySQL 中比較獨特的一種鎖定級別,在其餘數據庫管理軟件中也並非太常見。
頁面鎖開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度通常。
頁級鎖定的特色是鎖定顆粒度介於行級鎖定與表級鎖之間,因此獲取鎖定所須要的資源開銷,以及所能提供的併發處理能力也一樣是介於上面兩者之間。另外,頁級鎖定和行級鎖定同樣,會發生死鎖。
在數據庫實現資源鎖定的過程當中,隨着鎖定資源顆粒度的減少,鎖定相同數據量的數據所須要消耗的內存數量是愈來愈多的,實現算法也會愈來愈複雜。
不過,隨着鎖定資源顆粒度的減少,應用程序的訪問請求遇到鎖等待的可能性也會隨之下降,系統總體併發度也隨之提高。
使用頁級鎖定的主要是 BerkeleyDB 存儲引擎。
MySQL 提供全局鎖來對整個數據庫實例加鎖。
FLUSH TABLES WITH READ LOCK
這條語句通常都是用來備份的,當執行這條語句後,數據庫全部打開的表都會被關閉,而且使用全局讀鎖鎖定數據庫的全部表,同時,其餘線程的更新語句(增刪改),數據定義語句(建表,修改表結構)和更新類的事務提交都會被阻塞。
在mysql 8.0 之後,對於備份,mysql能夠直接使用備份鎖。
LOCK INSTANCE FOR BACKUP UNLOCK INSTANCE
這個鎖的做用範圍更廣,這個鎖會阻止文件的建立,重命名,刪除,包括 REPAIR TABLE TRUNCATE TABLE, OPTIMIZE TABLE操做以及帳戶的管理都會被阻塞。固然這些操做對於內存臨時表來講是能夠執行的,爲何內存表不受這些限制呢?由於內存表不須要備份,因此也就不必知足這些條件。
全部的存儲引擎都以本身的方式顯現了鎖機制,服務器層徹底不瞭解存儲引擎中的鎖實現:
MyISAM、MEMORY、CSV存儲引擎採用的是表級鎖(table-level locking)
BDB(Berkeley DB) 存儲引擎採用的是頁面鎖(page-level locking),但也支持表級鎖
InnoDB 存儲引擎既支持行級鎖(row-level locking),也支持表級鎖,但默認狀況下是採用行級鎖。
InnoDB行鎖是經過給索引上的索引項加鎖來實現的,InnoDB這種行鎖實現特色意味着:只有經過索引條件檢索數據,InnoDB才使用行級鎖,不然,InnoDB將使用表鎖!
行級鎖都是基於索引的,若是一條SQL語句用不到索引是不會使用行級鎖的,會使用表級鎖。行級鎖的缺點是:因爲須要請求大量的鎖資源,因此速度慢,內存消耗大。
默認狀況下,表鎖和行鎖都是自動得到的, 不須要額外的命令。
可是在有的狀況下, 用戶須要明確地進行鎖表或者進行事務的控制, 以便確保整個事務的完整性,這樣就須要使用事務控制和鎖定語句來完成。
InnoDB與MyISAM的最大不一樣有兩點:一是支持事務(TRANSACTION);二是採用了行級鎖。
Innodb存儲引擎因爲實現了行級鎖定,雖然在鎖定機制的實現方面所帶來的性能損耗可能比表級鎖定會要更高一些,可是在總體併發處理能力方面要遠遠優於MyISAM的表級鎖定的。當系統併發量較高的時候,Innodb的總體性能和MyISAM相比就會有比較明顯的優點了。
可是,Innodb的行級鎖定一樣也有其脆弱的一面,當咱們使用不當的時候,可能會讓Innodb的總體性能表現不只不能比MyISAM高,甚至可能會更差。
InnoDB 實現瞭如下兩種類型的行鎖:
共享鎖(S-shared):容許一個事務去讀一行,阻止其餘事務得到相同數據集的排他鎖。
排他鎖(X-exclusive):容許得到排他鎖的事務更新數據,阻止其餘事務取得相同數據集的共享讀鎖和排他寫鎖。
爲了支持在不一樣粒度上進行加鎖操做(容許行鎖和表鎖共存,實現多粒度鎖機制),InnoDB 還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖:
意向共享鎖(IS- intent share lock)事務想要得到一張表中某幾行的共享鎖
務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的 IS 鎖。
意向排他鎖(IX -intent exclusive lock)事務想要得到一張表中某幾行的排他鎖
事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的 IX 鎖。
因爲InnoDB存儲引擎支持的是行級別的鎖,所以意向鎖其實不會阻塞除全表掃之外的任何請求。故表級意向鎖與行級鎖的兼容性以下所示
若是一個事務請求的鎖模式與當前的鎖兼容, InnoDB 就將請求的鎖授予該事務; 反之, 若是二者不兼容,該事務就要等待鎖釋放
若將上鎖的對象當作一棵樹,那麼對最下層的對象上鎖,也就是對最細粒度的對象進行上鎖,那麼首先須要對粗粒度的對象上鎖。例上圖,若是須要對頁上的記錄r進行上X鎖,那麼分別須要對數據庫A、表、頁上意向鎖IX,最後對記錄r上X鎖。若其中任何一個部分致使等待,那麼該操做須要等待粗粒度鎖的完成。舉例來講,在對記錄r加X鎖以前,已經有事務對錶1進行了S表鎖,那麼表1上已存在S鎖,以後事務須要對記錄r在表1上加上IX,因爲不兼容,因此該事務須要等待表鎖操做的完成。
innodb的意向鎖主要用戶多粒度的鎖並存的狀況。好比事務A要在一個表上加S鎖,若是表中的一行已被事務B加了X鎖,那麼該鎖的申請也應被阻塞。若是表中的數據不少,逐行檢查鎖標誌的開銷將很大,系統的性能將會受到影響。爲了解決這個問題,能夠在表級上引入新的鎖類型來表示其所屬行的加鎖狀況,這就引出了「意向鎖」的概念。
舉個例子,若是表中記錄1億,事務A把其中有幾條記錄上了行鎖了,這時事務B須要給這個表加表級鎖,若是沒有意向鎖的話,那就要去表中查找這一億條記錄是否上鎖了。若是存在乎向鎖,那麼假如事務A在更新一條記錄以前,先加意向鎖,再加X鎖,事務B先檢查該表上是否存在乎向鎖,存在的意向鎖是否與本身準備加的鎖衝突,若是有衝突,則等待直到事務A釋放,而無須逐條記錄去檢測。事務B更新表時,其實無須知道到底哪一行被鎖了,它只要知道反正有一行被鎖了就好了。
主要做用是處理行鎖和表鎖之間的矛盾,可以顯示「某個事務正在某一行上持有了鎖,或者準備去持有鎖」
行鎖是加在索引上的
Innodb中的索引數據結構是 B+ 樹,數據是有序排列的,從根節點到葉子節點一層層找到對應的數據。
普通索引,也叫作輔助索引,葉子節點存放的是主鍵值。主鍵上的索引叫作彙集索引,表裏的每一條記錄都存放在主鍵的葉子節點上。當經過輔助索引select 查詢數據的時候,會先在輔助索引中找到對應的主鍵值,而後用主鍵值在彙集索引中找到該條記錄。
舉個例子,用name=Alice來查詢的時候,會先找到對應的主鍵值是18 ,而後用18在下面的彙集索引中找到name=Alice的記錄內容是 77 和 Alice。
表中每一行的數據,是組織存放在彙集索引中的,因此叫作索引組織表。
InnoDB 行鎖是經過給索引上的索引項加鎖來實現的,這一點 MySQL 與 Oracle 不一樣,後者是經過在數據塊中對相應數據行加鎖來實現的。InnoDB 這種行鎖實現特色意味着:只有經過索引條件檢索數據,InnoDB 才使用行級鎖,不然,InnoDB 將使用表鎖!
不管是使用主鍵索引、惟一索引或普通索引,InnoDB 都會使用行鎖來對數據加鎖。
只有執行計劃真正使用了索引,才能使用行鎖:即使在條件中使用了索引字段,可是否使用索引來檢索數據是由 MySQL 經過判斷不一樣執行計劃的代價來決定的,若是 MySQL 認爲全表掃描效率更高,好比對一些很小的表,它就不會使用索引,這種狀況下 InnoDB 將使用表鎖,而不是行鎖。所以,在分析鎖衝突時,別忘了檢查 SQL 的執行計劃(能夠經過 explain 檢查 SQL 的執行計劃),以確認是否真正使用了索引。(更多閱讀:MySQL索引總結)
因爲 MySQL 的行鎖是針對索引加的鎖,不是針對記錄加的鎖,因此雖然多個session是訪問不一樣行的記錄, 可是若是是使用相同的索引鍵, 是會出現鎖衝突的(後使用這些索引的session須要等待先使用索引的session釋放鎖後,才能獲取鎖)。 應用設計的時候要注意這一點。
Record Lock(單行記錄)
Gap Lock(間隙鎖,鎖定一個範圍,但不包含鎖定記錄)
Next-Key Lock(Record Lock + Gap Lock,鎖定一個範圍,而且鎖定記錄自己, MySql 防止幻讀,就是使用此鎖實現)
記錄鎖、間隙鎖、臨鍵鎖都是排它鎖
事務加鎖後鎖住的只是表的某一條記錄。
記錄鎖出現條件:精準條件命中,而且命中的條件字段是惟一索引;
例如:update user_info set name=’張三’ where id=1 ,這裏的id是惟一索引。
Record Lock老是會去鎖住索引記錄,若是InnoDB存儲引擎表在創建的時候沒有設置任何一個索引,那麼這時InnoDB存儲引擎會使用隱式的主鍵來進行鎖定。
記錄鎖的做用:加了記錄鎖以後能夠避免數據在查詢的時候被修改的重複讀問題,也避免了在修改的事務未提交前被其餘事務讀取的髒讀問題。
臨鍵鎖是INNODB的行鎖默認算法,它是記錄鎖和間隙鎖的組合,臨鍵鎖會把查詢出來的記錄鎖住,同時也會把該範圍查詢內的全部間隙空間也會鎖住,再之它會把相鄰的下一個區間也會鎖住。
臨鍵鎖出現條件:範圍查詢並命中,查詢命中了索引。
好比下面表的數據執行 select * from user_info where id>1 and id<=13 for update ;
會鎖住ID爲 1,5,10的記錄;同時會鎖住,1至5,5至10,10至15的區間。
臨鍵鎖的做用:結合記錄鎖和間隙鎖的特性,臨鍵鎖避免了在範圍查詢時出現髒讀、重複讀、幻讀問題。加了臨鍵鎖以後,在範圍區間內數據不容許被修改和插入。
Next-Key Lock是結合了Gap Lock和Record Lock的一種鎖定算法,在Next-Key Lock算法下,InnoDB對於行的查詢都是採用這種鎖定算法。
除了Next-Key Locking,還有Previous-Key Locking技術。
當咱們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫作「間隙(GAP)」,InnoDB也會對這個「間隙」加鎖,這種鎖機制就是所謂的間隙鎖。
很顯然,在使用範圍條件檢索並鎖定記錄時,InnoDB這種加鎖機制會阻塞符合條件範圍內鍵值的併發插入,這每每會形成嚴重的鎖等待。所以,在實際應用開發中,尤爲是併發插入比較多的應用,咱們要儘可能優化業務邏輯,儘可能使用相等條件來訪問更新數據,避免使用範圍條件。
防止幻讀,以知足相關隔離級別的要求;
知足恢復和複製的須要:
使用普通索引鎖定;
使用多列惟一索引;
使用惟一索引鎖定多行記錄。
以上狀況,都會產生間隙鎖
推薦閱讀《MySQL的鎖機制 - 記錄鎖、間隙鎖、臨鍵鎖》
MySQL 經過 BINLOG 錄入執行成功的 INSERT、UPDATE、DELETE 等更新數據的 SQL 語句,並由此實現 MySQL 數據庫的恢復和主從複製。MySQL 的恢復機制(複製其實就是在 Slave Mysql 不斷作基於 BINLOG 的恢復)有如下特色:
一是 MySQL 的恢復是 SQL 語句級的,也就是從新執行 BINLOG 中的 SQL 語句。
二是 MySQL 的 Binlog 是按照事務提交的前後順序記錄的, 恢復也是按這個順序進行的。
因而可知,MySQL 的恢復機制要求:在一個事務未提交前,其餘併發事務不能插入知足其鎖定條件的任何記錄,也就是不容許出現幻讀。
這張圖裏出現了三種鎖
記錄鎖:單行記錄上的鎖
間隙鎖:鎖定記錄之間的範圍,但不包含記錄自己。
Next Key Lock: 記錄鎖+ 間隙鎖,鎖定一個範圍,包含記錄自己。
不是全部索引都會加上Next-key Lock的,在查詢的列是惟一索引(包含主鍵索引)的狀況下,Next-key Lock會降級爲Record Lock。
CREATE TABLE z (a INT,b INT,PRIMARY KEY(a),KEY(b));// a是主鍵索引,b是普通索引
INSERT INTO z select1,1;
INSERT INTO z select3,1;
INSERT INTO z select5,3;
INSERT INTO z select7,6;
INSERT INTO z select10,8;複製代碼
這時候在會話A中執行 SELECT*FROM z WHERE b=3FOR UPDATE ,索引鎖定以下:
這時候會話B執行的語句落在鎖定範圍內的都會進行waiting
SELECT * FROM z WHERE a =5 LOCK IN SHARE MODE;
INSERT INTO z SELECT 4,2;
INSERT INTO z SELECT 6,5;複製代碼
用戶能夠經過如下兩種方式來顯示的關閉Gap Lock:
將事務的隔離級別設爲 READ COMMITED。
將參數innodblocksunsafeforbinlog設置爲1。
從上面的例子能夠看出來,Gap Lock的做用是爲了阻止多個事務將記錄插入到同一個範圍內,設計它的目的是用來解決Phontom Problem(幻讀問題)。在MySQL默認的隔離級別(Repeatable Read)下,InnoDB就是使用它來解決幻讀問題。
意向鎖是 InnoDB 自動加的, 不需用戶干預。
對於 UPDATE、 DELETE 和 INSERT 語句, InnoDB 會自動給涉及數據集加排他鎖(X);
對於普通 SELECT 語句,InnoDB 不會加任何鎖;
事務能夠經過如下語句顯式給記錄集加共享鎖或排他鎖:
共享鎖(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。 其餘 session 仍然能夠查詢記錄,並也能夠對該記錄加 share mode 的共享鎖。可是若是當前事務須要對該記錄進行更新操做,則頗有可能形成死鎖。
排他鎖(X):SELECT * FROM table_name WHERE ... FOR UPDATE。其餘 session 能夠查詢該記錄,可是不能對該記錄加共享鎖或排他鎖,而是等待得到鎖
InnoDB在事務執行過程當中,使用兩階段鎖協議:
隨時均可以執行鎖定,InnoDB會根據隔離級別在須要的時候自動加鎖;
鎖只有在執行commit或者rollback的時候纔會釋放,而且全部的鎖都是在同一時刻被釋放。
select ... lock in share mode //共享鎖
select ... for update //排他鎖複製代碼
在執行這個 select 查詢語句的時候,會將對應的索引訪問條目進行上排他鎖(X 鎖),也就是說這個語句對應的鎖就至關於update帶來的效果。
select *** for update 的使用場景:爲了讓本身查到的數據確保是最新數據,而且查到後的數據只容許本身來修改的時候,須要用到 for update 子句。
經過鎖住彙集索引中的節點來鎖住這條記錄(鎖住id=10的索引,即鎖住了這條記錄)。
這裏的name上加了惟一索引,惟一索引本質上是輔助索引,加了惟一約束。因此會先在輔助索引上找到name爲d的索引記錄,在輔助索引中加鎖,而後查找彙集索引,鎖住對應索引記錄。
試想一下,若是有併發的另一個SQL,是直接經過主鍵索引id=30來更新,會先在彙集索引中請求加鎖。若是隻在輔助索引中加鎖的話,兩個併發SQL之間是互相感知不到的。
in share mode 子句的做用就是將查找到的數據加上一個 share 鎖,這個就是表示其餘的事務只能對這些數據進行簡單的select 操做,並不可以進行 DML 操做。
select *** lock in share mode 使用場景:爲了確保本身查到的數據沒有被其餘的事務正在修改,也就是說確保查到的數據是最新的數據,而且不容許其餘人來修改數據。可是本身不必定可以修改數據,由於有可能其餘的事務也對這些數據 使用了 in share mode 的方式上了 S 鎖。
前一個上的是排他鎖(X 鎖),一旦一個事務獲取了這個鎖,其餘的事務是無法在這些數據上執行 for update ;後一個是共享鎖,多個事務能夠同時的對相同數據執行 lock in share mode。
select for update 語句,至關於一個 update 語句。在業務繁忙的狀況下,若是事務沒有及時的commit或者rollback 可能會形成其餘事務長時間的等待,從而影響數據庫的併發使用效率。
select lock in share mode 語句是一個給查找的數據上一個共享鎖(S 鎖)的功能,它容許其餘的事務也對該數據上S鎖,可是不可以容許對該數據進行修改。若是不及時的commit 或者rollback 也可能會形成大量的事務等待。
默認是 MVCC 機制(「一致性非鎖定讀-consistent nonlocking read」)保證 RR 級別的隔離正確性,是不上鎖的。
能夠選擇手動上鎖:select xxxx for update (排他鎖); select xxxx lock in share mode(共享鎖),稱之爲「一致性鎖定讀」。
使用鎖以後,就能在 RR 級別下,避免幻讀。固然,默認的 MVCC 讀,也能避免幻讀。
既然 RR 可以防止幻讀,那麼,SERIALIZABLE 有啥用呢?防止丟失更新。
這個時候,咱們必須使用 SERIALIZABLE 級別進行串行讀取。
最後,行鎖的實現原理就是鎖住彙集索引,若是你查詢的時候,沒有正確地擊中索引,MySql 優化器將會拋棄行鎖,使用表鎖。
鎖和多版本數據(MVCC)是 InnoDB 實現一致性讀和 ISO/ANSI SQL92 隔離級別的手段。
所以,在不一樣的隔離級別下,InnoDB 處理 SQL 時採用的一致性讀策略和須要的鎖是不一樣的:
對於許多 SQL,隔離級別越高,InnoDB 給記錄集加的鎖就越嚴格(尤爲是使用範圍條件的時候),產生鎖衝突的可能性也就越高,從而對併發性事務處理性能的 影響也就越大。
所以, 咱們在應用中, 應該儘可能使用較低的隔離級別, 以減小鎖爭用的機率。實際上,經過優化事務邏輯,大部分應用使用 Read Commited 隔離級別就足夠了。對於一些確實須要更高隔離級別的事務, 能夠經過在程序中執行 SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ 或 SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE 動態改變隔離級別的方式知足需求。
合理利用 InnoDB 的行級鎖定,作到揚長避短
儘量讓全部的數據檢索都經過索引來完成,從而避免 InnoDB 由於沒法經過索引鍵加鎖而升級爲表級鎖定。
合理設計索引,讓 InnoDB 在索引鍵上面加鎖的時候儘量準確,儘量的縮小鎖定範圍,避免形成沒必要要的鎖定而影響其餘 Query 的執行。
儘量減小基於範圍的數據檢索過濾條件,避免由於間隙鎖帶來的負面影響而鎖定了不應鎖定的記錄。
儘可能控制事務的大小,減小鎖定的資源量和鎖定時間長度。
在業務環境容許的狀況下,儘可能使用較低級別的事務隔離,以減小 MySQL 由於實現事務隔離級別所帶來的附加成本。
對於 InnoDB 表,在絕大部分狀況下都應該使用行級鎖,由於事務和行鎖每每是咱們之因此選擇 InnoDB 表的理由。
事務須要更新大部分或所有數據,表又比較大,若是使用默認的行鎖,不只這個事務執行效率低,並且可能形成其餘事務長時間鎖等待和鎖衝突,這種狀況下能夠考慮使用表鎖來提升該事務的執行速度。
事務涉及多個表,比較複雜,極可能引發死鎖,形成大量事務回滾。這種狀況也能夠考慮一次性鎖定事務涉及的表,從而避免死鎖、減小數據庫因事務回滾帶來的開銷。
在 InnoDB 下,使用表鎖要注意如下兩點:
使用 LOCK TABLES 雖然能夠給 InnoDB 加表級鎖,但必須說明的是,表鎖不是由 InnoDB 存儲引擎層管理的,而是由其上一層──MySQL Server 負責的。僅當 autocommit=0(不自動提交,默認是自動提交的)、InnoDB_table_locks=1(默認設置)時,InnoDB 層才能知道 MySQL 加的表鎖,MySQL Server 也才能感知 InnoDB 加的行鎖。這種狀況下,InnoDB 才能自動識別涉及表級鎖的死鎖,不然,InnoDB 將沒法自動檢測並處理這種死鎖。
在用 LOCK TABLES 對 InnoDB 表加鎖時要注意,要將 AUTOCOMMIT 設爲 0,不然 MySQL 不會給表加鎖。事務結束前,不要用 UNLOCK TABLES 釋放表鎖,由於 UNLOCK TABLES 會隱含地提交事務。COMMIT 或 ROLLBACK 並不能釋放用 LOCK TABLES 加的表級鎖,必須用 UNLOCK TABLES 釋放表鎖。
正確的方式見以下語句,例如,若是須要寫表 t1 並從表 t 讀,能夠按以下作:
SET AUTOCOMMIT=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
[do something with tables t1 and t2 here];
COMMIT;
UNLOCK TABLES;複製代碼
表共享讀鎖 (Table Read Lock):不會阻塞其餘用戶對同一表的讀請求,但會阻塞對同一表的寫請求;
表獨佔寫鎖 (Table Write Lock):會阻塞其餘用戶對同一表的讀和寫操做;
MyISAM 表的讀操做與寫操做之間,以及寫操做之間是串行的。當一個線程得到對一個表的寫鎖後, 只有持有鎖的線程能夠對錶進行更新操做。 其餘線程的讀、 寫操做都會等待,直到鎖被釋放爲止。
默認狀況下,寫鎖比讀鎖具備更高的優先級:當一個鎖釋放時,這個鎖會優先給寫鎖隊列中等候的獲取鎖請求,而後再給讀鎖隊列中等候的獲取鎖請求。 (This ensures that updates to a table are not 「starved」 even when there is heavy SELECT activity for the table. However, if there are many updates for a table, SELECT statements wait until there are no more updates.)。
這也正是 MyISAM 表不太適合於有大量更新操做和查詢操做應用的緣由,由於,大量的更新操做會形成查詢操做很難得到讀鎖,從而可能永遠阻塞。同時,一些須要長時間運行的查詢操做,也會使寫線程「餓死」 ,應用中應儘可能避免出現長時間運行的查詢操做(在可能的狀況下能夠經過使用中間表等措施對SQL語句作必定的「分解」 ,使每一步查詢都能在較短期完成,從而減小鎖衝突。若是複雜查詢不可避免,應儘可能安排在數據庫空閒時段執行,好比一些按期統計能夠安排在夜間執行)。
能夠設置改變讀鎖和寫鎖的優先級:
經過指定啓動參數low-priority-updates,使MyISAM引擎默認給予讀請求以優先的權利。
經過執行命令SET LOW_PRIORITY_UPDATES=1,使該鏈接發出的更新請求優先級下降。
經過指定INSERT、UPDATE、DELETE語句的LOW_PRIORITY屬性,下降該語句的優先級。
給系統參數max_write_lock_count設置一個合適的值,當一個表的讀鎖達到這個值後,MySQL就暫時將寫請求的優先級下降,給讀進程必定得到鎖的機會。
在執行查詢語句(SELECT)前,會自動給涉及的表加讀鎖
在執行更新操做(UPDATE、DELETE、INSERT 等)前,會自動給涉及的表加寫鎖
這個過程並不須要用戶干預,所以,用戶通常不須要直接用 LOCK TABLE 命令給 MyISAM 表顯式加鎖。
在自動加鎖的狀況下,MyISAM 老是一次得到 SQL 語句所須要的所有鎖,這也正是 MyISAM 表不會出現死鎖(Deadlock Free)的緣由。
MyISAM存儲引擎支持併發插入,以減小給定表的讀和寫操做之間的爭用:
若是MyISAM表在數據文件中間沒有空閒塊,則行始終插入數據文件的末尾。 在這種狀況下,你能夠自由混合併發使用MyISAM表的INSERT和SELECT語句而不須要加鎖——你能夠在其餘線程進行讀操做的時候,同時將行插入到MyISAM表中。 文件中間的空閒塊多是從表格中間刪除或更新的行而產生的。 若是文件中間有空閒快,則併發插入會被禁用,可是當全部空閒塊都填充有新數據時,它又會自動從新啓用。 要控制此行爲,可使用MySQL的concurrent_insert系統變量。
若是你使用LOCK TABLES顯式獲取表鎖,則能夠請求READ LOCAL鎖而不是READ鎖,以便在鎖定表時,其餘會話可使用併發插入。
當concurrent_insert設置爲0時,不容許併發插入。
當concurrent_insert設置爲1時,若是MyISAM表中沒有空洞(即表的中間沒有被刪除的行),MyISAM容許在一個線程讀表的同時,另外一個線程從表尾插入記錄。這也是MySQL的默認設置。
當concurrent_insert設置爲2時,不管MyISAM表中有沒有空洞,都容許在表尾併發插入記錄。
顯示鎖sql語句
共享讀鎖:lock table tableName read
獨佔寫鎖:lock table tableName write
同時加多鎖:lock table t1 write,t2 read
批量解鎖:unlock tables
惟一的辦法就是讓咱們的 Query 執行時間儘量的短
儘可能減小大的複雜 Query,將複雜 Query 分拆成幾個小的 Query 分佈進行。
儘量的創建足夠高效的索引,讓數據檢索更迅速。
儘可能讓 MyISAM 存儲引擎的表只存放必要的信息,控制字段類型。
利用合適的機會優化 MyISAM 表數據文件。
MyISAM 存儲引擎有一個控制是否打開 Concurrent Insert 功能的參數選項:
concurrent_insert=2,不管 MyISAM 表中有沒有空洞,都容許在表尾併發插入記錄。
concurrent_insert=1,若是 MyISAM 表中沒有空洞(即表的中間沒有被刪除的行),MyISAM 容許在一個進程讀表的同時,另外一個進程從表尾插入記錄。這也是 MySQL 的默認設置。
concurrent_insert=0,不容許併發插入
能夠利用 MyISAM 存儲引擎的併發插入特性,來解決應用中對同一表查詢和插入的鎖爭用。
例如,將 concurrent_insert 系統變量設爲 2,老是容許併發插入;同時,經過按期在系統空閒時段執行 OPTIMIZE TABLE 語句來整理空間碎片,收回因刪除記錄而產生的中間空洞。
經過執行命令 SET LOW_PRIORITY_UPDATES=1,使該鏈接讀比寫的優先級高,若是咱們的系統是一個以讀爲主,能夠設置此參數,若是以寫爲主,則不用設置。
經過指定 INSERT、UPDATE、DELETE 語句的 LOW_PRIORITY 屬性,下降該語句的優先級。
MySQL 也提供了一種折中的辦法來調節讀寫衝突,即給系統參數 max_write_lock_count 設置一個合適的值,當一個表的讀鎖達到這個值後,MySQL 就暫時將寫請求的優先級下降,給讀進程必定得到鎖的機會。
須要長時間運行的查詢操做,也會使寫進程「餓死」,儘可能避免出現長時間運行的查詢操做,不要總想用一條 SELECT 語句來解決問題,由於這種看似巧妙的 SQL 語句,每每比較複雜,執行時間較長。
多表級聯。事務涉及多個表,比較複雜的關聯查詢,極可能引發死鎖,形成大量事務回滾,這種狀況若能一次性鎖定事務涉及的表,從而能夠避免死鎖、減小數據庫因事務回滾帶來的開銷。
死鎖產生:
死鎖是指兩個或多個事務在同一資源上相互佔用,並請求鎖定對方佔用的資源,從而致使惡性循環。
當事務試圖以不一樣的順序鎖定資源時,就可能產生死鎖。多個事務同時鎖定同一個資源時也可能會產生死鎖。
鎖的行爲和順序和存儲引擎相關。以一樣的順序執行語句,有些存儲引擎會產生死鎖有些不會——死鎖有雙重緣由:真正的數據衝突;存儲引擎的實現方式。
檢測死鎖:數據庫系統實現了各類死鎖檢測和死鎖超時的機制。InnoDB存儲引擎能檢測到死鎖的循環依賴並當即返回一個錯誤。
死鎖恢復:死鎖發生之後,只有部分或徹底回滾其中一個事務,才能打破死鎖,InnoDB目前處理死鎖的方法是,將持有最少行級排他鎖的事務進行回滾。因此事務型應用程序在設計時必須考慮如何處理死鎖,多數狀況下只須要從新執行因死鎖回滾的事務便可。
外部鎖的死鎖檢測:發生死鎖後,InnoDB 通常都能自動檢測到,並使一個事務釋放鎖並回退,另外一個事務得到鎖,繼續完成事務。但在涉及外部鎖,或涉及表鎖的狀況下,InnoDB 並不能徹底自動檢測到死鎖, 這須要經過設置鎖等待超時參數 innodb_lock_wait_timeout 來解決
死鎖影響性能:死鎖會影響性能而不是會產生嚴重錯誤,由於InnoDB會自動檢測死鎖情況並回滾其中一個受影響的事務。在高併發系統上,當許多線程等待同一個鎖時,死鎖檢測可能致使速度變慢。 有時當發生死鎖時,禁用死鎖檢測(使用innodb_deadlock_detect配置選項)可能會更有效,這時能夠依賴innodb_lock_wait_timeout設置進行事務回滾。
在自動加鎖的狀況下,MyISAM 表不會出現死鎖(MyISAM 老是一次得到 SQL 語句所須要的所有鎖)。
爲了在單個InnoDB表上執行多個併發寫入操做時避免死鎖,能夠在事務開始時經過爲預期要修改的每一個元祖(行)使用SELECT ... FOR UPDATE語句來獲取必要的鎖,即便這些行的更改語句是在以後才執行的。
在事務中,若是要更新記錄,應該直接申請足夠級別的鎖,即排他鎖,而不該先申請共享鎖、更新時再申請排他鎖,由於這時候當用戶再申請排他鎖時,其餘事務可能又已經得到了相同記錄的共享鎖,從而形成鎖衝突,甚至死鎖
若是事務須要修改或鎖定多個表,則應在每一個事務中以相同的順序使用加鎖語句。 在應用中,若是不一樣的程序會併發存取多個表,應儘可能約定以相同的順序來訪問表,這樣能夠大大下降產生死鎖的機會
經過SELECT ... LOCK IN SHARE MODE獲取行的讀鎖後,若是當前事務再須要對該記錄進行更新操做,則頗有可能形成死鎖。
改變事務隔離級別,如下降隔離級別(若是業務容許,將隔離級別調低也是較好的選擇,好比將隔離級別從RR調整爲RC,能夠避免掉不少由於gap鎖形成的死鎖)
爲表添加合理的索引。能夠看到若是不走索引將會爲表的每一行記錄添加上鎖,死鎖的機率大大增大。
若是出現死鎖,能夠用 SHOW INNODB STATUS 命令來肯定最後一個死鎖產生的緣由。返回結果中包括死鎖相關事務的詳細信息,如引起死鎖的 SQL 語句,事務已經得到的鎖,正在等待什麼鎖,以及被回滾的事務等。據此能夠分析死鎖產生的緣由和改進措施。
下面兩條簡單的SQL,他們加什麼鎖?
select * from t1 where id = 10
delete from t1 where id = 10
若是要分析加鎖狀況,必須還要知道如下的一些前提,前提不一樣,加鎖處理的方式也不一樣
id列是否是主鍵?
當前系統的隔離級別是什麼?
id列若是不是主鍵,那麼id列上有索引嗎?
id列上若是有二級索引,那麼這個索引是惟一索引嗎?
兩個SQL的執行計劃是什麼?索引掃描?全表掃描?
根據上述狀況,有如下幾種組合
id列是主鍵,RC隔離級別
id列是二級惟一索引,RC隔離級別
id列是二級非惟一索引,RC隔離級別
id列上沒有索引,RC隔離級別
id列是主鍵,RR隔離級別
id列是二級惟一索引,RR隔離級別
id列是二級非惟一索引,RR隔離級別
id列上沒有索引,RR隔離級別
Serializable隔離級別
排列組合尚未列舉徹底,可是看起來,已經不少了。真的有必要這麼複雜嗎?事實上,要分析加鎖,就是須要這麼複雜。可是從另外一個角度來講,只要你選定了一種組合,SQL須要加哪些鎖,其實也就肯定了。接下來挑幾個比較經典的組合
這個組合,是最簡單,最容易分析的組合。id是主鍵,Read Committed隔離級別,給定SQL:delete from t1 where id = 10; 只須要將主鍵上,id = 10的記錄加上X鎖便可。以下圖1:
結論:id是主鍵時,此SQL只須要在id=10這條記錄上加X鎖便可。
這個組合,id不是主鍵,而是一個Unique的二級索引鍵值。那麼在RC隔離級別下,delete from t1 where id = 10; 須要加什麼鎖呢?見下圖2:
id是unique索引,而主鍵是name列。此時,加鎖的狀況因爲組合一有所不一樣。因爲id是unique索引,所以delete語句會選擇走id列的索引進行where條件的過濾,在找到id=10的記錄後,首先會將unique索引上的id=10索引記錄加上X鎖,同時,會根據讀取到的name列,回主鍵索引(聚簇索引),而後將聚簇索引上的name = ‘d’ 對應的主鍵索引項加X鎖。
結論:若id列是unique列,其上有unique索引。那麼SQL須要加兩個X鎖,一個對應於id unique索引上的id = 10的記錄,另外一把鎖對應於聚簇索引上的[name='d',id=10]的記錄、
相對於組合1、二,組合三又發生了變化,隔離級別仍舊是RC不變,可是id列上的約束又下降了,id列再也不惟一,只有一個普通的索引。假設delete from t1 where id = 10; 語句,仍舊選擇id列上的索引進行過濾where條件,那麼此時會持有哪些鎖?一樣見下圖3:根據此圖,能夠看到,首先,id列索引上,知足id = 10查詢條件的記錄,均已加鎖。同時,這些記錄對應的主鍵索引上的記錄也都加上了鎖。與組合二惟一的區別在於,組合二最多隻有一個知足等值查詢的記錄,而組合三會將全部知足查詢條件的記錄都加鎖。
結論:若id列上有非惟一索引,那麼對應的全部知足SQL查詢條件的記錄,都會被加鎖。同時,這些記錄在主鍵索引上的記錄,也會被加鎖。
還記得前面提到的MySQL的四種隔離級別的區別嗎?RC隔離級別容許幻讀,而RR隔離級別,不容許存在幻讀。可是在組合5、組合六中,加鎖行爲又是與RC下的加鎖行爲徹底一致。那麼RR隔離級別下,
組合七,Repeatable Read隔離級別,id上有一個非惟一索引,執行delete from t1 where id = 10; 假設選擇id列上的索引進行條件過濾,最後的加鎖行爲,是怎麼樣的呢?一樣看下圖1:
結論:Repeatable Read隔離級別下,id列上有一個非惟一索引,對應SQL:delete from t1 where id = 10; 首先,經過id索引定位到第一條知足查詢條件的記錄,加記錄上的X鎖,加GAP上的GAP鎖,而後加主鍵聚簇索引上的記錄X鎖,而後返回;而後讀取下一條,重複進行。直至進行到第一條不知足條件的記錄[11,f],此時,不須要加記錄X鎖,可是仍舊須要加GAP鎖,最後返回結束。
何時會取得gap lock或nextkey lock 這和隔離級別有關,只在REPEATABLE READ或以上的隔離級別下的特定操做纔會取得gap lock或nextkey lock。
相對於前面三個組合,這是一個比較特殊的狀況。id列上沒有索引,where id = 10;這個過濾條件,無法經過索引進行過濾,那麼只能走全表掃描作過濾。對應於這個組合,SQL會加什麼鎖?或者是換句話說,全表掃描時,會加什麼鎖?這個答案也有不少:有人說會在表上加X鎖;有人說會將聚簇索引上,選擇出來的id = 10;的記錄加上X鎖。那麼實際狀況呢?請看下圖2:
因爲id列上沒有索引,所以只能走聚簇索引,進行所有掃描。從圖中能夠看到,知足刪除條件的記錄有兩條,可是,聚簇索引上全部的記錄,都被加上了X鎖。不管記錄是否知足條件,所有被加上X鎖。既不是加表鎖,也不是在知足條件的記錄上加行鎖。
有人可能會問?爲何不是隻在知足條件的記錄上加鎖呢?這是因爲MySQL的實現決定的。若是一個條件沒法經過索引快速過濾,那麼存儲引擎層面就會將全部記錄加鎖後返回,而後由MySQL Server層進行過濾。所以也就把全部的記錄,都鎖上了。
結論:若id列上沒有索引,SQL會走聚簇索引的全掃描進行過濾,因爲過濾是由MySQL Server層面進行的。所以每條記錄,不管是否知足條件,都會被加上X鎖。可是,爲了效率考量,MySQL作了優化,對於不知足條件的記錄,會在判斷後放鎖,最終持有的,是知足條件的記錄上的鎖,可是不知足條件的記錄上的加鎖/放鎖動做不會省略。同時,優化也違背了2PL的約束。
組合八,Repeatable Read隔離級別下的最後一種狀況,id列上沒有索引。此時SQL:delete from t1 where id = 10; 沒有其餘的路徑能夠選擇,只能進行全表掃描。最終的加鎖狀況,圖3所示:
結論:在Repeatable Read隔離級別下,若是進行全表掃描的當前讀,那麼會鎖上表中的全部記錄,同時會鎖上聚簇索引內的全部GAP,杜絕全部的併發 更新/刪除/插入 操做。固然,也能夠經過觸發semi-consistent read,來緩解加鎖開銷與併發影響,可是semi-consistent read自己也會帶來其餘問題,不建議使用。
上面的四個組合,都是在Read Committed隔離級別下的加鎖行爲,接下來的四個組合,是在Repeatable Read隔離級別下的加鎖行爲。
組合五,id列是主鍵列,Repeatable Read隔離級別,針對delete from t1 where id = 10; 這條SQL,加鎖與組合一:[id主鍵,Read Committed]一致。
與組合五相似,組合六的加鎖,與組合二:[id惟一索引,Read Committed]一致。兩個X鎖,id惟一索引知足條件的記錄上一個,對應的聚簇索引上的記錄一個。
針對前面提到的簡單的SQL,最後一個狀況:Serializable隔離級別。對於SQL2:delete from t1 where id = 10; 來講,Serializable隔離級別與Repeatable Read隔離級別徹底一致,所以不作介紹。
Serializable隔離級別,影響的是SQL1:select * from t1 where id = 10; 這條SQL,在RC,RR隔離級別下,都是快照讀,不加鎖。可是在Serializable隔離級別,SQL1會加讀鎖,也就是說快照讀不復存在,MVCC併發控制降級爲Lock-Based CC。
結論:在MySQL/InnoDB中,所謂的讀不加鎖,並不適用於全部的狀況,而是隔離級別相關的。Serializable隔離級別,讀不加鎖就再也不成立,全部的讀操做,都是當前讀。
這種狀況很好理解,事務A和事務B操做兩張表,但出現循環等待鎖狀況。
這種狀況比較常見,以前遇到兩個job在執行數據批量更新時,jobA處理的的id列表爲[1,2,3,4],而job處理的id列表爲[8,9,10,4,2],這樣就形成了死鎖。
這種狀況比較隱晦,事務A在執行時,除了在二級索引加鎖外,還會在聚簇索引上加鎖,在聚簇索引上加鎖的順序是[1,4,2,3,5],而事務B執行時,只在聚簇索引上加鎖,加鎖順序是[1,2,3,4,5],這樣就形成了死鎖的可能性。
innodb在RR級別下,以下的狀況也會產生死鎖,比較隱晦。不清楚的同窗能夠自行根據上節的gap鎖原理分析下。
結論:在MySQL/InnoDB中,所謂的讀不加鎖,並不適用於全部的狀況,而是隔離級別相關的。Serializable隔離級別,讀不加鎖就再也不成立,全部的讀操做,都是當前讀。
儘可能使用較低的隔離級別;
精心設計索引, 並儘可能使用索引訪問數據, 使加鎖更精確, 從而減小鎖衝突的機會
選擇合理的事務大小,小事務發生鎖衝突的概率也更小
給記錄集顯示加鎖時,最好一次性請求足夠級別的鎖。好比要修改數據的話,最好直接申請排他鎖,而不是先申請共享鎖,修改時再請求排他鎖,這樣容易產生死鎖
不一樣的程序訪問一組表時,應儘可能約定以相同的順序訪問各表,對一個表而言,儘量以固定的順序存取表中的行。這樣能夠大大減小死鎖的機會
儘可能用相等條件訪問數據,這樣能夠避免間隙鎖對併發插入的影響
不要申請超過實際須要的鎖級別
除非必須,查詢時不要顯示加鎖。 MySQL的MVCC能夠實現事務中的查詢不用加鎖,優化事務性能;MVCC只在COMMITTED READ(讀提交)和REPEATABLE READ(可重複讀)兩種隔離級別下工做
對於一些特定的事務,可使用表鎖來提升處理速度或減小死鎖的可能
相關連接:
MySQL鎖總結 zhuanlan.zhihu.com/p/29150809
MySQL鎖機制——你想知道的都在這了! zhuanlan.zhihu.com/p/75673270
詳解mysql的各類鎖(表鎖、行鎖、共享鎖、意向共享鎖、記錄鎖、間隙鎖、臨鍵鎖) zhuanlan.zhihu.com/p/52312376
對於MySQL你必需要了解的鎖知識 zhuanlan.zhihu.com/p/62525459
mysql鎖機制總結,以及優化建議 zhuanlan.zhihu.com/p/70889229
深刻理解MySQL――鎖、事務與併發控制 這纔是正確的! zhuanlan.zhihu.com/p/36060546
深刻理解MySQL鎖 zhuanlan.zhihu.com/p/8355298
MySQL(Innodb)索引的原理 zhuanlan.zhihu.com/p/62018452
數據庫兩大神器【索引和鎖】 zhuanlan.zhihu.com/p/40396971
深刻理解MySQL鎖 zhuanlan.zhihu.com/p/83552985
原文:再談mysql鎖機制及原理-鎖的詮釋 - mysql - 周陸軍的我的網站 修改更新只在原文,文有不妥之處,請留言告知。