1. 程式人生 > 遊戲 >Coser頂流Enako談遊戲直播異聞 或有靈體自動播

Coser頂流Enako談遊戲直播異聞 或有靈體自動播

系列文章目錄和關於我

參考書籍《mysql是怎樣執行的》
推薦極客時間《mysql實戰45講》

一丶為什麼資料庫需要鎖

資料庫鎖設計的初衷是處理併發問題。作為多使用者共享 的資源,當出現併發訪問的時候,資料庫需要合理地控制資源的訪問規則。而鎖就是用來實 現這些訪問規則的重要資料結構。

根據加鎖的範圍,MySQL 裡面的鎖大致可以分成全域性鎖表級鎖行鎖三類

二丶全域性鎖&全庫邏輯備份

全域性鎖就是對整個資料庫例項加鎖。全域性鎖的典型使用場景是,做全庫邏輯備份,全庫邏輯備份有以下幾種方式:

1.Flush tables with read lock (FTWRL)

Flush tables with read lock (FTWRL)

可以讓整個庫處於只讀狀態的時候,使用這個命令,之後其他執行緒的資料更新語句(資料的增刪改)、資料定義語句(包括建表、修改表結構等)和更新類事務的提交語句講被阻塞。之所以備份需要加全域性鎖是為了保證資料的一致性,

2.mysqldump –single-transaction

使用此命令會先啟動一個事務拿到一致性檢視。由於 MVCC 的支援, 這個過程中其他執行緒可以進行正常操作,但是使用mysqldump –single-transaction的前提是需要支援事務,如果存在MyISAM引擎的表,並不能保證一致性。

3.set global readonly=true

set global readonly=true

也可以保證全庫只讀,主要有兩個原因:

  • 在有些系統中,readonly 的值會被用來做其他邏輯,比如用來判斷一個庫是主庫還是備庫。因此,修改 global 變數的方式影響面更大。
  • 在異常處理機制上有差異。如果執行 FTWRL 命令之後由於客戶端發生異常斷開, 那麼 MySQL 會自動釋放這個全域性鎖,整個庫回到可以正常更新的狀態。而將整個庫設定為 readonly 之後,如果客戶端發生異常,則資料庫就會一直保持 readonly 狀態,這樣會導致整個庫長時間處於不可寫狀態,風險較高。

三丶鎖解決併發事務帶來的問題

1.鎖解決髒寫

多個未提交事務修改相繼對同一條記錄進行改動的時候,需要進行排隊,排隊過程其實就是通過為記錄加鎖實現的。當一個事務想修改記錄的時候,首先需要看下有沒有於記錄相關聯的鎖結構,如果沒有,那麼會在記憶體中生成與之對應的鎖結構

可能多個事務同時修改同一記錄,會產生多個鎖結構,其中只有一個事務可以獲取到鎖,其is_waiting為false,其他事務is_waiting為true,當獲取鎖的事務結束後,會喚醒其他等待的事務

2.髒讀,幻讀,不可重複讀如何避免

mysql innodb 在repeatable隔離級別下很大程度下避免了幻讀(後續詳細說到)

2.1 mvcc解決髒讀,幻讀,不可重複讀問題

Mysql InnoDB多版本併發控制MVCC 這篇部落格我們詳細說到了mvcc的實現的原理,簡單來說就是在事務執行時會生成read view,其包含四個部分

  • m_ids:在生成read view時,當前系統中活躍的讀寫事務id列表
  • min_trx_id:生成read view時,當前系統中活躍的讀寫事務中最小事務id,也就是m_ids中的最小值
  • max_trx_id:生成read view時,系統應該分配給下一個事務的事務id值
  • creator_trx_id:生成該read view的事務的事務id

查詢語句只可以查詢生成read view 時刻可以看到的資料,及事務id小於min_trx_id中的資料,寫操作則針對最新的資料。非當前讀的普通語句在讀資料的時候,生成read view的時機不同,在可重複讀的時候只會在第一次讀取(如果使用start transaction with consistent snapshot則是在事務啟動時)生成一個read view 後續不變,達到可重複讀的目的,對於讀已提交,則是每次讀取都會生成新的read view,從重新整理未提交事務集合,和min_trx_id,讀取到已經提交的資料。使用mvcc讓讀寫並不衝突,資料庫的併發能力更強。

2.2讀寫均加鎖

比如在銀行存款業務中,我們需要先讀取賬戶餘額,然後加上新增存款,然後進行寫回操作,整個流程中,我們不希望存在另外一個ATM進行存取的操作,讀寫都需要排隊,這時候就得使用鎖。

上面我們說了,寫操作進行排隊可以解決髒寫,那麼髒讀,不可重複讀,幻讀是怎麼使用讀寫加鎖解決暱?

髒讀產生的原因是,當前事務讀取到了另外一個事務沒有提交的資料,那麼只需要另外一個事務對操作的記錄加鎖,當前事務無法獲取到鎖,自然就不會發生髒讀。

不可重複讀產生的原因是,當前事務先讀取了一條記錄,然後存在另外一個事務修改了此記錄的資料,那麼只要當前事務對記錄進行加鎖,自然後續的事務將無法修改,自然不會發生不可重複讀。

幻讀產生的原因是,當前事務根據條件查詢得到一批資料,然後後續事務新增了滿足這些條件的資料,導致再次查詢時發現多了一些資料,如同出現了幻覺。這裡加鎖則不是單對記錄加鎖,而是要鎖住一個範圍,讓其他事務無法插入資料,從而解決幻讀(後續會說到這種鎖)

很明顯讀寫都加鎖,併發能力不及mvcc

3.一致性讀

事務利用MVCC進行讀取操作稱為一致性讀(又稱一致性無鎖讀,快照讀),基本上所有的普通讀在可重複讀,讀提交隔離級別下,都是一致性讀。

4.鎖定讀

4.1共享鎖&獨佔鎖

共享鎖:簡稱S鎖,事務要讀取一條記錄的時候需要先獲取到記錄的共享鎖

獨佔鎖:簡稱x鎖,當事務需要改動記錄的時候,需要先獲取記錄的獨佔鎖

共享鎖和共享鎖相容,獨佔鎖和獨佔鎖,獨佔鎖和共享鎖互斥

4.2鎖定讀的語句

select xxx lock in share mode可以對讀取的記錄加S鎖

select xxx for update可以對讀取的記錄加X鎖

4.3 寫操作

  • delete

    刪除記錄首先要在b+數中定位到這條記錄的位置,然後獲取x鎖,然後指向delete mark(標記記錄被刪除)

  • update

    如果未修改鍵值,並且修改前後資料的儲存空間大小不變,那麼現在b+樹上定位記錄,然後加x鎖。反之需要在b+樹上定位資料,然後把記錄徹底刪除嗎,然後再插入一條新的記錄,對新增的這條資料加x鎖

  • insert

    新插入記錄一般都是加隱式鎖(後面說)不需要在記憶體中生成對應的鎖結構。

三丶InnoDB表鎖

1.表級S,X鎖

innodb支援表級鎖,也支援行級鎖,表級鎖粒度相對更粗,佔用資源較少。使用表級鎖效果相當於為表中的所有記錄加鎖,所以效能比較差。

  • 表級S鎖,X鎖

    使用Lock Tables t Read,innodb儲存引擎會對錶t加共享鎖

    使用Lock tables t write,innodb儲存引擎會對錶t加獨佔鎖

類似於Java中的讀寫鎖,共享鎖和共享鎖不互斥,獨佔鎖和獨佔鎖,獨佔鎖和共享鎖互斥。

2.表級意向鎖

innodb儲存引擎中,當對錶中某些記錄加S鎖之前,會在表上加上一個IS鎖,同樣加X鎖之前會加表級IX鎖,這裡的I表示意向鎖,SX表示共享還是互斥,表級意向鎖存在的目的是後續對錶加S鎖,X鎖的時候,可以快速判斷表中是否存在加鎖的記錄,避免遍歷每一個記錄檢視是否被加鎖。

3.表級AUTO-INC鎖

mysql可以為某列執行Auto Increment自增,系統給自增列賦值的實現方式主要存在兩種

  • 使用AUTO-INC鎖,執行插入語句的時候加一個表級AUTO-INC鎖,然後為每條待插入的記錄中的自增列,進行遞增賦值,單個插入語句執行結束後釋放AUTO-INC鎖。這樣會導致其他事務的插入被阻塞。
  • 採用輕量級AUTO-INC鎖,在為插入列賦值結束後,就釋放輕量級鎖,而不是插入語句執行完後才釋放

四.MDL

一般情況下 對某一個表執行增刪改查的時候,都不會加表鎖,但是執行一些DDL修改表結構,刪除表時,其他事務對這表的增刪改查發生阻塞。這是由MDL鎖實現的,MDL鎖也分為讀鎖和寫鎖,在進行crud操作的時候,會加MDL讀鎖,進行DDL的時候會加MDL寫鎖。

我們需要注意MDL讀鎖寫鎖是互斥的

如圖四個不同的session先後依次執行語句,其中A,B都是獲取MDL讀鎖,互不阻塞,隨即C獲取MDL寫鎖,這時候C會被阻塞,這一阻塞不打緊,還會阻塞後續獲取MDL讀鎖的事務,造成整個表不可用。這啟發了我們,在做DDL的時候要解決長事務,事務不提交,就會一直佔著MDL鎖。在 MySQL 的 information_schema 庫的innodb_trx 表中,可以查到當前執行中的事務。如果要做 DDL 變更的表剛好有長事務在執行,要考慮先暫停 DDL,或者 kill 掉這個長事務。

五丶innodb 行鎖

1.Record Lock

官方名稱Lock_REC_NOT_GAP

記錄鎖有S鎖和X鎖,S型記錄鎖之間可以共享,X型記錄鎖和S型記錄鎖,X型記錄鎖互斥

2.GAP Lock

innodb的可重複讀級別,使用詞鎖解決幻讀問題,前面我們說過,其難點在於,加鎖的時候幻影記錄還未出現。官方使用Lock_GAP實現如下操作

此處的gap鎖可以反之其他事務在number為8記錄前面的間隙插入新的記錄,在區間(3,8)內無法進行插入操作,當另外一個事務要插入number為4的記錄時,首先需要定位到該條記錄的下一條記錄,也就是number為8的記錄,此時number為8的記錄具備gap鎖,所以將阻塞插入操作,直到gap鎖被釋放,其他事務才能進行插入。
gap鎖出現的目的,就是為了防止插入幻影記錄,如果對記錄上gap鎖,並不會限制其他事務對記錄加記錄鎖

innodb有兩個虛擬的記錄Infimum(虛擬最小),Supermun(虛擬最大)當我們想在(xx,正無窮)範圍鎖住幻影記錄時就可以對Supermun加gap鎖。

3.Next-Key Lock

Next-Key Lock = 記錄鎖 + gap鎖,既鎖住記錄,也鎖住記錄之前的間隙

4.Insert Intention Lock

插入意向鎖,表示事務想在某個間隙插入新的記錄,但是當前處於等待狀態。

比如事務A持有(4,8)範圍內的gap鎖,事務B和C,想插入(4,8)範圍內的記錄,就會在記憶體中生成事務B,C對應的插入意向鎖,當前事務A釋放gap鎖的時候,將喚醒事務B和C,事務B和C可以同時獲取插入意向鎖,然後進行插入。插入意向鎖並不會阻止對記錄繼續上鎖。

5.隱式鎖

為事務生成記憶體中的鎖結構並不是一個0成本的事情,為了節省這個成本,提出隱式鎖的概念。

當一個事務插入語一條記錄A,其他事務

  1. select xxx Lock in share mode讀取記錄A(獲取記錄A的S鎖),或者使用select xxx for update(獲取記錄A的X鎖)
  2. 立即修改記錄A(獲取x鎖)

對於聚簇索引來說,有一個隱藏列trx_id此列儲存著最後更改記錄的事務id,在當前事務A插入記錄後,便是儲存著當前事務A的id,其他事務B企圖獲取x鎖,s鎖的時候,就需要下先看一下,trx_id隱藏列對應的事務是否存活,如果不是那麼正常獲取,反之需要為當前事務A建立一個x鎖記憶體結構,並標記is_waiting為false,然後事務B將為自己建立一個鎖結構,is_waiting 為true然後事務B進入等待狀態

對於二級索引來說,其不具備隱藏列trx_id但是在二級索引頁面的page header中的page_maxt_trx_id屬性,記錄了改動頁面最大的事務id,如果其屬性值小於當前最小的活躍事務id,那麼說明對頁面的改動事務已經提交,否則需要定位到二級索引記錄,然後回表對聚簇索引進行上述聚簇索引的操作。

一個事務對新插入的記錄不需要顯示的加鎖,由於事務id的存在相當於加了一個隱式鎖,別的事務需要加S鎖或者X鎖的時候,先幫之前的事務生成鎖結構,然後為自己生成鎖結構,再進入阻塞狀態。隱式鎖起到了延遲加鎖的作用,也許別的事務不會獲取於隱式鎖衝突的鎖,這時候可以減少記憶體中生成鎖結構。