事務隔離級別

兩次轉賬

談談你對MVCC的理解 -_鏈表

轉賬後:A:6 B:12 總和:18

違反了一致性

事務併發遇到的不一致性問題:

w=write c=commit a=abort w1=事務T1寫 x,y初始值為0

  • 髒寫:一個事務修改了另一個未提交事務修改過的數據

w1[x=1]w2[x=2]w2[y=2]c2w1[y=1]c1 最終x=2,y=1破壞了一致性;

w1[x=2]w2[x=3]w2[y=3]c2a1 a1將會對x=2進行回滾到0,此時T2已經將x=3持久化。破壞了原子性和持久性。

  • 髒讀:一個事務讀到了另一個未提交事務修改過的數據
  • w1[x=1]r2[x=1]r2[y=0]c2w1[y=1]c1 T2得到了一個不一致的狀態
  • w1[x=1]r2[x=1]a1 T2讀取了一個不存在的值x=1
  • 不可重複讀:一個事務修改了另外一個未提交事務讀取的數據
    r1[x=1]w2[x=2]c2r1[x=2]c1 兩次讀取不一致
  • 幻讀:一個事務按照某個搜索條件讀取多次記錄時,在後讀取時讀到了之前沒有讀到的記錄 P:符合搜索條件P的記錄,[y in P]表示寫入一些符合P條件的記錄——多出來的記錄可以稱之為幻影記錄
    r1[P]w2[y in P]c2r1[P]c1

解決一致性的兩種方式

  • MVCC

SQL標準中的四種隔離級別

  • READ UNCOMMITTED:讀未提交
  • READ COMMITTED:讀已提交
  • REPEATABLE READ:可重複讀
  • SERIALIZABLE:可串行化

隔離級別

髒讀

不可重複讀

幻讀

READ UNCOMMITTED

可能

可能

可能

READ COMMITTED

不可能

可能

可能

REPEATABLE READ

不可能

不可能

可能

SERIALIZABLE

不可能

不可能

不可能

設置隔離級別

SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level

MySQL的默認隔離級別是啥?為什麼?

https://dev.mysql.com/doc/refman/8.0/en/set-transaction.html

我們平時用的隔離級別是啥?

MVCC原理

版本鏈

create table hero(
	 number int,
    name varchar(100),
    country varchar(100),
    PRIMARY KEY(number)
) Engine=InnoDB CHARSET=utf-8;

insert into hero values(1,'劉備');

number

name

country

1

劉備


InnoDB聚簇索引中兩個必要的隱藏列:

  • trx_id:修改聚簇索引記錄的事務id
  • roll_pointer: 對聚簇索引進行改動時,都會把舊的版本寫入到undo日誌中。roll_pointer相當於一個指針,指向的就是修改前的信息

事務id分配時機

  • 普通的select語句執行過程中不會分配事務id,事務id默認是0
  • 修改語句會分配一個事務id,且事務id是自增的。

undo log 的類型:

  • insert 類型: 事務提交後就沒用了
  • update類型: 事務提交後還需要供MVCC使用

談談你對MVCC的理解 -_二級索引_02

ReadView(一致性視圖)

READ UNCOMMITED:只需要讀取最新版本記錄即可;

READ COMMITED、REPEATABLE READ: 需要保證讀到的已經提交的事務修改的記錄

也就是版本鏈中的哪個版本的數據對當前事務是可見的

ReadView結構:

  • m_ids:生成ReadView時,當前系統中活躍的讀寫事務的事務id列表
  • min_trx_id:生成ReadView時,當前系統中活躍的讀寫事務中最小的事務id;也就是m_ids中的最小值
  • max_trx_id:生成ReadView時系統應該分配給下一個事務的事務id(不一定是m_ids中的最大值)
  • creator_trx_id:生成ReadView的事務的事務id

判斷規則

被訪問版本的trx_id等於creator_trx_id:可以訪問當前版本

被訪問版本的trx_id小於min_trx_id:可以訪問

被訪問版本的trx_id大於max_trx_id:不可以訪問

min_trx_id<被訪問版本的trx_id<max_trx_id:

  • 被訪問版本的trx_id in (m_ids):不可訪問
  • 可以訪問

READ COMMITED -------每次讀取數據前都生成一個ReadView

#transaction 100											         #transaction 200
begin;														             begin;

update hero set name = '關羽' where number = 1;              #更新其他一些別的表記錄
update hero set name = '張飛' where number = 1;
#使用READ COMMITED隔離級別的事務

begin;

select * from hero where number = 1;#得到的值是多少?
再來一次
#transaction 100											#transaction 200
begin;														begin;

update hero set name = '關羽' where number = 1;              #更新其他一些別的表記錄
update hero set name = '張飛' where number = 1;

------------------------------------------------------------------------------------------------------------
commit;

															update hero set name = '趙雲' where number = 1;
                                                            update hero set name = '諸葛亮' where number = 1;
#使用READ COMMITED隔離級別的事務

begin;

#事務100、200都未提交
select * from hero where number = 1;#得到的值是多少?

#事務100提交,200未提交
select * from hero where number = 1;#得到的值是多少?

REPEATABLE READ -------只會在第一次讀取數據時生成一個ReadView

注:MySQL中的REPEATABLE READ可以很大程度上禁止幻讀

二級索引與MVCC

只有在聚簇索引中才會有trx_id和roll_pointer隱藏列。如果查詢語句使用二級索引查詢,如何判斷可見性呢?

begin

select name from hero where name = '劉備'
  • 二級索引頁面的PageHeader有一個PAGE_MAX_TRX_ID屬性,記錄着當前頁的最大事務id;當前生成ReadView中的min_trx_id是否大於PAGE_MAX_TRX_ID,若是則可見,否則下一步進行判斷
  • 通過回表,去比對版本鏈中的trx_id。判斷可見版本中的二級索引值是否與查詢時相同。

delete並不會立即刪除對應的記錄,而是執行一個delete mark操作,相當於打一個標記,主要就是為MVCC服務的

例如:在可重複讀級別中條件搜索

MVCC小結

所謂的mvcc就是在READ COMMITED、REPEATABLE READ這兩種隔離級別的事務執行普通的SELECT操作時,訪問記錄的版本鏈的過程。

可以對不同事務的讀 - 寫、寫 - 讀併發執行,從而提高系統性能。

關於Purge

把update undo日誌以及僅僅標記刪除的記錄徹底刪除,這個刪除操作就叫purge。

Rollback Segment Header中的兩個屬性:

  • TRX_RSEG_HISTORY:History鏈表的基節點
  • TRX_RSEG_HISTORY_SIZE:History鏈表佔用的頁面數量

怎麼判斷是undo日誌是否可以刪除呢?

  1. 在事務提交時,會為這個事務生成一個事務no,表示事務提交的順序,先提交的事務no小,提交的事務no大。

事務no將會填到undo日誌的TRX_UNDO_TRX_NO屬性上。因為History鏈表是按照事務提交的順序來排列undo日誌的。索引History鏈表中的undo日誌也是按照事務no來排序的

  1. 一個ReadView除了前面的屬性之外,還有一個事務no,生成ReadView時,會把比系統中最大的事務no還大1的值賦給這個屬性。

InnoDB會把當前系統中所有的ReadView按照創建時間連成一個鏈表。

判斷邏輯

將最早生成的ReadView取出來,然後從各個回滾段的History鏈表中取出事務no較小的各組undo日誌。如果一組undo日誌的事務no小於當前系統中最早生成的ReadView的事務no屬性,那麼就意味着undo日誌沒有用了。可以從鏈表中移除,釋放空間。