MySQL性能調優與架構設計(六)—— MySQL數據庫鎖定機制

前言

  1. 在說鎖定機制以前,有必要理解下併發與並行的基本概念。
  2. 併發是指一臺處理器上同時處理多個任務,並行是指多個處理器同時處理多個任務,如hadoop分佈式集羣。
  3. 通俗的講,併發就是不一樣線程同時幹一件事情,並行就是不一樣線程同時幹不一樣的事情。
  4. 因此併發編程的目標是充分的利用處理器的每個核,以達到最高的性能。
  5. 那麼併發必然會牽扯到應用系統中資源的競爭。
  6. 詳細的關於並行與併發能夠參考一篇文章來理解:https://blog.csdn.net/qq_3329...
  7. 在高併發的狀況下,爲了保證數據的完整一致性,任何一個數據庫都有鎖定機制。
  8. 鎖定機制的優劣直接影響了一個數據庫的併發處理能力和性能。
  9. 本章將對mysql中兩種使用最爲頻繁的存儲引擎MyISAM和InnoDB各自的鎖定機制進行較爲詳細的分析。

MySQL鎖定機制簡介

  1. 數據庫鎖定機制簡單來講就是數據庫爲了保證數據的一致性而使各類共享資源在被併發訪問變得有序所設計的一種規則。
  2. 對於任何一種數據庫來講都須要有相應的鎖定機制,因此MySQL天然也不例外。
  3. MySQL數據庫因爲其自身架構的特色,存在多種數據存儲引擎,每種存儲引擎所針對的應用場景特色都不太同樣,爲了知足各自特定應用場景的需求,每種存儲引擎的鎖定機制都是爲各自所面對的特定場景而優化設計,因此,各類存儲引擎的鎖定機制也有較大區別。
  4. 總的來講,MySQL各存儲引擎使用了三種類型(級別)的鎖定機制:行級鎖定,頁級鎖定和表級鎖定。

行級鎖定(row-level)

  1. 行級鎖,通常是指排它鎖,即被鎖定行不可進行修改、刪除,只能夠被其餘會話select。
  2. 排他鎖又稱爲寫鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其餘鎖並存,如一個事務獲取了一個數據行的排他鎖,其餘事務就不能再獲取該行的其餘鎖。
  3. 行級鎖定最大的特色就是鎖定對象的顆粒度很小,也是目前各大數據庫管理軟件所實現的鎖定顆粒度最小的。
  4. 因爲鎖定顆粒度很小,因此發生鎖定資源競爭的機率也小,可以給予應用程序儘量大的併發處理能力提升一些須要高併發應用系統的總體性能。
  5. 雖然在併發處理能力上面有較大的優點,可是行級鎖定也所以帶來了很多弊端。
  6. 因爲鎖定資源的顆粒度很小,因此每次獲取鎖和釋放鎖須要作的事情也不少,帶來的消耗天然也就更大了。
  7. 此外,行級鎖定也最容易發生死鎖。

表級鎖定(table-level)

  1. 表級鎖,直接鎖定整張表,在你鎖按期間,其餘進程沒法對該表進行寫操做。若是你是寫鎖,則其餘進程則讀也不容許。
  2. 和行級鎖定相反,表級別的鎖定是mysql各存儲引擎中最大顆粒度的鎖定機制。
  3. 該鎖定機制最大的特色就是實現邏輯很是簡單,帶來的系統負面影響最小。因此獲取鎖和釋放鎖的速度很快。
  4. 因爲表級鎖一次會將整個表鎖定,因此能夠很好的避免困擾咱們的死鎖問題。
  5. 固然,鎖定顆粒度大帶來的負面影響就是出現資源爭用的機率也會很高,導致併發度大打折扣。

頁級鎖定(page-level)

  1. 頁級鎖定是MySQL中比較獨特的一種鎖定級別,在其餘數據庫管理軟件中也並非太常見。
  2. 頁級鎖定的特色是鎖定顆粒度介於行級鎖定和表級鎖定之間,因此獲取鎖所須要的資源開銷,以及所能提供的併發處理能力也一樣介於上面兩者之間。
  3. 另外,頁級鎖定和行級鎖定同樣,也會發生死鎖。

小結

  1. 在數據庫實現資源鎖定的過程當中,隨着鎖定資源顆粒度的減少,鎖定相同數據量的數據所須要消耗的內存數量是愈來愈多,實現算法也會愈來愈複雜。
  2. 隨着鎖定資源顆粒度的減少,應用程序的訪問請求遇到鎖等待的可能性也會隨之下降,系統總體併發度頁隨之提高。
  3. 在MySQL中,使用表級鎖定的是MyISAM、MEmory、CSv等一些非事務型存儲引擎,而使用行級鎖的主要是InnoDB存儲引擎和NDB Cluster存儲引擎,頁級鎖定主要是BerkeleyDB存儲引擎的鎖定方式。

各類鎖定機制分析

表級鎖定

  1. MySQL的表級鎖定主要分爲兩種類型,一種是寫鎖定,一種是讀鎖定。
  2. 在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)
  3. 當前持有讀鎖的全部線程的相關信息都可以在Current read-lock queue (lock->read) 中找到,隊列中的信息按照獲取到鎖的時間依次存放。而正在等待讀鎖的信息則存放在Pending read-lock queue 裏面,另外兩個存放寫鎖信息的隊列也按照相同的規則來存放信息。

表級讀、寫鎖定

  1. 讀鎖定:html

    (1)一個新的客戶端請求在申請讀鎖資源的時候,須要知足兩個條件:
        【1】請求鎖定的資源沒有寫鎖定
        【2】寫鎖定等待隊列Pending write-lock queue中沒有更高優先級的寫鎖定等待
    (2)若是知足來上述兩個條件以後,該請求會當即經過,並將相關的信息存入Current read-lock queue 中,而若是上面兩個條件中有一個不知足,都會被迫進入等待隊列Pending read-lock queue中等待資源的釋放。
  2. 寫鎖定:mysql

    (1)當客戶端請求寫鎖定的時候,MySQL首先會檢查Current write-lock queue中是否已經有鎖定相同資源的信息存在。
    (2)若是Current write-lock queue中沒有,則再檢查Pending write-lock queue;
    (3)若是Pending write-lock queue中找到了,本身也須要進入等待隊列並暫停自身線程等待鎖定資源。
    (4)反之,若是Pending write-lock queue爲空,則再檢測Current read-lock queue,若是有鎖定存在,則一樣須要進入Pending write-lock queue等待。
    (5)有兩種特殊狀況,會當即得到鎖而進入Current write-lock queue中:
        【1】請求鎖定的類型爲WRITE_DELAYED;
        【2】請求鎖定類型爲WRITE_CONCURRENT_INSERT或者TL_WRITE_ALLOW_WRITE,同時Current read lock是READ_NO_INSERT的鎖定類型。 
    (6)若是一開始就檢測到Current write-lock queue中已經存在了鎖定相同資源的寫鎖定存在,那麼就只能進入等待隊列等待相應資源鎖定的釋放。
  3. 雖然對於咱們這些使用者來講,mysql展示出來的只有讀鎖和寫鎖,實際上在MySQL內部實現中卻有多達11種鎖定類型,有系統中一個枚舉量(thr_lock_type)定義。具體的類型能夠另外查詢。
  4. 讀請求和寫等待隊列中的寫鎖請求的優先級規則主要爲如下規則決定:算法

    (1)除了READ_HIGH_PRIORITY的讀鎖定以外,Pending write-lock queue中的    WRITE寫鎖定可以阻塞全部其餘的讀鎖定;
    (2)READ_HIGH_PRIORITY讀鎖定的請求可以阻塞全部Pending write-lock queue中的寫鎖定;
    (3)除了WRITE寫鎖定以外,Pending write-lock queue中的其餘任何寫鎖定都比讀鎖定的優先級低。
  5. 寫鎖定出如今Current write-lock queue以後,會阻塞除了如下狀況下的全部其餘鎖定的請求:sql

    (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以外的全部讀鎖定請求

行級鎖定- Innodb鎖定模式及實現機制

  1. Innodb的行級鎖定分爲兩種類型,共享鎖和排他鎖。
  2. 在鎖定機制的實現過程當中爲了讓行級鎖定和表級鎖定共存,Innodb也一樣使用了意向鎖(表級鎖定)的概念,也就有了意向共享鎖和意向排他鎖這兩種。
  3. 當一個事務須要給本身須要的某個資源加鎖的時候,若是遇到一個共享鎖正鎖定着本身須要的資源,本身能夠再加一個共享鎖,不過不能加排他鎖。
  4. 可是,若是遇到本身須要鎖定的資源已經被一個排他鎖佔有以後,則只能等待該鎖定釋放資源以後本身才能獲取鎖定資源並添加本身的鎖定。
  5. 而意向鎖的做用就是當一個事務在須要獲取資源鎖定的時候,若是遇到本身須要的資源已經被排他鎖佔用的時候,該事務能夠須要鎖定行的表上面添加一個合適的意向鎖。
  6. 若是本身須要一個共享鎖,那麼就在表上面添加一個意向共享鎖。
  7. 而若是本身須要的是某行上面添加一個排他鎖的花話,則先在表上面添加一個意向排他鎖。
  8. 意向共享鎖能夠同時並存多個,可是意向排他鎖同時只能有一個存在。
  9. 因此,能夠說Innodb的鎖定模式實際上能夠分爲四種:共享鎖(S),排他鎖(X),意向共享鎖(IS),意向排他鎖(IX)。咱們能夠經過如下表格來總結上面這四種的共存邏輯關係:
    圖片描述
  10. 雖然Innodb的鎖定機制和Oracle有很多相近的地方,可是二者的實現卻大相徑庭。
  11. 總的來講,Oracle鎖定數據是經過須要鎖定的某行記錄所在的物理block上的事務槽上表級鎖定信息,而Innodb的鎖定則是經過在指定數據記錄的第一個索引鍵以前和最後一個索引鍵以後的空域空間上標記鎖定信息而實現的。
  12. Innodb的這種鎖定實現方式被稱爲「NEXT-KEY locking」(間隙鎖),由於query執行過程當中經過範圍查找的話,他會鎖定整個範圍內全部的索引鍵值,即便這個鍵值並不存在。
  13. 間隙鎖有一個致命弱點,就是當鎖定一個範圍鍵值以後,即便某些不存在的鍵值也會被鎖定,而形成在鎖定的時候沒法插入鎖定建值範圍內的任何數據。在某些場景下這可能會對性能形成很大的危害,而Innodb給出的解釋是爲了阻止幻讀的出現,因此他們選擇間隙鎖來實現鎖定。
  14. 除了間隙鎖給Innodb帶來性能的負面影響以外,經過索引實現鎖定的方式還存在其餘幾個較大的性能隱患:數據庫

    (1)當query沒法利用索引的時候,Innodb會放棄使用行級鎖定而該用表級鎖定,形成併發性能的下降;
    (2)當query使用的索引並不包含全部過濾條件的時候,數據檢索使用到的索引鍵所指向的數據可能有部分並不屬於該query的結果集的行列,可是也會被鎖定,由於間隙鎖鎖定的是一個範圍,而不是具體的索引鍵;
    (3)當query使用索引定位數據的時候,若是使用的索引鍵同樣但訪問的數據行不一樣的時候(索引只是過濾條件的一部分),同樣會被鎖定。

行級鎖定 - Innodb各事務隔離級別下鎖定及死鎖

  1. Innodb實現的在ISO/ANSI SQL92規範中鎖定義的Read UnCommited,Read Commited,Repeatable Read和Serializable這四種事務隔離級別。同時,爲了保證數據在事務中的一致性,實現了多版本數據訪問。
  2. 以前咱們已經介紹過,行級鎖定確定會帶來死鎖問題,Innodb也不例外。
  3. 在Innodb中當系統檢測到死鎖產生以後是如何來處理的?編程

    (1)在Innodb的事務管理和鎖定機制中,有專門檢測死鎖的機制,會在系統中產生死鎖以後的很短期內就檢測到該死鎖的存在。
    (2)當Innodb檢測到死鎖以後,Innodb會經過相應的判斷來選這產生死鎖的兩個事務中較小的事務來回滾,而讓另一個較大的事務成功完成。
    (3)那Innodb是以什麼爲標準斷定事務的大小呢?實際上在Innodb發現死鎖以後,會計算出兩個事務各自插入、更新或者刪除的數據量來斷定兩個事務的大小。也就是說哪一個事務所改變的記錄條數越多,在死鎖中就越不會被回滾掉。
    (4)有一點要注意,當產生死鎖的場景中涉及到不止Innodb存儲引擎的時候,Innodb是沒辦法檢測到該死鎖的,這時候就只能經過鎖定超時限制來解決該死鎖了。

合理利用鎖機制優化MySQL

MyISAM表鎖優化建議

  1. 對於MyISAM存儲引擎,雖然使用表級鎖定在鎖定實現的過程當中比實現行級鎖定或者頁級鎖定所帶來的附加成本都要小,鎖定自己所消耗的資源也是最少,可是,因爲鎖定的顆粒度較大,因此形成鎖定資源的爭用狀況也會比其餘的鎖定級別要多,從而在較大程度上會下降併發處理能力。
  2. 因此,在優化MyISAM存儲引擎鎖定問題的時候,最關鍵的是如何讓其提升併發度。
  3. 因爲鎖定級別是不可能改變的了,因此咱們首先須要儘量讓鎖定的時間變短,而後就是讓可能併發進行的操做盡量的併發。
  4. 縮短鎖定時間架構

    (1)縮短鎖定時間,提及來容易,實際作起來不簡單。如何讓鎖定時間儘量的縮短呢?惟一的辦法就是讓咱們的query執行時間儘量的短
    (2)儘可能減小大的複雜query,將複雜query分拆成幾個小的query分佈進行中
    (3)儘量的創建足夠高效的索引,讓數據檢索更迅速
    (4)儘可能讓MyISAM存儲引擎的表只存放必要的信息,控制字段類型
    (5)利用合適的機會優化MyISAM表數據文件
  5. 分離能並行的操做併發

    (1)MyISAM表鎖是讀寫互相阻塞的表鎖,因此可能有些人會認爲在MyISAM存儲引擎的表上就只能徹底的串行化,沒辦法並行了。
    (2)可是不要忘記,MyISAM的存儲引擎還有一個很是有用的特性,那就是個Concurrent Insert(併發插入)的特性。
    (3)MyISAM存儲引擎有一個控制是否打開Concurrent Insert(併發插入)功能的參數選項:concurrent_insert,能夠設置爲0、一、2
        【1】concurrent_insert=2,不管MyISAM存儲引擎的表數據文件的中間部分是否存在由於刪除數據而留下的空閒空間,都容許在數據文件尾部進行Concurrent Insert; 
        【2】concurrent_insert=1,當MyISAM存儲引擎表數據文件中間不存在空閒空間的時候,能夠從文件尾部進行Concurrent Insert;
        【3】concurrent_insert=0,不管MyISAM存儲引擎的表數據文件的中間部分是否存在因刪除數據而留下的空閒空間,都不容許Concurrent Insert。
  6. 合理利用讀寫優先級分佈式

    (1)在本章各類鎖定分析一節中咱們瞭解到,mysql的表級鎖定對於讀和寫是有不一樣優先級的,默認狀況下是寫優先級大於讀優先級。
    (2)因此咱們能夠根據各自系統環境的差別決定讀與寫的優先級。
    (3)若是咱們的系統是一個以讀爲主,並且要優先保證查詢性能的話,咱們能夠經過設置系統參數選項low_priority_updates=1,將寫的優先級設置爲比讀優先級低,這樣mysql就會盡可能先處理讀請求。
    (4)這裏咱們徹底能夠利用這個特性,將concurrent_insert參數設置爲1,甚至若是數據被刪除的可能性很小的時候,若是對暫時性的浪費少許空間並非特別的在意的話,將concurrent_insert參數設置爲2均可以嘗試。固然,數據文件中間留有空域空間,在浪費空間的時候,還會形成在查詢的時候須要讀取更多的數據,因此若是刪除量不是很小的話,仍是建議將concurrent_insert設置爲1更爲合適。

InnoDB行級鎖優化建議

  1. InnoDB存儲引擎因爲實現了行級鎖,雖然在鎖定機制的實現方面所帶來的性能損耗可能比表級鎖定會更高一些,可是在總體併發處理能力方面要遠遠因爲MyISAM的表級鎖定。
  2. 當系統併發量較高的時候,Innodb的總體性能和MyISAM相比就會有比較明顯的優點了。
  3. 要想合理的利用InnoDB的行級鎖,作到揚長避短,咱們必須作好如下工做高併發

    (1)儘量讓全部數據檢索都經過索引來完成,從而避免Innodb由於沒法經過索引健加鎖而升級爲表鎖
    (2)合理涉及索引,讓InnoDB在索引鍵上面加鎖儘量準確,儘量的縮小鎖定範圍,避免形成沒必要要的鎖定而影響query的執行
    (3)儘量減小基於範圍的數據檢索過濾條件,避免由於間隙鎖帶來的負面影響而鎖定不應鎖定的記錄
    (4)儘可能控制事務的大小,減小鎖定的資源量和鎖定時間長度
    (5)在業務環境容許的狀況下,儘可能使用較低級別的事務隔離,以減小mysql由於事務隔離級別所帶來的附加成本
  4. 因爲InnoDB的行級鎖定和事務性,因此確定會產生死鎖,建議:

    (1)相似業務模塊中,儘量按照相同的訪問順序來訪問,防止產生死鎖
    (2)在同一事務中,儘量作到一次鎖定所須要的全部資源,減小死鎖產生機率
    (3)對於很是容易產生死鎖的業務部分,能夠嘗試使用升級鎖定顆粒度,經過表級鎖定來減小死鎖產生的機率

系統鎖定爭用狀況查詢

  1. 對於兩種鎖定級別,MySQL內部有兩組專門的狀態變量記錄系統內部鎖資源爭用狀況,咱們先看看MySQL實現的表級鎖定的爭用狀態變量:
  2. 這裏有兩個狀態變量記錄MySQL內部表級鎖定的狀況,兩個變量說明以下:

    (1)Table_locks_immediate:產生表級鎖定的次數; 
    (2)Table_locks_waited:出現表級鎖定爭用而發生等待的次數;
  3. 兩個狀態值都是從系統啓動後開始記錄,沒出現一次對應的事件則數量加 1。若是這裏的Table_locks_waited狀態值比較高,那麼說明系統中表級鎖定爭用現象比較嚴重,就須要進一步分析爲何會有較多的鎖定資源爭用了。
  4. 對於Innodb所使用的行級鎖定,系統中是經過另一組更爲詳細的狀態變量來記錄的,以下:

    mysql> show status like 'innodb_row_lock%';

    圖片描述

  5. Innodb的行級鎖定狀態變量不只記錄了鎖定等待次數,還記錄了鎖定總時長,每次平均時長,以及最大時長,此外還有一個非累積狀態量顯示了當前正在等待鎖定的等待數量。對各個狀態量的說明以下:

    (1)Innodb_row_lock_current_waits:當前正在等待鎖定的數量;
    (2)Innodb_row_lock_time:從系統啓動到如今鎖定總時間長度;
    (3)Innodb_row_lock_time_avg:每次等待所花平均時間; 
    (4)Innodb_row_lock_time_max:從系統啓動到如今等待最常的一次所花的時間;
    (5)Innodb_row_lock_waits:系統啓動後到如今總共等待的次數;
  6. 對於這 5 個狀態變量,比較重要的主要是 Innodb_row_lock_time_avg(等待平均時長),Innodb_row_lock_waits(等待總次數)以及Innodb_row_lock_time(等待總時長)這三項。尤爲是當等待次數很高,並且每次等待時長也不小的時候,咱們就須要分析系統中爲何會有如此多的等待,而後根據分析結果着手指定優化計劃。
  7. 此外,Innodb出了提供這五個系統狀態變量以外,還提供的其餘更爲豐富的即時狀態信息供咱們分析使用。能夠經過以下方法查看:

    (1)經過建立Innodb Monitor表來打開Innodb的monitor功能:
         mysql> create table innodb_monitor(a int) engine=innodb;
    
         Query OK, 0 rows affected (0.07 sec) 
    (2)而後經過使用「SHOW INNODB STATUS」查看細節信息(因爲輸出內容太多就不在此記錄了); 可能會有讀者朋友問爲何要先建立一個叫innodb_monitor的表呢?由於建立該表實際上就是告訴Innodb咱們開始要監控他的細節狀態了,而後 Innodb就會將比較詳細的事務以及鎖定信息記錄進入MySQL的error log中,以便咱們後面作進一步分析使用。

參考連接

https://www.cnblogs.com/jesse...

相關文章
相關標籤/搜索