mysql事務詳細介紹

簡介

事務是由一組sql語句組成的邏輯處理單元

事務四個特性

原子性(Atomicity):
	要麼都成功要麼都失敗
	undo log實現
一致性(Consistent):
	如轉賬前後兩個數額總合保持不變
隔離性(lsolation):數據庫提供一定的隔離機制,保證事務在不受外部並發操作影響的“獨立”環境下運行
	鎖,mvcc多版本並發控制
持久性(Durable):事務提交持久化磁盤
	redo log

事務隔離級別

數據庫的事務隔離級別有四種,分別是讀未提交,讀已提交,可重復讀,序列化,不同的隔離級別會產生臟讀,幻讀,不可重復讀等相關問題,因此,在選擇隔離級別的時候要根據應用場景來決定,使用不同的隔離級別

隔離級別 臟讀 不可重復讀 幻讀
READ-UNCOMMITTED
READ-COMMITED ×
REPEATABLE-READ × ×
SERIALIZABLE × × ×

事務隔離級別帶來的問題

臟讀(Dirty Reads一個事務訪問到瞭另外一個事務未提交的數據):
	當一個事務正在訪問數據,並且對數據進行瞭修改,而這種修改還沒有提交到數據庫中,這時另一個事務也訪問這個數據,然後使用瞭這個數據。
不可重復度(Non-Repeatable Reads 一個事務兩次同樣的查詢,查詢到瞭不同的數據):
	一個事務在讀取某些數據後的某個時間,再次讀取以前讀過的數據,發現和以前讀出的數據不一致
	更新刪除
幻讀(Phantom Reads 一個事務兩次同樣的查詢,查詢到瞭不同的數據):
	一個事務按照相同的查詢條件重新讀取以前查詢過的數據,卻發現其他事務插入瞭滿足其查詢條件的新數據
	插入

驗證

查看事務的隔離級別show variables like ‘tx_isolation’;

查看事務是否自動提交show variables like ‘autocommit’;

關閉自動提交事務=0|OFF

set autocommit = 0;

臟讀:

設置事務隔離級別A、B
	set session transaction isolation level read uncommitted;
sessionA
開啟事務
	start transaction;
插入一條數據
	INSERT INTO `db_test`.`t_user`(`id`, `name`) VALUES (5, 'DuQi');
sessionB
另一個連接進行查詢
	select * from t_user;
	+----+----------+
	| id | name     |
	+----+----------+
	|  1 | ZhangSan |
	|  2 | LiSi     |
	|  3 | WangWu   |
	|  4 | LaoWang  |
	|  5 | DuQi     |
	+----+----------+
	此時連接B查詢到連接A未提交的事務的記錄id為5
	
到這裡驗證瞭一個session讀取到瞭另一個事務未提交的數據

不可重復讀:

修改事務隔離級別
	set session transaction isolation level read committed;
A開啟事務
	start transaction;
驗證更新
	B執行查詢語句
		MySQL [db_test]> select * from t_user;
		+----+----------+
		| id | name     |
		+----+----------+
		|  1 | ZhangSan |
		|  2 | LiSi     |
		|  3 | WangWu   |
		|  4 | LaoWang  |
		|  5 | DuQi     |
		+----+----------+
	A執行更新語句
		update t_user set name = 'duqi' where id = 5;
	B執行查詢語句
		start transaction;
		MySQL [db_test]> select * from t_user;
		+----+----------+
		| id | name     |
		+----+----------+
		|  1 | ZhangSan |
		|  2 | LiSi     |
		|  3 | WangWu   |
		|  4 | LaoWang  |
		|  5 | DuQi     |
		+----+----------+
	A提交事務
		commit;
	B執行查詢語句(同一個事務兩次查詢結果不一致)
		MySQL [db_test]> select * from t_user;
		+----+----------+
		| id | name     |
		+----+----------+
		|  1 | ZhangSan |
		|  2 | LiSi     |
		|  3 | WangWu   |
		|  4 | LaoWang  |
		|  5 | duqi     |
		+----+----------+
繼續驗證刪除
	A 開啟事務 B開啟事務
		start transaction ;
	A刪除一條記錄
		delete from t_user where id = 5;
	B事務查詢正常,查詢被刪除的記錄還在
		MySQL [db_test]> select * from t_user;
		+----+----------+
		| id | name     |
		+----+----------+
		|  1 | ZhangSan |
		|  2 | LiSi     |
		|  3 | WangWu   |
		|  4 | LaoWang  |
		|  5 | DuQi     |
		+----+----------+
	A commit;
	B 繼續查詢 發現同一事物中多次查詢結果不一致
		MySQL [db_test]> select * from t_user;
		+----+----------+
		| id | name     |
		+----+----------+
		|  1 | ZhangSan |
		|  2 | LiSi     |
		|  3 | WangWu   |
		|  4 | LaoWang  |
		+----+----------+

驗證插入
	A、B 開啟事務
		start transaction;
	A 插入記錄
		INSERT INTO `db_test`.`t_user`(`id`, `name`) VALUES (5, 'DuQi');
	B進行查詢
		MySQL [db_test]> select * from t_user;
		+----+----------+
		| id | name     |
		+----+----------+
		|  1 | ZhangSan |
		|  2 | LiSi     |
		|  3 | WangWu   |
		|  4 | LaoWang  |
		+----+----------+	
	A提交事務
		commit;
	B查詢 也是能查詢到A提交的事務
		MySQL [db_test]> select * from t_user;
		+----+----------+
		| id | name     |
		+----+----------+
		|  1 | ZhangSan |
		|  2 | LiSi     |
		|  3 | WangWu   |
		|  4 | LaoWang  |
		|  5 | DuQi     |
		+----+----------+

幻讀:

修改事務隔離級別
	set session transaction isolation level repeatable read;
A、B開啟事務
	start transaction;
A插入一條數據	
	INSERT INTO `db_test`.`t_user`(`id`, `name`) VALUES (5, 'DuQi');
B查詢
	MySQL [db_test]> select * from t_user;
	+----+----------+
	| id | name     |
	+----+----------+
	|  1 | ZhangSan |
	|  2 | LiSi     |
	|  3 | WangWu   |
	|  4 | LaoWang  |
	+----+----------+
A提交事務
	commit;
B事務查詢
	MySQL [db_test]> select * from t_user;
	+----+----------+
	| id | name     |
	+----+----------+
	|  1 | ZhangSan |
	|  2 | LiSi     |
	|  3 | WangWu   |
	|  4 | LaoWang  |
	|  5 | DuQi     |
	+----+----------+
可能發現,不同事務之間,插入是可以查詢到的

咱們再繼續驗證更新和刪除
	A、B開啟事務
	A更新 update t_user set name = 'duqi' where id = 5;
	B查詢
		MySQL [db_test]> select * from t_user;
		+----+----------+
		| id | name     |
		+----+----------+
		|  1 | ZhangSan |
		|  2 | LiSi     |
		|  3 | WangWu   |
		|  4 | LaoWang  |
		|  5 | DuQi     |
		+----+----------+	
	A提交事務commit
	B繼續查詢
		MySQL [db_test]> select * from t_user;
		+----+----------+
		| id | name     |
		+----+----------+
		|  1 | ZhangSan |
		|  2 | LiSi     |
		|  3 | WangWu   |
		|  4 | LaoWang  |
		|  5 | DuQi     |
		+----+----------+
	咱們再繼續驗證刪除
		A、B開啟事務
		A事務執行刪除操作 delete from t_user where id = 5;
		B事務執行查詢
			MySQL [db_test]> select * from t_user;
			+----+----------+
			| id | name     |
			+----+----------+
			|  1 | ZhangSan |
			|  2 | LiSi     |
			|  3 | WangWu   |
			|  4 | LaoWang  |
			|  5 | duqi     |
			+----+----------+
		A提交事務,B繼續查詢
			MySQL [db_test]> select * from t_user;
			+----+----------+
			| id | name     |
			+----+----------+
			|  1 | ZhangSan |
			|  2 | LiSi     |
			|  3 | WangWu   |
			|  4 | LaoWang  |
			|  5 | duqi     |
			+----+----------+
可能大傢會發現,REPEATABLE-READ 事務隔離級別解決瞭刪除和更新的問題,但是插入的問題一直存在。		

MVCC

多版本並發控制。MVCC是一種並發控制的方法,一般在數據庫管理系統中,實現對數據庫的並發訪問,在編程語言中實現事務內存

mvcc在Mysql INNODB中的實現主要是為瞭提高數據庫並發性能,用更好的方式去處理讀寫沖突,做到即使有讀寫沖突時,也能做到不加鎖,非阻塞並發讀。

瞭解mvcc之前首先要瞭解兩個概念,什麼是當前讀,什麼是快照讀

當前讀

讀取最新版本的數據

像select lock in share mode(共享鎖),select for update;update、insert、delete(排他鎖)這些操作都是一種當前讀,
為什麼叫當前讀?
	就是它讀取的是記錄的最新版本,讀取時還要保證其他並發事務不能修改當前記錄,會對讀取的記錄進行加鎖

快照讀

讀取歷史版本的數據

像不加鎖的select操作就是快照讀,既不加鎖的非阻塞讀;
快照讀的前提是隔離級別不是串行級別,串行級別下的快照讀會退化成當前讀;
之所以出現快照讀的情況,是基於提高並發性能的考慮,快照讀的實現是基於多版本控制並發控制,既MVCC,可以認為mvcc是行鎖的一個變種,但它在很多情況下避免瞭加鎖操作,降低瞭開銷;

當前讀、快照讀、MVCC關系

MVCC多版本並發控制指的是維持一個數據的多個版本,使得讀寫操作沒有沖突,快照讀是mysql為實現mvcc的一個非阻塞讀功能。
mvcc模塊在Mysql中的具體實現是由三個隱式字段,undo日志,readview三個組件來實現的。

這裡補充一點:三個隱式字段中其中有一個是列的唯一標志。有些同學設計表的時候一定要加主鍵(列依賴主鍵),即使它幾乎無用處也要加上。其實對於配置表,幾乎不進行增刪操作的表完全沒必要加主鍵,mysql在插入數據的時候會進行判斷表有無主鍵,如果有主鍵會使用主鍵作為唯一標示,如果沒有主鍵,會自動生成7byte大小的主鍵,所以表的合理性要根據不用使用場景進行設計。

mvcc 解決的問題

並發場景

1、讀讀:不存在任何問題,也不需要並發控制
2、讀寫:有線程安全問題,可能會造成事務隔離級別問題,可能遇到臟讀、不可重復讀、幻讀
3、寫寫:有線程安全問題,可能存在更新丟失問題

解決的問題

1、在並發讀寫數據庫時,可以做到在讀操作時不用阻塞寫操作,寫操作也不用阻塞讀操作,提高瞭數據庫並發讀寫的性能
2、解決臟讀、幻讀、不可重復讀等事務隔離問題,但是不能解決更新丟失問題

MVCC實現原理

mvcc的實現原理主要依賴於記錄中的三個隱藏字段、undolog,read view來實現的。

隱藏字段

行記錄除瞭我們自定義的字段外,還有數據庫隱式定義的DB_TRX_ID,BD_ROLL_PTR,DB_ROW_ID等字段

DB_TRX_ID 最近修改事務id:
	6字節,記錄創建這條記錄或者最後一次修改該記錄的事務id
DB_ROLL_PTR 回滾指針:
	7字節,指向這條記錄的上一個版本,用於配合undolog,指向上一個舊版本
DB_ROW_ID 隱藏主鍵:
	6字節,如果數據庫表沒有主鍵,那麼innodb會自動生成一個6字節的row_id

undo log

undo log被稱之為回滾日志,表示進行insert,delete,update操作的時候產生的方便回滾日志

當進行insert操作的時候,產生的undo log隻在事務回滾的時候需要,並且在事務提交之後可以被立刻丟棄
當進行update和delete操作的時候,產生的undo log不僅僅在事務回滾的時候需要,在快照讀的時候也需要,所以不能隨便刪除,隻有在快照讀或事務回滾不涉及該日志時,對應的日志才會被purge線程統一清除(當數據發生更新和刪除操作的時候都隻是設置一下老記錄,如果某個記錄的deleted_id為true,並且DB_TRX_ID相對於purge線程的read view可見,那麼這條記錄一定可以被清除的)

原理

當進行insert操作時,會生成對應delete語句
當進行delete操作時,會備份原數據的insert語句
當進行update時,會記錄原數據的update語句
這樣操作方便記錄回滾

read View

READ View是事務進行快照讀操作的時候產生的讀視圖,在該事務執行快照的那一刻,會生成一個數據系統當前的快照,記錄並維護系統當前活躍事務的id,事務的id值是遞增的。

DB_ROW_ID DB_TRX_ID DB_ROLL_PTR c_name i_age
1 1 zhangsan1 18
2 2 1 zhangsan2 19
READ VIEW的最大作用是用來做可見性判斷的,也就是說當某個事務在執行快照讀的時候,對該記錄創建一個Read View的視圖,把它當作條件去判斷當前事務能夠看到哪個版本的數據,有可能讀取到的是最新的數據,也有可能讀取到的是最新的數據,也有可能讀取的是當前記錄的undolog中某個版本的數據
read view遵循的可見性算法主要是將要被修改的數據的最新記錄中的DB_TRX_ID取出來,與系統當前其他活躍事務的id去對比,如果DB_TRX_ID跟READ VIEW的屬性做瞭比較,不符合可見性,那麼就通過DB_ROLL_PTR回滾指針去取出undolog中的DB_TRX_ID做比較,既遍歷鏈表中的DB_TRX_ID,直到找到滿足條件的DB_TRX_ID,這個DB_TRX_ID所在的舊記錄就是當前事務能看到的最新老版本數據

可見性規則

瞭解可見性規則之前首先要瞭解下Read View中的三個全局屬性

trx_list:
	一直數值列表,用來維護Read View生成時刻系統正活躍的事務ID
up_limit_id:
	記錄trx_list列表中事務ID的最小ID
low_limit_id:
	Read View生成時刻系統尚未分配下一個事務ID

比較規則

1、首先判斷DB_TRX_ID < up_limit_id,如果小於,則當前事務能看到DB_TRX_ID所在的記錄,如果大於等於進入下一個判斷
2、判斷DB_TRX_ID >= low_limit_id,如果大於等於則代表DB_TRX_ID所在的記錄在Read View生成後才出現的,那麼對於當前事務肯定不可見,如果小於,進入下一步判斷
3、判斷DB_TRX_ID是否在活躍事務中,如果在,則代表在Read View生成時刻,這個事務還是活躍狀態,還沒有commit,修改的數據,當前事務也是看不到,如果不在,則說明這個事務在Read View生成之前就已經開始commit,那麼修改的結果是能夠看到的。

到此這篇關於mysql事務詳細介紹的文章就介紹到這瞭,更多相關mysql事務內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: