爲了保證數據的一致性和完整性,數據庫系統廣泛存在鎖定機制,鎖定機制的優劣直接應想到一個數據庫系統的併發處理能力和性能,因此鎖定機制的實現也就成爲了各類數據庫的核心技術之一,Mysql的鎖定機制有三種,分別是行級鎖定,表級鎖定,頁級鎖定,不一樣的存儲引擎有着不一樣的鎖定機制,要學會適時而用html
顆粒度最小,發生資源爭用的機率也最小,併發能力高,但每次獲取鎖和釋放鎖須要作的事情也更多,所消耗的系統資源例如內存也最大,而且容易發生死鎖mysql
顆粒度最大,所需系統資源也最小,因爲鎖定整張表,能夠避免死鎖,可是因爲資源爭用機率最高,因此併發能力也最差sql
顆粒度和併發能力以及消耗的系統資源介於上面二者之間,但也會發生死鎖數據庫
注:在MySQL數據庫中,使用表級鎖定的主要是MyISAM,Memory,CSV等一些非事務性存儲引擎,而使用行級鎖定的主要是Innodb存儲引擎和NDBCluster存儲引擎,頁級鎖定主要是BerkeleyDB存儲引擎的鎖定方式併發
Mysql容許存儲引擎本身經過接口傳入的鎖定類型而自行決定(針對讀和寫)該怎樣鎖定數據分佈式
MySQL的表級鎖定主要分爲兩種類型,一種是讀鎖定,另外一種是寫鎖定。在MySQL中,主要經過四個隊列來維護這兩種鎖定:兩個存放當前正在鎖定中的讀和寫鎖定信息,另外兩個存放等待中的讀寫鎖定信息,以下:性能
Current read-lock queue (lock->read) Pending read-lock queue (lock->read_wait) Current write-lock queue (lock->write) Pending write-lock queue (lock->write_wait)
當前持有讀鎖的全部線程的相關信息都可以在Currentread-lockqueue中找到,隊列中的信息按照獲取到鎖的時間依序存放。而正在等待鎖定資源的信息則存放在Pendingread-lockqueue裏面,另外兩個存放寫鎖信息的隊列也按照上面相同規則來存放信息優化
雖然對於咱們這些使用者來講MySQL展示出來的鎖定(表鎖定)只有讀鎖定和寫鎖定這兩種類型,可是在MySQL內部實現中卻有多達11種鎖定類型,由系統中一個枚舉量(thr_lock_type)定義,各值描述以下:spa
鎖定類型線程 |
說明 |
IGNORE |
當發生鎖請求的時候內部交互使用,在鎖定結構和隊列中並不會有任何信息存儲 |
UNLOCK |
釋放鎖定請求的交互用所類型 |
READ |
普通讀鎖定 |
WRITE |
普通寫鎖定 |
READ_WITH_SHARED_LOCKS |
在Innodb中使用到,由以下方式產生如:SELECT...LOCKINSHAREMODE |
READ_HIGH_PRIORITY |
高優先級讀鎖定 |
READ_NO_INSERT |
不容許ConcurentInsert的鎖定 |
WRITE_ALLOW_WRITE |
這個類型實際上就是當由存儲引擎自行處理鎖定的時候,mysqld容許其餘的線程再獲取讀或者寫鎖定,由於即便資源衝突,存儲引擎本身也會知道怎麼來處理 |
WRITE_ALLOW_READ |
這種鎖定發生在對錶作DDL(ALTERTABLE...)的時候,MySQL能夠容許其餘線程獲取讀鎖定,由於MySQL是經過重建整個表而後再RENAME而實現的該功能,所在整個過程原表仍然能夠提供讀服務 |
WRITE_CONCURRENT_INSERT |
正在進行ConcurentInsert時候所使用的鎖定方式,該鎖定進行的時候,除了READ_NO_INSERT以外的其餘任何讀鎖定請求都不會被阻塞 |
WRITE_DELAYED |
在使用INSERTDELAYED時候的鎖定類型 |
WRITE_LOW_PRIORITY |
顯示聲明的低級別鎖定方式,經過設置LOW_PRIORITY_UPDAT=1而產生 |
WRITE_ONLY |
當在操做過程當中某個鎖定異常中斷以後系統內部須要進行CLOSETABLE操做,在這個過程當中出現的鎖定類型就是WRITE_ONLY |
一個新的客戶端請求在申請獲取讀鎖定資源的時候,須要知足兩個條件:
若是知足了兩個條件以後,該請求會被當即經過,並將相關的信息存入Currentread-lockqueue中,而若是上面兩個條件中任何一個沒有知足,都會被迫進入等待隊列Pendingread-lockqueue中等待資源的釋放。
當客戶端請求寫鎖定的時候,MySQL首先檢查在Currentwrite-lockqueue是否已經有鎖定相同資源的信息存在,若是Currentwrite-lockqueue沒有,則再檢查Pendingwrite-lockqueue,若是在Pendingwrite-lockqueue中找到了,本身也須要進入等待隊列並暫停自身線程等待鎖定資源。反之,若是Pendingwrite-lockqueue爲空,則再檢測Currentread-lockqueue,若是有鎖定存在,則一樣須要進入Pendingwrite-lockqueue等待。固然,也可能遇到如下這兩種特殊狀況:
當遇到這兩種特殊狀況的時候,寫鎖定會當即得到而進入Current write-lock queue 中
若是剛開始第一次檢測就Currentwrite-lockqueue中已經存在了鎖定相同資源的寫鎖定存在,那麼就只能進入等待隊列等待相應資源鎖定的釋放了
讀請求和寫等待隊列中的寫鎖請求的優先級規則主要爲如下規則決定:
寫鎖定出如今Currentwrite-lockqueue以後,會阻塞除了如下狀況下的全部其餘鎖定的請求:
行級鎖定不是MySQL本身實現的鎖定方式,而是由其餘存儲引擎本身所實現的,如廣爲你們所知的Innodb存儲引擎,以及MySQL的分佈式存儲引擎NDBCluster等都是實現了行級鎖定
Innodb 鎖定模式及實現機制
Innodb是目前事務型存儲引擎中使用最爲普遍的存儲引擎,Innodb的行級鎖定一樣分爲兩種類型,共享鎖和排他鎖,而在鎖定機制的實現過程當中爲了讓行級鎖定和表級鎖定共存,Innodb也一樣使用了意向鎖(表級鎖定)的概念,也就有了意向共享鎖和意向排他鎖這兩種
當一個事務須要給本身須要的某個資源加鎖的時候,若是遇到一個共享鎖正鎖定着本身須要的資源的時候,本身能夠再加一個共享鎖,不過不能加排他鎖。可是,若是遇到本身須要鎖定的資源已經被一個排他鎖佔有以後,則只能等待該鎖定釋放資源以後本身才能獲取鎖定資源並添加本身的鎖定。而意向鎖的做用就是當一個事務在須要獲取資源鎖定的時候,若是遇到本身須要的資源已經被排他鎖佔用的時候,該事務能夠須要鎖定行的表上面添加一個合適的意向鎖。若是本身須要一個共享鎖,那麼就在表上面添加一個意向共享鎖。而若是本身須要的是某行(或者某些行)上面添加一個排他鎖的話,則先在表上面添加一個意向排他鎖。意向共享鎖能夠同時並存多個,可是意向排他鎖同時只能有一個存在。因此,能夠說Innodb的鎖定模式實際上能夠分爲四種:共享鎖(S),排他鎖(X),意向共享鎖(IS)和意向排他鎖(IX),咱們能夠經過如下表格來總結上面這四種所的共存邏輯關係:
|
共享鎖(S) |
排他鎖(X) |
意向共享鎖(IS) |
意向排他鎖(IX) |
共享鎖(S) |
兼容 |
衝突 |
兼容 |
衝突 |
排他鎖(X) |
衝突 |
衝突 |
衝突 |
衝突 |
意向共享鎖(IS) |
兼容 |
衝突 |
兼容 |
兼容 |
意向排他鎖(IX) |
衝突 |
衝突 |
兼容 |
兼容 |
雖然Innodb的鎖定機制和Oracle有很多相近的地方,可是二者的實現確是大相徑庭的。總的來講就是Oracle鎖定數據是經過須要鎖定的某行記錄所在的物理block上的事務槽上表級鎖定信息,而Innodb的鎖定則是經過在指向數據記錄的第一個索引鍵以前和最後一個索引鍵以後的空域空間上標記鎖定信息而實現的。Innodb的這種鎖定實現方式被稱爲「NEXT-KEYlocking」(間隙鎖),由於Query執行過程當中經過範圍查找的話,他會鎖定整個範圍內全部的索引鍵值,即便這個鍵值並不存在
間隙鎖有一個比較致命的弱點,就是當鎖定一個範圍鍵值以後,即便某些不存在的鍵值也會被無辜的鎖定,而形成在鎖定的時候沒法插入鎖定鍵值範圍內的任何數據。在某些場景下這可能會對性能形成很大的危害。而Innodb給出的解釋是爲了組織幻讀的出現,因此他們選擇的間隙鎖來實現鎖定
除了間隙鎖給Innodb帶來性能的負面影響以外,經過索引實現鎖定的方式還存在其餘幾個較大的性能隱患
MyISAM存儲引擎雖然消耗的資源小,可是顆粒度大,因此形成鎖定資源的爭用狀況多,併發能力差,因此在優化上就要儘量的讓鎖定的時間變短,讓可能併發進行的操做盡量的併發
一、縮短鎖定時間
如何讓鎖定時間儘量的短呢?惟一的辦法就是讓咱們的Query執行時間儘量的短
儘可能減小大的複雜Query,將複雜Query分拆成幾個小的Query分佈進行
儘量的創建足夠高效的索引,讓數據檢索更迅速
儘可能讓MyISAM存儲引擎的表只存放必要的信息,控制字段類型
二、分離能並行的操做
雖然MyISAM表鎖讀寫互相阻塞,可是它提供了一個很是有用的特性,那就是ConcurrentInsert(併發插入)的特性,MyISAM存儲引擎有一個控制是否打開Concurrent Insert功能的參數選項:concurrent_insert,能夠設置爲0,1或者2。三個值的具體說明以下
三、合理利用讀寫優先級
若是咱們的系統是一個以讀爲主,並且要優先保證查詢性能的話,咱們能夠經過設置系統參數選項low_priority_updates=1,將寫的優先級設置爲比讀的優先級低,便可讓告訴MySQL儘可能先處理讀請求
要想合理利用Innodb的行級鎖定,作到揚長避短,咱們必須作好如下工做:
Innodb的行級鎖定和事務性會產生死鎖,下面是一些比較經常使用的減小死鎖產生機率的的小建議
show status like 'table%';
Variable_name Value
Table_locks_immediate 75
Table_locks_waited 0
Table_open_cache_hits 3
Table_open_cache_misses 0
Table_open_cache_overflows 0
這裏有兩個狀態變量記錄MySQL內部表級鎖定的狀況,兩個變量說明以下:
Table_locks_immediate:產生表級鎖定的次數
Table_locks_waited:出現表級鎖定爭用而發生等待的次數
兩個狀態值都是從系統啓動後開始記錄,沒出現一次對應的事件則數量加1。若是這裏的Table_locks_waited狀態值比較高,那麼說明系統中表級鎖定爭用現象比較嚴重,就須要進一步分析爲何會有較多的鎖定資源爭用了
SHOW STATUS LIKE 'innodb_row_lock%'
Variable_name Value
Innodb_row_lock_current_waits 0
Innodb_row_lock_time 0
Innodb_row_lock_time_avg 0
Innodb_row_lock_time_max 0
Innodb_row_lock_waits 0
Innodb 的行級鎖定狀態變量不只記錄了鎖定等待次數,還記錄了鎖定總時長,每次平均時長,以及最大時長,此外還有一個非累積狀態量顯示了當前正在等待鎖定的等待數量。對各個狀態量的說明以下:
Innodb_row_lock_current_waits:當前正在等待鎖定的數量
Innodb_row_lock_time:從系統啓動到如今鎖定總時間長度
Innodb_row_lock_time_avg:每次等待所花平均時間
Innodb_row_lock_time_max:從系統啓動到如今等待最常的一次所花的時間
Innodb_row_lock_waits:系統啓動後到如今總共等待的次數
對於這5個狀態變量,比較重要的主要是time_avg(等待平均時長),waits(等待總次數)以及time(等待總時長)這三項。尤爲是當等待次數很高,並且每次等待時長也不小的時候,咱們就須要分析系統中爲何會有如此多的等待,而後根據分析結果着手指定優化計劃
推薦閱讀: