深入淺出Mysql - 優化篇(鎖)

深入淺出Mysql - 優化篇(鎖)

鎖是計算機協調多個進程或線程併發訪問某一資源的機制。在數據庫中,除傳統的計算資源(如CPU、RAM、I/O等)的爭用以外,數據也是一種供許多用戶共享的資源。如何保證數據併發訪問的一致性、有效性是所有數據庫必須解決的一個問題,鎖衝突也是影響數據庫併發訪問性能的一個重要因素。從這個角度來說,鎖對數據庫而言顯得尤其重要,也更加複雜。

Mysql鎖概述

相對其他數據庫而言,MySQL的鎖機制比較簡單,其最顯著的特點是不同的存儲引擎支持不同的鎖機制。比如,MylSAM和MEMORY存儲引擎採用的是表級鎖(table-level locking);BDB存儲弓障採用的是頁面鎖(page-level locking),但也支持表級鎖;InnoDB存儲引擎既支持行級鎖(row-level locking),也支持表級鎖,但默認情況下是採用行級鎖。

  • 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,並煩最低。
  • 行級鎖:開帰大,加鎖慢;會出現死鎖;鎖定牲度最小,發生鎖彳突的概率最低併發度也最高。
  • 頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定牲度界於表鎖和行鎖之間,併發度一般。

Myisam 表鎖

MylSAM存儲引擎只支持表鎖,這也是MySQL開始幾個版本中唯一支持的鎖類型。隨着應用對事務完整性和併發性要求的不斷提高,MySQL纔開始開發基於事務的存儲引擎,後來?慢慢出現了支持頁鎖的BDB存儲引擎和支持行鎖的InnoDB存儲引撃。但是MylSAM的表鎖依然是使用最爲廣泛的鎖類型。本節將詳細介紹MylSAM表鎖的使用。

查詢表級鎖的爭用情況
mysql> show status like 'table%';
+-----------------------+-----------+
| Variable_name         | Value     |
+-----------------------+-----------+
| Table_locks_immediate | 268670717 |
| Table_locks_waited    | 16        |
+-----------------------+-----------+
2 rows in set (0.03 sec)

如果Table_locks_waited的值比較高,則說明存在着較嚴重的表級鎖爭用情況。

Mysql表級鎖的鎖模式

MySQL的表級鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨佔寫鎖(Table Write Lock )。

在這裏插入圖片描述

可見,對MylSAM表的讀操作,不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一?表的寫請求;對MylSAM表的寫操作,則會阻塞其他用戶對同一表的讀和寫操作;MylSAM表的讀操作與寫操作之間,以及寫操作之間是串行的!當一個線程獲得對一個表的寫鎖後,只有持有鎖的線程可以對錶進行更新操作。其他線程的讀、?寫操作都會等待,直到鎖被釋放爲止。

在這裏插入圖片描述

如何加表鎖

MylSAM在執行査詢語句(SELECT)前,會自動給涉及的所有表加讀鎖,在執行更新操
作(UPDATE、DELETE. INSERT等)前,會自動給涉及的表加寫鎖,這個過程並不需要用戶
幹預,因此,用戶一般不需要直接用LOCK TABLE命令給MylSAM表顯式加鎖。在本書的示
例中,顯式加鎖基本上都是爲了方便說明問題,並非必須如此。

給MylSAM表顯式加鎖,一般是爲了在一定程度模擬事務操作,實現對某一時間點多個?表的一致性讀取。例如,有一個訂單表orders,其中記錄有各訂單的總金額total,同時還有一個訂單明細表order_detaU,其中記錄有各訂單每一產品的金額W、計subtotal,假設我們需要檢?査這兩個表的金額各計是否相符,可能就需要執行如下兩條SQL語句。

lock tables fa_order read local ;
select sum(actual_amount) form fa_order;
unlock tables;

上面的例子在LOCK TABLES時加了 "local 選項,其作用就是在滿足MylSAM表
併發插入條件的精況下,允許其他用戶在表尾併發插入記錄,有關MylSAM表的併發插入問
題,在後面的章節中還會進一步介紹。

在用LOCKTABLES給表顯式加表鎖時,必須同時取得所有涉及表的鎖,並且MySQL不支持鎖升級.也就是說,在執行LOCK TABLES後,只能訪問威式加鎖的這些表,不能訪問未加鎖的表;同時,如果加的是讀鎖,那麼只能執行查詢操作,而不能執行更新操作.在白動加鎖的情況下也是如此,MylSAM總是一次獲得SQL語句所需要的全部鎖.這也正是MylSAM表不會出現死鎖(DeadlockFree)的原因。

一個session使用LOCK TABLE命令給表film.text加了讀鎖,這個session可以査詢鎖定表中的記錄,但更新或訪問其他表都會提示錯誤;高時,另外一個session可以査詢表中的記錄,但更新就會出現鎖等待。

併發插入

上文提到過MylSAM表的讀和寫是申行的,但這是就總體而言的。在一定條件下,MylSAM表也支持査詢和插入操作的併發進行。

MylSAM存儲引擎有一個系統變量concurrent.insert,專門用以控制其併發插入的行爲,其值分別可以爲0、1或2。

  • 當concurrent_insert設置爲0時,不允許併發插入。

  • 當concurrentjnsert設置爲1時,如果MylSAM表中沒有空洞,MylSAM允許在一個進程讀表的同時,另一個進程從表尾插入記錄.這也是MySQL的默認設置。

  • 當concurrentjnsert設置爲2時,無論MylSAM表中有沒有空洞,都允許在表尾併發插入記錄。

session_l獲得了一個表的READ LOCAL鎖,該線程可以對錶進行査詢操作,但不能對錶進行更新操屈;其他的線程(session_1),雖然不能對錶進行刪除和更新操作,但卻可以對該表進行併發插入操作,這裏假設該表串間不存在空洞。

可以利用MylSAM存儲引擎的併發插入特性來解決應用中對同一表査詢和插入的鎖爭用。例如,將concurrent_insert系統變量設爲2,總是允許併發插入;同時,通過定期在系統空閒時段執行OPTIMIZE TABLE語句來整理空間碎片,收回因刪除記錄而產生的中間空洞。

Myisam的鎖調度

前面講過,MylSAM存儲引撃的讀鎖和寫鎖是互斥的,讀寫操作是串行的。那麼,一個進程請求某個MylSAM表的讀鎖,同時另一個進程也請求同一表的寫鎖,MySQL如何處理呢?答案是寫進程先獲得鎖。不僅如此,即使讀請求先到鎖等待隊列,寫請求後到,寫鎖也會插到讀鎖請求之前!這是因爲MySQL認爲寫請求_般比讀請求要重要。這也正是MylSAM表不太適合於有大量更新操作和査詢操作應用的原因,因爲,大量的更新操作會造成査詢操作很難獲得讀鎖,從而可能永遠阻塞。

InnoDB鎖問題

InnoDB與MylSAM的最大不同有兩點:一是支持事務(TRANSACTION);二是採用了行級鎖。

事務及其ACID屬性:

  • 原子性(Atomicity):亨務是一個原子操作單元,其對數據的修改,要麼全都執行,要麼全都不執行。

  • 一致性(Consistent):在亨務開始和完成時,數據都必須保持一致狀態.這意味着所有相關的數據規則都必須應用於亨務的修改,以保持數據的完整性;事務結束時,所有的內部數據結構(如B樹索引或雙向鏈表)也都必須是正確的。

  • 隔髙性(Isolation):數據庫系統提供一定的隔髙機制,保證亨務在不受外部併發操作?影響的“獨立”環境執行.這意味着亨務處理過程中的中間狀態對外部是不可見的,反之亦然。

  • 持久性(Durable):亨務完成之後,它對於數據的修改是永久性的,即使出現系統故障也能夠保持。

併發處理帶來的問題

更新丟失(Lost Update):當兩個或多個事務選擇同一行,然後基於最初選定的值更新該行時,由於每個亨務都不知道其他事務的存在,就會發生丟失更新問題一最後的更新夜蓋了由其他事務所做的更新。例如,兩個編輯人員製作了同一文檔的電子副本每個編輯人員獨立地更改其副本,然後保存更改後的副本,這樣就覆葢了原始文檔.最後保存其更改副本的編輯人員覆蓋另一個編輯人員所做的更改.如果在一個編輯人員完成並提交亨務之前,另一個編輯人員不能訪問同一文件,則可避免此問題。

髒讀(Dirty Reads): 一個亨務正在對一條記錄做修改,在這個亨務完成並提交前,這條記錄的數據就處於不一致狀態;這時,另一個事務也來讀取同一條記錄,如果不加控制,第二個亨務讀取了這些“髒”數據,並據此做進一步的處理,就會產生未提交的數據依賴關係.這種現象被形象地叫做“髒讀”。

不可重複讀(Non-RepeatableReads): 一個亨務在讀取某些數據後的某個時間,再次讀取以前讀過的數據,卻發現其讀出的數據巳經發生了改變或某些記錄已經被刪除了!這種現象就叫做“不可重複讀。

幻讀(Phantom Reads): 一個事務按相同的查詢條件重新讀取以紡檢索過的數據,卻?發現其他亨務插入了滿足其查詢條件的新數據,這種現象就稱爲“幻讀”。

事務的隔離級別

在上面講到的併發事務處理帶來的問題中,“更新丟失”通常是應該完全避免的。但防止?更新丟失,並不能單靠數據庫事務控制器來解決,需要應用程序對要更新的數據加必要的鎖來解決,因此,防止更新丟失應該是應用的責任。

“髒讀”、“不可重複讀”和“幻讀”,其實都是數據庫讀一致性問題,必須由數據庫提供一?定的事務隔髙機制來解決。數據庫實現事務隔離的方式,基本上可分爲以下兩種。

  • 一種是在讀取數據前,對其加鎖,阻止其他事務對敷據進行修改。

  • 另一種是不用加任何鎖,通過一定機制生成一個數據請求時間點的一致性數據快照(Snapshot),並用這個快照來提供一定級別(語句級或亨務級)的一致性讀取.從用戶的角度來看,好像是數據庫可以提供同一數據的多個版本,因此,這種技術叫做數據多版本井發控制?(MultiVbrsion Concurrency Control,簡稱MVCC或MCC),也經常稱爲多版本數據庫。

數據庫的事務隔離越嚴格,併發副作用越小,但付出的代價也就越大,因爲専務隔離實質?上就是使事務在一定程度上“串行化”進行,這顯然與“併發”是矛盾的。同時,不同的應用對讀一致性和事務隔離程度的要求也是不同的,比如許多應用對“不可重複讀”和“幻讀”並?不敏感,可能更關心數據併發訪問的能力。

獲取Innodb 行鎖爭用情況

mysql> show status like 'innodb_row_%';
+-------------------------------+--------------+
| 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_rows_deleted           | 5094969      |
| Innodb_rows_inserted          | 81058340     |
| Innodb_rows_read              | 109973483305 |
| Innodb_rows_updated           | 2721990      |
+-------------------------------+--------------+
9 rows in set (1.46 sec)

如果發現鎖爭用比較嚴重,如 InnoDB_row_lock_waits 和 InnoDB_row_lock_time_avg 的值比較高,可以通過査詢infbrmatig_scheml數窟庫皐相關的表來査專鎖福況,竜者通過設置?InnoDBMonitors來進一步觀察發生反衝突的表、數據行等,並分析鎖爭用的原因。

Innodb的行鎖模式及加鎖方式

  • 共享鎖(S)允許一個亨務去讀一行,阻止其他事務獲得相同數據集的排他鎖。

  • 排他鎖(X):允許獲得排他鎖的亨務更斷數據,阻止其他亨務取得相同數據集的共享讀鎖和排他寫鎖。

另外,爲了允許行鎖和表鎖共在,實現多粒度鎖機制,innoDB還有兩種內部使用的意向鎖(Intentioii Locks),這兩種意向鎖都是表鎖。

  • 意向共享鎖(IS):亨務打算給數據行加行共享核,亨務在給一個數據行加共享鎖前?必須先取得該表的IS鎖。

  • 意向排他鎖(IX):亨務打算給數據行加行排他鎖,亨務在給一個數據行加排他鎖前?必須先取得該表的IX鎖。

如果一個事務請求的鎖模式與當前的鎖兼容,innoDB就將請求的鎖授予該事務;反之,?如果兩者不兼容,該事務就要等待鎖釋放。意向鎖是InnoDB自動加的,不需用戶干預。對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及數據集加排他鎖(X);對於普通SELECT語句,hmoDB不會加任何鎖;事務可以通過以下語句顯示給記錄集加共享鎖或排他鎖。

共享鎖(S): SELECT * FROM table_name WHERE... LOCK IN SHARE MODE
用咐鎖(X): SELECT * FROM tablc_namc WHERE... FOR UPDATE.

用SELECT… IN SHARE MODE獲得共享鎖,主要用在需要數據依存關係時來確認某行記錄是否存在,並確保沒有人對這個記錄進行UPDATE或者DELETE操作。但是如果當前事務也需要對該記錄進行更新操作,則很有可能造成死鎖,對於鎖定行記錄後需要進行更新操作的赫,應該穌 SELECT… FOR UPDATE方式獲得排他鎖。

Innodb行鎖實現方式

InnoDB行鎖是通過給索引上的索引項加鎖來實現的,如果沒有索引,InnoDB將通過隱蒙?的聚簇索引來對記錄加鎖。

  • Record lock:對索引項加傾。
  • Gap lock:對索引項之間的“間隙第一條記錄前的 響隙”爲I後一條記錄後的間BT加鎖。
  • Next-keylock:前兩種的館合,對記錄及其前面的間隙加做。

ImoDB這種行鎖實現特點意味着:如果不通過索引條件檢索數據,那麼IimoDB將對錶中?的所有記錄加鎖,實際效果跟表鎖一樣!

在實際應用中,要特別注意ImoDB行鎖的這一特性,否則可能導致大量的鎖衝突,從而影響併發性能。

在不通過索引條件查詢時,InnoDB會鎖定表中的所有記錄。

看起來sessional只給一行加了排他鎖,但session^在請求其他行的排他鎖時,卻出現了鎖等待!原因就是星沒有索引的情況下,hmoDB會嗣有記錄都加鎖。

由於MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然是訪問不?同行的記錄,但是如果是使用相同的索引鍵,是會出現鎖衝突的。

在這裏插入圖片描述

當表有多個索引的時候,不同的事務可以使用不同的索引鎖定不同的行,不論是使用主縫索引、唯一索引或普通索引,IimoDB都會使用行鎖來對數據加鎖。

在這裏插入圖片描述

Next-Key 鎖

當我們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,Innodb會給符合條件的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫做“間隙(GAP)", InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的Next-Key鎖。

InnoDB使用Next-Key鎖的目的,一方面是爲了防止幻讀,以滿足相關隔離級別的要求,?對於上面的例子,要是不使用間隙鎖,如果其他事務插入了 empid大於100的任何記錄,那麼?本事務如果再次執行上述語句,就會發生幻讀;另一方面,是爲了滿足其恢復和複製的需要。

很顯然,在使用範圍條件檢索並鎖定記錄時,InnoDB 這種加鎖機制會阻塞符合條件範圍?內鍵值的併發插入,這往往會造成嚴重的鎖等待。因此,在實際應用開發中,尤其是併發插入比較多的應用,我們要儘量優化業務邏輯,儘量使用相等條件來訪問更新數據,避免使用範圍條件。

還要特別說明的是,InnoDB除了通過範圍條件加鎖時使用Next-Key鎖外,如果使用相等?條件請求給一個不存在的記錄加鎖,IimoDB也會使用Next-Key鎖!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章