爲了保證數據的一致完整性,任何一個數據庫都存在鎖定機制。鎖定機制的優劣直接應想到一個數據庫系統的併發處理能力和性能,因此鎖定機制的實現也就成爲了各類數據庫的核心技術之一。本章將對MySQL中兩種使用最爲頻繁的存儲引擎MyISAM和Innodb各自的鎖定機制進行較爲詳細的分析。html
數據庫鎖定機制簡單來講就是數據庫爲了保證數據的一致性而使各類共享資源在被併發訪問訪問變得有序所設計的一種規則。對於任何一種數據庫來講都須要有相應的鎖定機制,因此MySQL天然也不能例外。MySQL數據庫因爲其自身架構的特色,存在多種數據存儲引擎,每種存儲引擎所針對的應用場景特色都不太同樣,爲了知足各自特定應用場景的需求,每種存儲引擎的鎖定機制都是爲各自所面對的特定場景而優化設計,因此各存儲引擎的鎖定機制也有較大區別。mysql
總的來講,MySQL各存儲引擎使用了三種類型(級別)的鎖定機制:行級鎖定,頁級鎖定和表級鎖定。下面咱們先分析一下MySQL這三種鎖定的特色和各自的優劣所在。算法
行級鎖定最大的特色就是鎖定對象的顆粒度很小,也是目前各大數據庫管理軟件所實現的鎖定顆粒度最小的。因爲鎖定顆粒度很小,因此發生鎖定資源爭用的機率也最小,可以給予應用程序儘量大的併發處理能力而提升一些須要高併發應用系統的總體性能。sql
雖然可以在併發處理能力上面有較大的優點,可是行級鎖定也所以帶來了很多弊端。因爲鎖定資源的顆粒度很小,因此每次獲取鎖和釋放鎖須要作的事情也更多,帶來的消耗天然也就更大了。此外,行級鎖定也最容易發生死鎖。數據庫
和行級鎖定相反,表級別的鎖定是MySQL各存儲引擎中最大顆粒度的鎖定機制。該鎖定機制最大的特色是實現邏輯很是簡單,帶來的系統負面影響最小。因此獲取鎖和釋放鎖的速度很快。因爲表級鎖一次會將整個表鎖定,因此能夠很好的避免困擾咱們的死鎖問題。session
固然,鎖定顆粒度大所帶來最大的負面影響就是出現鎖定資源爭用的機率也會最高,導致並大度大打折扣。架構
頁級鎖定是MySQL中比較獨特的一種鎖定級別,在其餘數據庫管理軟件中也並非太常見。頁級鎖定的特色是鎖定顆粒度介於行級鎖定與表級鎖之間,因此獲取鎖定所須要的資源開銷,以及所能提供的併發處理能力也一樣是介於上面兩者之間。另外,頁級鎖定和行級鎖定同樣,會發生死鎖。併發
在數據庫實現資源鎖定的過程當中,隨着鎖定資源顆粒度的減少,鎖定相同數據量的數據所須要消耗的內存數量是愈來愈多的,實現算法也會愈來愈複雜。不過,隨着鎖定資源顆粒度的減少,應用程序的訪問請求遇到鎖等待的可能性也會隨之下降,系統總體併發度也隨之提高。分佈式
在MySQL數據庫中,使用表級鎖定的主要是MyISAM,Memory,CSV等一些非事務性存儲引擎,而使用行級鎖定的主要是Innodb存儲引擎和NDBCluster存儲引擎,頁級鎖定主要是BerkeleyDB存儲引擎的鎖定方式。高併發
MySQL的如此的鎖定機制主要是因爲其最初的歷史所決定的。在最初,MySQL但願設計一種徹底獨立於各類存儲引擎的鎖定機制,並且在早期的MySQL數據庫中,MySQL的存儲引擎(MyISAM和Momery)的設計是創建在「任何表在同一時刻都只容許單個線程對其訪問(包括讀)」這樣的假設之上。可是,隨着MySQL的不斷完善,系統的不斷改進,在MySQL3.23版本開發的時候,MySQL開發人員不得不修正以前的假設。由於他們發現一個線程正在讀某個表的時候,另外一個線程是能夠對該表進行insert操做的,只不過只能INSERT到數據文件的最尾部。這也就是從MySQL從3.23版本開始提供的咱們所說的Concurrent Insert。
當出現Concurrent Insert以後,MySQL的開發人員不得不修改以前系統中的鎖定實現功能,可是僅僅只是增長了對Concurrent Insert的支持,並無改動總體架構。但是在不久以後,隨着BerkeleyDB存儲引擎的引入,以前的鎖定機制遇到了更大的挑戰。由於BerkeleyDB存儲引擎並無MyISAM和Memory存儲引擎同一時刻只容許單一線程訪問某一個表的限制,而是將這個單線程訪問限制的顆粒度縮小到了單個page,這又一次迫使MySQL開發人員不得再也不一次修改鎖定機制的實現。
因爲新的存儲引擎的引入,致使鎖定機制不能知足要求,讓MySQL的人意識到已經不可能實現一種徹底獨立的知足各類存儲引擎要求的鎖定實現機制。若是由於鎖定機制的拙劣實現而致使存儲引擎的總體性能的降低,確定會嚴重打擊存儲引擎提供者的積極性,這是MySQL公司很是不肯意看到的,由於這徹底不符合MySQL的戰略發展思路。因此工程師們不得不放棄了最初的設計初衷,在鎖定實現機制中做出修改,容許存儲引擎本身改變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)定義,各值描述以下:
鎖定類型 |
說明 |
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 |
一個新的客戶端請求在申請獲取讀鎖定資源的時候,須要知足兩個條件:
一、請求鎖定的資源當前沒有被寫鎖定;
二、寫鎖定等待隊列(Pendingwrite-lockqueue)中沒有更高優先級的寫鎖定等待;
若是知足了上面兩個條件以後,該請求會被當即經過,並將相關的信息存入Currentread-lockqueue中,而若是上面兩個條件中任何一個沒有知足,都會被迫進入等待隊列Pendingread-lockqueue中等待資源的釋放。
當客戶端請求寫鎖定的時候,MySQL首先檢查在Currentwrite-lockqueue是否已經有鎖定相同資源的信息存在。
若是Currentwrite-lockqueue沒有,則再檢查Pendingwrite-lockqueue,若是在Pendingwrite-lockqueue中找到了,本身也須要進入等待隊列並暫停自身線程等待鎖定資源。反之,若是Pendingwrite-lockqueue爲空,則再檢測Currentread-lockqueue,若是有鎖定存在,則一樣須要進入Pendingwrite-lockqueue等待。固然,也可能遇到如下這兩種特殊狀況:
1. 請求鎖定的類型爲WRITE_DELAYED;
2. 請求鎖定的類型爲WRITE_CONCURRENT_INSERT或者是TL_WRITE_ALLOW_WRITE,同時Currentreadlock是READ_NO_INSERT的鎖定類型。
當遇到這兩種特殊狀況的時候,寫鎖定會當即得到而進入Current write-lock queue 中
若是剛開始第一次檢測就Currentwrite-lockqueue中已經存在了鎖定相同資源的寫鎖定存在,那麼就只能進入等待隊列等待相應資源鎖定的釋放了。
讀請求和寫等待隊列中的寫鎖請求的優先級規則主要爲如下規則決定:
1. 除了READ_HIGH_PRIORITY的讀鎖定以外,Pendingwrite-lockqueue中的WRITE寫鎖定可以阻塞全部其餘的讀鎖定;
2. READ_HIGH_PRIORITY讀鎖定的請求可以阻塞全部Pendingwrite-lockqueue中的寫鎖定;
3. 除了WRITE寫鎖定以外,Pendingwrite-lockqueue中的其餘任何寫鎖定都比讀鎖定的優先級低。
寫鎖定出如今Currentwrite-lockqueue以後,會阻塞除了如下狀況下的全部其餘鎖定的請求:
1. 在某些存儲引擎的容許下,能夠容許一個WRITE_CONCURRENT_INSERT寫鎖定請求
2. 寫鎖定爲WRITE_ALLOW_WRITE的時候,容許除了WRITE_ONLY以外的全部讀和寫鎖定請求
3. 寫鎖定爲WRITE_ALLOW_READ的時候,容許除了READ_NO_INSERT以外的全部讀鎖定請求
4. 寫鎖定爲WRITE_DELAYED的時候,容許除了READ_NO_INSERT以外的全部讀鎖定請求
5. 寫鎖定爲WRITE_CONCURRENT_INSERT的時候,容許除了READ_NO_INSERT以外的全部讀鎖定請求
隨着MySQL存儲引擎的不斷髮展,目前MySQL自身提供的鎖定機制已經沒有辦法知足需求了,不少存儲引擎都在MySQL所提供的鎖定機制之上作了存儲引擎本身的擴展和改造。
MyISAM存儲引擎基本上能夠說是對MySQL所提供的鎖定機制所實現的表級鎖定依賴最大的一種存儲引擎了,雖然MyISAM存儲引擎本身並無在自身增長其餘的鎖定機制,可是爲了更好的支持相關特性,MySQL在原有鎖定機制的基礎上爲了支持其ConcurrentInsert的特性而進行了相應的實現改造。
而其餘幾種支持事務的存儲存儲引擎,如Innodb,NDBCluster以及BerkeleyDB存儲引擎則是讓MySQL將鎖定的處理直接交給存儲引擎本身來處理,在MySQL中僅持有WRITE_ALLOW_WRITE類型的鎖定。
因爲MyISAM存儲引擎使用的鎖定機制徹底是由MySQL提供的表級鎖定實現,因此下面咱們將以MyISAM存儲引擎做爲示例存儲引擎,來實例演示表級鎖定的一些基本特性。因爲,爲了讓示例更加直觀,我將使用顯示給表加鎖來演示:RITE_ALLOW_READ 類型的寫鎖定
時刻 |
Session a |
Session b |
行鎖定基本演示 |
||
1 |
mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) |
mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) |
mysql> update test_innodb_lock set b = 'b1' where a = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 更新,可是不提交 |
||
2 |
mysql> update test_innodb_lock set b = 'b1' where a = 1; 被阻塞,等待 |
|
3 |
mysql> commit; Query OK, 0 rows affected (0.05 sec) 提交 |
|
4 |
mysql> update test_innodb_lock set b = 'b1' where a = 1; Query OK, 0 rows affected (36.14 sec) Rows matched: 1 Changed: 0 Warnings: 0 解除阻塞,更新正常進行 |
|
無索引升級爲表鎖演示 |
||
5 |
mysql> update test_innodb_lock set b = '2' where b = 2000; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
mysql> update test_innodb_lock set b = '3' where b = 3000; 被阻塞,等待 |
6 |
||
7 |
mysql> commit; Query OK, 0 rows affected (0.10 sec) |
|
8 |
mysql> update test_innodb_lock set b = '3' where b = 3000; Query OK, 1 row affected (1 min 3.41 sec) Rows matched: 1 Changed: 1 Warnings: 0 阻塞解除,完成更新 |
|
間隙鎖帶來的插入問題演示 |
||
9 |
mysql> select * from test_innodb_lock; +------+------+ | a | b |+------+------+ | 1 | b2 | | 3 | 3 | | 4 | 4000 | | 5 | 5000 | | 6 | 6000 | | 7 | 7000 | | 8 | 8000 | | 9 | 9000 | | 1 | b1 | +------+------+ 9 rows in set (0.00 sec) mysql> update test_innodb_lock set b = a * 100 where a < 4 and a > 1; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
10 |
mysql> insert into test_innodb_lock values(2,'200'); 被阻塞,等待 |
|
11 |
mysql> commit; Query OK, 0 rows affected (0.02 sec) |
|
12 |
mysql> insert into test_innodb_lock values(2,'200'); Query OK, 1 row affected (38.68 sec) 阻塞解除,完成插入 |
|
使用共同索引不一樣數據的阻塞示例 |
||
13 |
mysql> update test_innodb_lock set b = 'bbbbb' where a = 1 and b = 'b2'; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
14 |
mysql> update test_innodb_lock set b = 'bbbbb' where a = 1 and b = 'b1'; 被阻塞 |
|
15 |
mysql> commit; Query OK, 0 rows affected (0.02 sec) |
|
16 |
mysql> update test_innodb_lock set b = 'bbbbb' where a = 1 and b = 'b1'; Query OK, 1 row affected (42.89 sec) Rows matched: 1 Changed: 1 Warnings: 0 session 提交事務,阻塞去除,更新完成 |
|
死鎖示例 |
||
17 |
mysql> update t1 set id = 110 where id = 11; Query OK, 0 rows affected (0.00 sec) Rows matched: 0 Changed: 0 Warnings: 0 |
|
18 |
mysql> update t2 set id = 210 where id = 21; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
19 |
mysql>update t2 set id=2100 where id=21; 等待sessionb釋放資源,被阻塞 |
|
20 |
mysql>update t1 set id=1100 where id=11; Query OK,0 rows affected (0.39sec) Rows matched: 0 Changed: 0 Warnings:0 等待sessiona釋放資源,被阻塞 |
|
兩個 session 互相等等待對方的資源釋放以後才能釋放本身的資源,形成了死鎖
|
行級鎖定不是MySQL本身實現的鎖定方式,而是由其餘存儲引擎本身所實現的,如廣爲你們所知的Innodb存儲引擎,以及MySQL的分佈式存儲引擎NDBCluster等都是實現了行級鎖定。
考慮到行級鎖定君由各個存儲引擎自行實現,並且具體實現也各有差異,而Innodb是目前事務型存儲引擎中使用最爲普遍的存儲引擎,因此這裏咱們就主要分析一下Innodb的鎖定特性。
總的來講,Innodb的鎖定機制和Oracle數據庫有很多類似之處。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帶來性能的負面影響以外,經過索引實現鎖定的方式還存在其餘幾個較大的性能隱患:
當Query沒法利用索引的時候,Innodb會放棄使用行級別鎖定而改用表級別的鎖定,形成併發性能的下降;
當Quuery使用的索引並不包含全部過濾條件的時候,數據檢索使用到的索引鍵所只想的數據可能有部分並不屬於該Query的結果集的行列,可是也會被鎖定,由於間隙鎖鎖定的是一個範圍,而不是具體的索引鍵;
當Query在使用索引定位數據的時候,若是使用的索引鍵同樣但訪問的數據行不一樣的時候(索引只是過濾條件的一部分),同樣會被鎖定
Innodb實現的在ISO/ANSISQL92規範中所定義的ReadUnCommited,ReadCommited,RepeatableRead和Serializable這四種事務隔離級別。同時,爲了保證數據在事務中的一致性,實現了多版本數據訪問。
以前在第一節中咱們已經介紹過,行級鎖定確定會帶來死鎖問題,Innodb也不可能例外。至於死鎖的產生過程咱們就不在這裏詳細描述了,在後面的鎖定示例中會經過一個實際的例子爲你們愛展現死鎖的產生過程。這裏咱們主要介紹一下,在Innodb中當系檢測到死鎖產生以後是如何來處理的。
在Innodb的事務管理和鎖定機制中,有專門檢測死鎖的機制,會在系統中產生死鎖以後的很短期內就檢測到該死鎖的存在。當Innodb檢測到系統中產生了死鎖以後,Innodb會經過相應的判斷來選這產生死鎖的兩個事務中較小的事務來回滾,而讓另一個較大的事務成功完成。那Innodb是以什麼來爲標準斷定事務的大小的呢?MySQL官方手冊中也提到了這個問題,實際上在Innodb發現死鎖以後,會計算出兩個事務各自插入、更新或者刪除的數據量來斷定兩個事務的大小。也就是說哪一個事務所改變的記錄條數越多,在死鎖中就越不會被回滾掉。可是有一點須要注意的就是,當產生死鎖的場景中涉及到不止Innodb存儲引擎的時候,Innodb是沒辦法檢測到該死鎖的,這時候就只能經過鎖定超時限制來解決該死鎖了。另外,死鎖的產生過程的示例將在本節最後的Innodb鎖定示例中演示。
mysql> create table test_innodb_lock (a int(11),b varchar(16)) engine=innodb; Query OK, 0 rows affected (0.02 sec) mysql> create index test_innodb_a_ind on test_innodb_lock(a); Query OK, 0 rows affected (0.05 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> create index test_innodb_lock_b_ind on test_innodb_lock(b); Query OK, 11 rows affected (0.01 sec) Records: 11 Duplicates: 0 Warnings: 0
時刻 |
Session a |
Session b |
行鎖定基本演示 |
||
1 |
mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) |
mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) |
mysql> update test_innodb_lock set b = 'b1' where a = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 更新,可是不提交 |
||
2 |
mysql> update test_innodb_lock set b = 'b1' where a = 1; 被阻塞,等待 |
|
3 |
mysql> commit; Query OK, 0 rows affected (0.05 sec) 提交 |
|
4 |
mysql> update test_innodb_lock set b = 'b1' where a = 1; Query OK, 0 rows affected (36.14 sec) Rows matched: 1 Changed: 0 Warnings: 0 解除阻塞,更新正常進行 |
|
無索引升級爲表鎖演示 |
||
5 |
mysql> update test_innodb_lock set b = '2' where b = 2000; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
mysql> update test_innodb_lock set b = '3' where b = 3000; 被阻塞,等待 |
6 |
||
7 |
mysql> commit; Query OK, 0 rows affected (0.10 sec) |
|
8 |
mysql> update test_innodb_lock set b = '3' where b = 3000; Query OK, 1 row affected (1 min 3.41 sec) Rows matched: 1 Changed: 1 Warnings: 0 阻塞解除,完成更新 |
|
間隙鎖帶來的插入問題演示 |
||
9 |
mysql> select * from test_innodb_lock; +------+------+ | a | b |+------+------+ | 1 | b2 | | 3 | 3 | | 4 | 4000 | | 5 | 5000 | | 6 | 6000 | | 7 | 7000 | | 8 | 8000 | | 9 | 9000 | | 1 | b1 | +------+------+ 9 rows in set (0.00 sec) mysql> update test_innodb_lock set b = a * 100 where a < 4 and a > 1; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
10 |
mysql> insert into test_innodb_lock values(2,'200'); 被阻塞,等待 |
|
11 |
mysql> commit; Query OK, 0 rows affected (0.02 sec) |
|
12 |
mysql> insert into test_innodb_lock values(2,'200'); Query OK, 1 row affected (38.68 sec) 阻塞解除,完成插入 |
|
使用共同索引不一樣數據的阻塞示例 |
||
13 |
mysql> update test_innodb_lock set b = 'bbbbb' where a = 1 and b = 'b2'; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
14 |
mysql> update test_innodb_lock set b = 'bbbbb' where a = 1 and b = 'b1'; 被阻塞 |
|
15 |
mysql> commit; Query OK, 0 rows affected (0.02 sec) |
|
16 |
mysql> update test_innodb_lock set b = 'bbbbb' where a = 1 and b = 'b1'; Query OK, 1 row affected (42.89 sec) Rows matched: 1 Changed: 1 Warnings: 0 session 提交事務,阻塞去除,更新完成 |
|
死鎖示例 |
||
17 |
mysql> update t1 set id = 110 where id = 11; Query OK, 0 rows affected (0.00 sec) Rows matched: 0 Changed: 0 Warnings: 0 |
|
18 |
mysql> update t2 set id = 210 where id = 21; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
19 |
mysql>update t2 set id=2100 where id=21; 等待sessionb釋放資源,被阻塞 |
|
20 |
mysql>update t1 set id=1100 where id=11; Query OK,0 rows affected (0.39sec) Rows matched: 0 Changed: 0 Warnings:0 等待sessiona釋放資源,被阻塞 |
|
兩個 session 互相等等待對方的資源釋放以後才能釋放本身的資源,形成了死鎖
|
對於MyISAM存儲引擎,雖然使用表級鎖定在鎖定實現的過程當中比實現行級鎖定或者頁級鎖所帶來的附加成本都要小,鎖定自己所消耗的資源也是最少。可是因爲鎖定的顆粒度比較到,因此形成鎖定資源的爭用狀況也會比其餘的鎖定級別都要多,從而在較大程度上會下降併發處理能力。
因此,在優化MyISAM存儲引擎鎖定問題的時候,最關鍵的就是如何讓其提升併發度。因爲鎖定級別是不可能改變的了,因此咱們首先須要儘量讓鎖定的時間變短,而後就是讓可能併發進行的操做盡量的併發。
一、縮短鎖定時間
縮短鎖定時間,短短几個字,提及來確實聽容易的,但實際作起來恐怕就並不那麼簡單了。如何讓鎖定時間儘量的短呢?惟一的辦法就是讓咱們的Query執行時間儘量的短。
盡兩減小大的複雜Query,將複雜Query分拆成幾個小的Query分佈進行;
儘量的創建足夠高效的索引,讓數據檢索更迅速;
儘可能讓MyISAM存儲引擎的表只存放必要的信息,控制字段類型;
利用合適的機會優化MyISAM表數據文件;
二、分離能並行的操做
說到MyISAM的表鎖,並且是讀寫互相阻塞的表鎖,可能有些人會認爲在MyISAM存儲引擎的表上就只能是徹底的串行化,沒辦法再並行了。你們不要忘記了,MyISAM的存儲引擎還有一個很是有用的特性,那就是ConcurrentInsert(併發插入)的特性。
MyISAM存儲引擎有一個控制是否打開Concurrent Insert功能的參數選項:concurrent_insert,能夠設置爲0,1或者2。三個值的具體說明以下:
concurrent_insert=2,不管MyISAM存儲引擎的表數據文件的中間部分是否存在由於刪除數據而留下的空閒空間,都容許在數據文件尾部進行ConcurrentInsert;
concurrent_insert=1,當MyISAM存儲引擎表數據文件中間不存在空閒空間的時候,能夠從文件尾部進行ConcurrentInsert;
concurrent_insert=0,不管MyISAM存儲引擎的表數據文件的中間部分是否存在由於刪除數據而留下的空閒空間,都不容許ConcurrentInsert。
三、合理利用讀寫優先級
在本章各類鎖定分析一節中咱們瞭解到了MySQL的表級鎖定對於讀和寫是有不一樣優先級設定的,默認狀況下是寫優先級要大於讀優先級。因此,若是咱們能夠根據各自系統環境的差別決定讀與寫的優先級。若是咱們的系統是一個以讀爲主,並且要優先保證查詢性能的話,咱們能夠經過設置系統參數選項low_priority_updates=1,將寫的優先級設置爲比讀的優先級低,便可讓告訴MySQL儘可能先處理讀請求。固然,若是咱們的系統須要有限保證數據寫入的性能的話,則能夠不用設置low_priority_updates參數了。
這裏咱們徹底能夠利用這個特性,將concurrent_insert參數設置爲1,甚至若是數據被刪除的可能性很小的時候,若是對暫時性的浪費少許空間並非特別的在意的話,將concurrent_insert參數設置爲2均可以嘗試。固然,數據文件中間留有空域空間,在浪費空間的時候,還會形成在查詢的時候須要讀取更多的數據,因此若是刪除量不是很小的話,仍是建議將concurrent_insert設置爲1更爲合適。
Innodb存儲引擎因爲實現了行級鎖定,雖然在鎖定機制的實現方面所帶來的性能損耗可能比表級鎖定會要更高一些,可是在總體併發處理能力方面要遠遠優於MyISAM的表級鎖定的。當系統併發量較高的時候,Innodb的總體性能和MyISAM相比就會有比較明顯的優點了。可是,Innodb的行級鎖定一樣也有其脆弱的一面,當咱們使用不當的時候,可能會讓Innodb的總體性能表現不只不能比MyISAM高,甚至可能會更差。
要想合理利用Innodb的行級鎖定,作到揚長避短,咱們必須作好如下工做:
儘量讓全部的數據檢索都經過索引來完成,從而避免Innodb由於沒法經過索引鍵加鎖而升級爲表級鎖定;
合理設計索引,讓Innodb在索引鍵上面加鎖的時候儘量準確,儘量的縮小鎖定範圍,避免形成沒必要要的鎖定而影響其餘Query的執行;
儘量減小基於範圍的數據檢索過濾條件,避免由於間隙鎖帶來的負面影響而鎖定了不應鎖定的記錄;
儘可能控制事務的大小,減小鎖定的資源量和鎖定時間長度;
在業務環境容許的狀況下,儘可能使用較低級別的事務隔離,以減小MySQL由於實現事務隔離級別所帶來的附加成本;
因爲Innodb的行級鎖定和事務性,因此確定會產生死鎖,下面是一些比較經常使用的減小死鎖產生機率
的的小建議,讀者朋友能夠根據各自的業務特色針對性的嘗試:a)相似業務模塊中,儘量按照相同的訪問順序來訪問,防止產生死鎖;b)在同一個事務中,儘量作到一次鎖定所須要的全部資源,減小死鎖產生機率;c)對於很是容易產生死鎖的業務部分,能夠嘗試使用升級鎖定顆粒度,經過表級鎖定來減小死鎖產生的機率;
系統鎖定爭用狀況查詢對於兩種鎖定級別,MySQL內部有兩組專門的狀態變量記錄系統內部鎖資源爭用狀況,咱們先看看
MySQL 實現的表級鎖定的爭用狀態變量:
mysql> show status like 'table%'; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | Table_locks_immediate | 100 | | Table_locks_waited | 0 | +-----------------------+-------+
這裏有兩個狀態變量記錄MySQL內部表級鎖定的狀況,兩個變量說明以下:
Table_locks_immediate:產生表級鎖定的次數;
Table_locks_waited:出現表級鎖定爭用而發生等待的次數;
兩個狀態值都是從系統啓動後開始記錄,沒出現一次對應的事件則數量加1。若是這裏的Table_locks_waited狀態值比較高,那麼說明系統中表級鎖定爭用現象比較嚴重,就須要進一步分析爲何會有較多的鎖定資源爭用了。
對於Innodb所使用的行級鎖定,系統中是經過另一組更爲詳細的狀態變量來記錄的,以下:
mysql>showstatuslike'innodb_row_lock%'; +-------------------------------+--------+|Variable_name|Value|+-------------------------------+--------+ |Innodb_row_lock_current_waits|0| |Innodb_row_lock_time|490578| |Innodb_row_lock_time_avg|37736| |Innodb_row_lock_time_max|121411| |Innodb_row_lock_waits|13| +-------------------------------+--------+
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個狀態變量,比較重要的主要是Innodb_row_lock_time_avg(等待平均時長),Innodb_row_lock_waits(等待總次數)以及Innodb_row_lock_time(等待總時長)這三項。尤爲是當等待次數很高,並且每次等待時長也不小的時候,咱們就須要分析系統中爲何會有如此多的等待,而後根據分析結果着手指定優化計劃。
此外,Innodb出了提供這五個系統狀態變量以外,還提供的其餘更爲豐富的即時狀態信息供咱們分析使用。能夠經過以下方法查看:
1.經過建立InnodbMonitor表來打開Innodb的monitor功能:
mysql> create table innodb_monitor(a int) engine=innodb; Query OK, 0 rows affected (0.07 sec)
2.而後經過使用「SHOWINNODBSTATUS」查看細節信息(因爲輸出內容太多就不在此記錄了);
可能會有讀者朋友問爲何要先建立一個叫innodb_monitor的表呢?由於建立該表實際上就是告訴Innodb咱們開始要監控他的細節狀態了,而後Innodb就會將比較詳細的事務以及鎖定信息記錄進入MySQL的errorlog中,以便咱們後面作進一步分析使用。
本章以MySQLServer中的鎖定簡介開始,分析了當前MySQL中使用最爲普遍的鎖定方式表級鎖定和行級鎖定的基本實現機制,並經過MyISAM和Innodb這兩大典型的存儲引擎做爲示例存儲引擎所使用的表級鎖定和行級鎖定作了較爲詳細的分析和演示。而後,再經過分析兩種鎖定方式的特性,給出相應的優化建議和策略。最後瞭解了一下在MySQLServer中如何得到系統當前各類鎖定的資源爭用情況。但願本章內容可以對各位讀者朋友在理解MySQL鎖定機制方面有必定的幫助。