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!
推薦閱讀:
- 淺析MySQL如何實現事務隔離
- 一文解析MySQL的MVCC實現原理
- MySQL事務與鎖實例教程詳解
- MySQL臟讀幻讀不可重復讀及事務的隔離級別和MVCC、LBCC實現
- 詳解MySQL中事務隔離級別的實現原理