1. 程式人生 > >【MySQL】事務,隔離級別,鎖,併發性

【MySQL】事務,隔離級別,鎖,併發性

資料庫語言型別

  1. 資料查詢語言(DQL)select
  2. 資料操作語言(DML) insert,update,delete主要用來對資料庫的資料進行操作
  3. 資料庫定義語言(DDL)create,alter,drop用在定義或改變表的結果,資料型別,表之間的連結和約束(操作是隱性提交的,不能rollback)
  4. 資料庫控制語言(DCL)grant,deny,revoke設定或更改資料庫使用者或角色許可權的語句

事務

概念: 滿足ACID特性的一組DML操作,可以通過commit提交一個事務,也可以使用rollback進行回滾操作。

  1. 原子性(Atomicity)事務被視為不可分割的最小單元,所有操作要麼全部提交成功,要麼全部失敗回滾。
  2. 一致性(Consistency)事務在執行前後都保持一致性狀態,在一致性狀態下,所有事務對一個數據的讀取結果都是相同的;
  3. 隔離性(Isolation)事務所做的修改在最終提交以前,對其他事務是不可見的;
  4. 永續性(Durability)事務一旦提交則其所做的修改是永久的,即使系統發生崩潰,事務執行的結果也不會丟失。

理解:

  1. 只有滿足一致性,事務的執行結果才是正確的;
  2. 無併發的情況下,事務序列執行,隔離性一定能滿足。此時只要滿足原子性,就能滿足一致性;
  3. 在併發情況下,多個事務並行執行,事務不僅要滿足原子性,還要滿足隔離性,才能滿足一致性;
  4. 事務滿足永續性是為了能應對資料庫崩潰的情況。事務滿足永續性是為了能應對資料庫崩潰的情況。 在這裡插入圖片描述

mysql預設採用自動提交模式,也就是說如果不顯示的使用start transaction語句來開啟一個事務,那麼每個查詢都會被當做一個事務自動提交。

併發一致性問題

  1. 丟失修改:事務AB,AB均寫入,A寫的資料被B覆蓋了。
  2. 讀髒資料:事務A修改了一個數據,事務B讀了資料,事務A撤銷了修改,那麼B讀取的資料是髒資料;一個事務提交之前,人和其他事務不可讀取器修改過的值,則可以避免此問題,
  3. 不可重複讀:一個事務內,多次讀同一資料,結果不一樣。(重點在於修改);如果只有在修改事務完全提交後才可以讀取資料,則可以賓冕不可重複讀。
  4. 幻讀:事務A讀取某一範圍的資料,事務B插入了新的資料,事務A再次讀取這個範圍的資料,此時讀取的結果和第一次結果不同。(重點在於增加或刪除);在操作事務完成處理資料之前,任何其他事務都不可以新增新資料,則可以避免。

封鎖

封鎖粒度

  • 行級鎖和表級鎖
  • 應該儘量只鎖定需要修改的部分資料,而不是所有資源,鎖定的資料量越少,發生鎖爭用的可能就越小,併發程度越高;
  • 加鎖需要消耗資源,鎖的各種操作會增加系統開銷,因此封鎖的粒度越小,系統開銷越大,需要在開銷和併發程度之間做一個權衡。

封鎖型別

  • 共享鎖:也叫S鎖,是一種讀鎖,當一個事務獲得了一條資料的共享鎖,其他事務也可以獲得該共享鎖,但不能獲得排他鎖,表示其他事務可讀,但不可寫。
  • 排他鎖:也叫X鎖,是一種寫鎖,當一個事務對臨界區加上排他鎖,其他事務不能獲得該臨界區的任何鎖(包括共享過和排他鎖)表示只能一個人去處理資料,其他人不能讀也不能寫。
  • 意向鎖:使用意向鎖可以更容易地支援多粒度封鎖;存在行級鎖和表級鎖的情況下,事務T想對錶A加排他鎖,需要先檢測是否有其他事務對錶A或者表A的任意一行加了鎖,那麼需要對錶A的每一行都檢測一次,非常耗時;意向鎖在原來的X/S鎖上引入了IX/IS鎖,都是表鎖,用來表示一個事務想要在表中的某個資料行上加X鎖或S鎖,有以下兩個規定:
    1. 一個事務在獲得某個資料行物件的S鎖之前,必須先獲得表的IS鎖或者更強的鎖;
    2. 一個事務在獲得某個資料行物件的X鎖之前,必須先獲得表的IX鎖

引入意向鎖,事務T想要對錶A加X鎖,只需要先檢測是否有其他事務對錶A加了X/IX/S/IS鎖,如果加了就表示有其他的事務正在使用這個表或者表中某一行的鎖,因此T加X鎖失敗。 1. 任意IS/IX鎖之間是相容的,因為他們只是表示想要對錶加鎖,而不是真正加鎖; 2. S鎖子與S鎖和IS鎖相容,也就是說事務T想要對資料加S鎖,其他事務可以獲得對錶或者表中的行的S鎖。

  • 樂觀鎖:樂觀鎖假設認為資料一般情況下不會造成衝突,所以只會對資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測,如果衝突了,則返回使用者錯誤的資訊,讓使用者決定如何去做。實現樂觀鎖的兩種方式:
    1. 使用版本號,為資料增加一個版本標識,讀資料時,將version一同讀出,資料每更新一次對version加一,當提交更新時,判斷資料表對應記錄的版本資訊與第一次讀取出來的version值比對,如果相等,則予以更新,否則,認為是過期資料;
    2. 使用時間戳:增加一個欄位,使用時間戳,更新提交時檢查當前資料庫中資料的時間戳和自己更新前取到的時間戳對比,如果一致則OK,否則就是版本衝突。使用時間戳:增加一個欄位,使用時間戳,更新提交時檢查當前資料庫中資料的時間戳和自己更新前取到的時間戳對比,如果一致則OK,否則就是版本衝突。
  • 悲觀鎖:指的是是對資料被外界(本系統的其他事務,來自外部系統的事務處理)修改持保守的態度。在整個資料處理中,將資料處於鎖定的狀態;悲觀鎖的實現,要依靠資料庫提供的鎖機制。 select status from t_items where id=1 for update,另一個事務會阻塞,如果沒有for update則不會阻塞
  • MySQL InnoDB預設Row-Level Lock,只有明確的指定主鍵或者索引,才會執行Row lock,負責將會執行Table Lock。
  • 共享鎖和排他鎖都屬於悲觀鎖

封鎖協議

三級封鎖協議

  1. 一級封鎖協議:事務T要修改資料A時,必須加X鎖,知道事務T結束才釋放鎖; 可以解決丟失修改,因為不能同時有兩個事務對同一個資料進行修改,那麼事務的修改不會被覆蓋;
  2. 二級封鎖協議:在一級的基礎上,要求讀取資料A時必須加S鎖,讀完馬上釋放S鎖; 可以解決髒讀資料,因為如果一個事務在對資料A進行修改,根據一級等所協議,會加X鎖,那麼就不能再加S鎖了,也就是不會讀入資料;
  3. 三級封鎖協議:在二級的基礎上,要求讀取資料A時必須加S鎖,直到事務結束才能釋放S鎖; 可以解決不可重複讀的問題,因為讀A時,其他事務不能對A加X鎖,從而避免了在讀的期間資料發生改變。

兩段鎖協議:是指所有事務必須分為兩個階段對資料項加鎖和解鎖;

  1. 對任何資料進行讀寫之前,要申請並獲得對該資料的封鎖;
  2. 每個事務中,所有的封鎖請求先於所有的解鎖請求。
  • 可序列化排程是指,通過併發控制,使得併發執行的事務結果與某個序列執行的事務結果相同。事務遵循兩段鎖協議是保證可序列化排程的充分條件,也就是說:所有事務均遵守兩段鎖協議,則這些事務的所有交叉排程都是可序列化的;一個可序列化的併發排程的所有事務並不一定都符合兩段鎖協議。
  • 遵循兩段鎖協議可能發生死鎖。
  • MySQL的InnoDB儲存引擎採用兩段鎖協議,會根據隔離級別在需要的時候自動加鎖,並且所有的鎖都是在同一時刻被釋放,這被稱為隱式鎖定。
  • select … lock in share mode; select … for update為顯示鎖定。

一次封鎖法:要求事務必須一次性將所有要使用的資料全部加鎖,否則就不能繼續執行,因此一次封鎖法遵守兩端封鎖協議,但兩段封鎖協議並不要求事務必須一次性將所有要使用的資料全部加鎖。這是遵守兩端鎖協議仍可能發生死鎖的原因所在。

事務的隔離級別

  1. 讀未提交:沒有解決任何問題,在讀取時不會加鎖,在更新資料時,對其加行級共享鎖(其他事務不能更改,但可以讀取,導致髒讀),事務結束時釋放。
  2. 讀已提交:讀取的資料是已經提交成功的資料,解決了髒讀的問題;給寫資料加行級排他鎖(寫的過程是無法讀取的,直到事務處理完畢才釋放排他鎖)讀的資料加行級共享鎖,讀的時候也是無法寫的,但是一旦讀完該行就釋放共享鎖;(事務A負責讀,B負責寫,A讀完資料後釋放共享鎖,B更新資料,事務還未結束,A在讀,兩次資料不一樣。)
  3. 可重複讀:可以重複的讀取資料,解決了不可重複讀的問題。寫的資料加行級排他鎖,事務結束釋放,讀的資料加行級共享鎖,事務結束後釋放。(事務A負責讀,B負責寫,A讀完資料後等事務結束才釋放共享鎖,B更新資料,直到事務結束,A再讀,兩次讀到的資料均為A第一次讀到的資料,解決了不可重複讀。)(事務A負責讀,只為讀取的資料加行級共享鎖,B在A讀的過程中向表單中插入資料,A由於處理判斷到新的資料,產生幻讀。)
  4. 可序列化:可以解決問題。事務一個接著一個執行,代價花費最高,效能最低。(事務讀資料則加表級共享鎖,事務寫資料則加表級排他鎖。)

多版本併發控制(MVCC)

是MySQL的InnoDB儲存引擎實現隔離級別的一種具體方式。用於實現提交度和可重複讀這兩種隔離解別。(未提交讀隔離解別總是讀最新的資料行,無需使用MVCC,可序列化隔離級別需要對所有讀取的行都加鎖,單純使用MVCC無法實現)。

版本號:

  • 系統版本號:一個遞增的數字,每開始一個事務,系統版本號就會自動遞增;
  • 事務版本號:事務開始時的系統版本號。

隱藏的列:MVCC在每行記錄後面都儲存著兩個隱藏的列,用來儲存兩個版本號

  • 建立版本號:指示建立一個數據行的快照時的系統版本號;
  • 刪除版本號:如果該快照的刪除版本號大於當前事務版本號表示該快照有效,否則表示該快照已經被刪除。

MVCC使用到的快照儲存在Undo日誌中,該日誌通過回滾指標班一個數據行的所有快照連線起來;

實現過程

當開始一個新的事務時,該事務的版本號肯定會大於當前所有資料行快照的建立版本號;

  1. select:多個事務必須讀取到同一個資料行的快照,並且這個快照是距離現在最近的一個有效快照。但是也有例外,如果一個事務正在修改該資料行,那麼它可以讀取該事務本身所做的修改,而不用和其他事務的讀取結果一致。 一個沒有對資料行做修改的事務T,它所讀取的資料行快照的建立版本號必須小於T的版本號,因為如果大於等於則表示該資料行快照時其他事務的最新修改,因此不能去讀取它;除此之外,T索要讀取的資料行快照的刪除版本號必須大於等於T的版本號,因為如果小於等於則表示該資料行快照是已經被刪除的,不應該去讀取它。
  2. insert:將當前系統版本號作為資料行快照的建立版本號;
  3. delete:將當前系統版本號作為資料行快照的刪除版本號;
  4. update:將當前系統版本號作為更新前的資料行快照的刪除版本號,並將當前系統版本號作為更新後的資料行快照的建立版本號。可以理解為先執行delete 後執行insert。

快照讀與當前讀

  1. 快照讀:使用MVCC讀取的是快照中的資料,可以減少加鎖帶來的開銷;select * from table…;
    • 快照讀:一致非鎖定讀,select的時候會生成一個快照;
    • 生成快照的時機:事務中第一次呼叫select語句的時候才會生成快照,在此之前事務中執行的uodate, insert, delete操作都不會生成快照;
    • READ COMMITED隔離解別下,每次讀取都會重新生成一個快照,每次快照都是最新的,因此事務中每次select也可以看到其他已經提交事務所做的更改;REPEATED READ隔離級別下,快照會在事務中第一次select語句執行時生成,只有本事務中對資料進行更改才會更新快照,因此,只有第一次select之前其他已提交事務所做的更改可以看到,但是如果已經執行了seletc,那麼其他事務commit資料,select是看不到的。
  2. 當前讀:讀取的是最新的資料,需要加鎖;
select * from table where ? lock in share mode;
select * from table where ? for update;
insert;
update;
delete;

InnoDB如何解決幻讀問題?

MVCC、next-key lock、間隙鎖

  • Record Locks(記錄鎖)鎖定一個記錄上的索引,而不是記錄本身,如果沒有設定索引,InnoDB會自動在主鍵上建立隱藏的聚簇索引,因此Record Locks依然可以使用;
  • Gap Locks(間隙鎖)鎖定索引之間的間隙,但是不包含索引本身。例如當一個事務執行以下語句,其他事務就不能再t.c中插入15,select c from t where c between 10 and 20 for update;
  • Next-key Locks是記錄鎖和間隙鎖的結合,不僅鎖定一個記錄上的索引,也鎖定索引之間的間隙。例如一個索引包含以下值:10,11,13,20,那麼就需要鎖定以下區間(負無窮, 10], (10, 11], (11, 13], (13, 20], (20, 正無窮)

InnoDB預設的隔離級別是RR(可重複讀),不能解決幻讀;MVCC+next-key lock可以解決幻讀的問題。(快照讀即一般的select靠MVCC解決幻讀)(當前讀select … for update, select … lock in share mode; insert …; update …; delete …依賴於間隙鎖解決)

  1. 間隙鎖的主要作用是為了防止出現幻讀,會把鎖定的方位擴大。控制間隙鎖的引數是innodb_locks_unsafe_for_binlog 這個引數的預設值是off,也就是啟用間隙鎖。
  2. 行鎖(record lock)和間隙鎖組合起來叫做next-key lock,鎖定一個範圍,並且鎖定記錄本身,主要目的是解決幻讀的問題。行鎖(record lock)和間隙鎖組合起來叫做next-key lock,鎖定一個範圍,並且鎖定記錄本身,主要目的是解決幻讀的問題。

1. 間隙鎖防止間隙內有數新資料被插入;防止已存在的資料,更新成間隙內的資料; 2. InnoDB自動使用間隙鎖的條件:必須在RR級別下,檢索條件必須有索引。

接下來舉一個例子: 在這裡插入圖片描述

mysql> show create table test\G;
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `id` int(11) NOT NULL,
  `number` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `number` (`number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

案例:

session 1:
start transaction ;
select * from test where number=4 for update;

session 2:
start transaction;
insert into test value(0,2);#(執行成功)
insert into test value(2,2);#(阻塞)
insert into test value(2,4);#(阻塞)
insert into test value(2,2);#(阻塞)
insert into test value(4,4);#(阻塞)
insert into test value(4,5);#(阻塞)
insert into test value(7,5);#(執行成功)
insert into test value(9,5);#(執行成功)
insert into test value(11,5);#(執行成功)