mysql事務(wù)詳細(xì)介紹
簡介
事務(wù)是由一組sql語句組成的邏輯處理單元
事務(wù)四個特性
原子性(Atomicity): 要么都成功要么都失敗 undo log實(shí)現(xiàn) 一致性(Consistent): 如轉(zhuǎn)賬前后兩個數(shù)額總合保持不變 隔離性(lsolation):數(shù)據(jù)庫提供一定的隔離機(jī)制,保證事務(wù)在不受外部并發(fā)操作影響的“獨(dú)立”環(huán)境下運(yùn)行 鎖,mvcc多版本并發(fā)控制 持久性(Durable):事務(wù)提交持久化磁盤 redo log
事務(wù)隔離級別
數(shù)據(jù)庫的事務(wù)隔離級別有四種,分別是讀未提交,讀已提交,可重復(fù)讀,序列化,不同的隔離級別會產(chǎn)生臟讀,幻讀,不可重復(fù)讀等相關(guān)問題,因此,在選擇隔離級別的時候要根據(jù)應(yīng)用場景來決定,使用不同的隔離級別
隔離級別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
事務(wù)隔離級別帶來的問題
臟讀(Dirty Reads一個事務(wù)訪問到了另外一個事務(wù)未提交的數(shù)據(jù)): 當(dāng)一個事務(wù)正在訪問數(shù)據(jù),并且對數(shù)據(jù)進(jìn)行了修改,而這種修改還沒有提交到數(shù)據(jù)庫中,這時另一個事務(wù)也訪問這個數(shù)據(jù),然后使用了這個數(shù)據(jù)。 不可重復(fù)度(Non-Repeatable Reads 一個事務(wù)兩次同樣的查詢,查詢到了不同的數(shù)據(jù)): 一個事務(wù)在讀取某些數(shù)據(jù)后的某個時間,再次讀取以前讀過的數(shù)據(jù),發(fā)現(xiàn)和以前讀出的數(shù)據(jù)不一致 更新刪除 幻讀(Phantom Reads 一個事務(wù)兩次同樣的查詢,查詢到了不同的數(shù)據(jù)): 一個事務(wù)按照相同的查詢條件重新讀取以前查詢過的數(shù)據(jù),卻發(fā)現(xiàn)其他事務(wù)插入了滿足其查詢條件的新數(shù)據(jù) 插入
驗(yàn)證
查看事務(wù)的隔離級別show variables like ‘tx_isolation';
查看事務(wù)是否自動提交show variables like ‘a(chǎn)utocommit';
關(guān)閉自動提交事務(wù)=0|OFF
set autocommit = 0;
臟讀:
設(shè)置事務(wù)隔離級別A、B set session transaction isolation level read uncommitted; sessionA 開啟事務(wù) start transaction; 插入一條數(shù)據(jù) INSERT INTO `db_test`.`t_user`(`id`, `name`) VALUES (5, 'DuQi'); sessionB 另一個連接進(jìn)行查詢 select * from t_user; +----+----------+ | id | name | +----+----------+ | 1 | ZhangSan | | 2 | LiSi | | 3 | WangWu | | 4 | LaoWang | | 5 | DuQi | +----+----------+ 此時連接B查詢到連接A未提交的事務(wù)的記錄id為5 到這里驗(yàn)證了一個session讀取到了另一個事務(wù)未提交的數(shù)據(jù)
不可重復(fù)讀:
修改事務(wù)隔離級別 set session transaction isolation level read committed; A開啟事務(wù) start transaction; 驗(yàn)證更新 B執(zhí)行查詢語句 MySQL [db_test]> select * from t_user; +----+----------+ | id | name | +----+----------+ | 1 | ZhangSan | | 2 | LiSi | | 3 | WangWu | | 4 | LaoWang | | 5 | DuQi | +----+----------+ A執(zhí)行更新語句 update t_user set name = 'duqi' where id = 5; B執(zhí)行查詢語句 start transaction; MySQL [db_test]> select * from t_user; +----+----------+ | id | name | +----+----------+ | 1 | ZhangSan | | 2 | LiSi | | 3 | WangWu | | 4 | LaoWang | | 5 | DuQi | +----+----------+ A提交事務(wù) commit; B執(zhí)行查詢語句(同一個事務(wù)兩次查詢結(jié)果不一致) MySQL [db_test]> select * from t_user; +----+----------+ | id | name | +----+----------+ | 1 | ZhangSan | | 2 | LiSi | | 3 | WangWu | | 4 | LaoWang | | 5 | duqi | +----+----------+ 繼續(xù)驗(yàn)證刪除 A 開啟事務(wù) B開啟事務(wù) start transaction ; A刪除一條記錄 delete from t_user where id = 5; B事務(wù)查詢正常,查詢被刪除的記錄還在 MySQL [db_test]> select * from t_user; +----+----------+ | id | name | +----+----------+ | 1 | ZhangSan | | 2 | LiSi | | 3 | WangWu | | 4 | LaoWang | | 5 | DuQi | +----+----------+ A commit; B 繼續(xù)查詢 發(fā)現(xiàn)同一事物中多次查詢結(jié)果不一致 MySQL [db_test]> select * from t_user; +----+----------+ | id | name | +----+----------+ | 1 | ZhangSan | | 2 | LiSi | | 3 | WangWu | | 4 | LaoWang | +----+----------+ 驗(yàn)證插入 A、B 開啟事務(wù) start transaction; A 插入記錄 INSERT INTO `db_test`.`t_user`(`id`, `name`) VALUES (5, 'DuQi'); B進(jìn)行查詢 MySQL [db_test]> select * from t_user; +----+----------+ | id | name | +----+----------+ | 1 | ZhangSan | | 2 | LiSi | | 3 | WangWu | | 4 | LaoWang | +----+----------+ A提交事務(wù) commit; B查詢 也是能查詢到A提交的事務(wù) MySQL [db_test]> select * from t_user; +----+----------+ | id | name | +----+----------+ | 1 | ZhangSan | | 2 | LiSi | | 3 | WangWu | | 4 | LaoWang | | 5 | DuQi | +----+----------+
幻讀:
修改事務(wù)隔離級別 set session transaction isolation level repeatable read; A、B開啟事務(wù) start transaction; A插入一條數(shù)據(jù) 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提交事務(wù) commit; B事務(wù)查詢 MySQL [db_test]> select * from t_user; +----+----------+ | id | name | +----+----------+ | 1 | ZhangSan | | 2 | LiSi | | 3 | WangWu | | 4 | LaoWang | | 5 | DuQi | +----+----------+ 可能發(fā)現(xiàn),不同事務(wù)之間,插入是可以查詢到的 咱們再繼續(xù)驗(yàn)證更新和刪除 A、B開啟事務(wù) 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提交事務(wù)commit B繼續(xù)查詢 MySQL [db_test]> select * from t_user; +----+----------+ | id | name | +----+----------+ | 1 | ZhangSan | | 2 | LiSi | | 3 | WangWu | | 4 | LaoWang | | 5 | DuQi | +----+----------+ 咱們再繼續(xù)驗(yàn)證刪除 A、B開啟事務(wù) A事務(wù)執(zhí)行刪除操作 delete from t_user where id = 5; B事務(wù)執(zhí)行查詢 MySQL [db_test]> select * from t_user; +----+----------+ | id | name | +----+----------+ | 1 | ZhangSan | | 2 | LiSi | | 3 | WangWu | | 4 | LaoWang | | 5 | duqi | +----+----------+ A提交事務(wù),B繼續(xù)查詢 MySQL [db_test]> select * from t_user; +----+----------+ | id | name | +----+----------+ | 1 | ZhangSan | | 2 | LiSi | | 3 | WangWu | | 4 | LaoWang | | 5 | duqi | +----+----------+ 可能大家會發(fā)現(xiàn),REPEATABLE-READ 事務(wù)隔離級別解決了刪除和更新的問題,但是插入的問題一直存在。
MVCC
多版本并發(fā)控制。MVCC是一種并發(fā)控制的方法,一般在數(shù)據(jù)庫管理系統(tǒng)中,實(shí)現(xiàn)對數(shù)據(jù)庫的并發(fā)訪問,在編程語言中實(shí)現(xiàn)事務(wù)內(nèi)存
mvcc在Mysql INNODB中的實(shí)現(xiàn)主要是為了提高數(shù)據(jù)庫并發(fā)性能,用更好的方式去處理讀寫沖突,做到即使有讀寫沖突時,也能做到不加鎖,非阻塞并發(fā)讀。
了解mvcc之前首先要了解兩個概念,什么是當(dāng)前讀,什么是快照讀
當(dāng)前讀
讀取最新版本的數(shù)據(jù)
像select lock in share mode(共享鎖),select for update;update、insert、delete(排他鎖)這些操作都是一種當(dāng)前讀, 為什么叫當(dāng)前讀? 就是它讀取的是記錄的最新版本,讀取時還要保證其他并發(fā)事務(wù)不能修改當(dāng)前記錄,會對讀取的記錄進(jìn)行加鎖
快照讀
讀取歷史版本的數(shù)據(jù)
像不加鎖的select操作就是快照讀,既不加鎖的非阻塞讀; 快照讀的前提是隔離級別不是串行級別,串行級別下的快照讀會退化成當(dāng)前讀; 之所以出現(xiàn)快照讀的情況,是基于提高并發(fā)性能的考慮,快照讀的實(shí)現(xiàn)是基于多版本控制并發(fā)控制,既MVCC,可以認(rèn)為mvcc是行鎖的一個變種,但它在很多情況下避免了加鎖操作,降低了開銷;
當(dāng)前讀、快照讀、MVCC關(guān)系
MVCC多版本并發(fā)控制指的是維持一個數(shù)據(jù)的多個版本,使得讀寫操作沒有沖突,快照讀是mysql為實(shí)現(xiàn)mvcc的一個非阻塞讀功能。 mvcc模塊在Mysql中的具體實(shí)現(xiàn)是由三個隱式字段,undo日志,readview三個組件來實(shí)現(xiàn)的。
這里補(bǔ)充一點(diǎn):三個隱式字段中其中有一個是列的唯一標(biāo)志。有些同學(xué)設(shè)計(jì)表的時候一定要加主鍵(列依賴主鍵),即使它幾乎無用處也要加上。其實(shí)對于配置表,幾乎不進(jìn)行增刪操作的表完全沒必要加主鍵,mysql在插入數(shù)據(jù)的時候會進(jìn)行判斷表有無主鍵,如果有主鍵會使用主鍵作為唯一標(biāo)示,如果沒有主鍵,會自動生成7byte大小的主鍵,所以表的合理性要根據(jù)不用使用場景進(jìn)行設(shè)計(jì)。
mvcc 解決的問題
并發(fā)場景
1、讀讀:不存在任何問題,也不需要并發(fā)控制 2、讀寫:有線程安全問題,可能會造成事務(wù)隔離級別問題,可能遇到臟讀、不可重復(fù)讀、幻讀 3、寫寫:有線程安全問題,可能存在更新丟失問題
解決的問題
1、在并發(fā)讀寫數(shù)據(jù)庫時,可以做到在讀操作時不用阻塞寫操作,寫操作也不用阻塞讀操作,提高了數(shù)據(jù)庫并發(fā)讀寫的性能 2、解決臟讀、幻讀、不可重復(fù)讀等事務(wù)隔離問題,但是不能解決更新丟失問題
MVCC實(shí)現(xiàn)原理
mvcc的實(shí)現(xiàn)原理主要依賴于記錄中的三個隱藏字段、undolog,read view來實(shí)現(xiàn)的。
隱藏字段
行記錄除了我們自定義的字段外,還有數(shù)據(jù)庫隱式定義的DB_TRX_ID,BD_ROLL_PTR,DB_ROW_ID等字段
DB_TRX_ID 最近修改事務(wù)id: 6字節(jié),記錄創(chuàng)建這條記錄或者最后一次修改該記錄的事務(wù)id DB_ROLL_PTR 回滾指針: 7字節(jié),指向這條記錄的上一個版本,用于配合undolog,指向上一個舊版本 DB_ROW_ID 隱藏主鍵: 6字節(jié),如果數(shù)據(jù)庫表沒有主鍵,那么innodb會自動生成一個6字節(jié)的row_id
undo log
undo log被稱之為回滾日志,表示進(jìn)行insert,delete,update操作的時候產(chǎn)生的方便回滾日志
當(dāng)進(jìn)行insert操作的時候,產(chǎn)生的undo log只在事務(wù)回滾的時候需要,并且在事務(wù)提交之后可以被立刻丟棄 當(dāng)進(jìn)行update和delete操作的時候,產(chǎn)生的undo log不僅僅在事務(wù)回滾的時候需要,在快照讀的時候也需要,所以不能隨便刪除,只有在快照讀或事務(wù)回滾不涉及該日志時,對應(yīng)的日志才會被purge線程統(tǒng)一清除(當(dāng)數(shù)據(jù)發(fā)生更新和刪除操作的時候都只是設(shè)置一下老記錄,如果某個記錄的deleted_id為true,并且DB_TRX_ID相對于purge線程的read view可見,那么這條記錄一定可以被清除的)
原理
當(dāng)進(jìn)行insert操作時,會生成對應(yīng)delete語句 當(dāng)進(jìn)行delete操作時,會備份原數(shù)據(jù)的insert語句 當(dāng)進(jìn)行update時,會記錄原數(shù)據(jù)的update語句 這樣操作方便記錄回滾
read View
READ View是事務(wù)進(jìn)行快照讀操作的時候產(chǎn)生的讀視圖,在該事務(wù)執(zhí)行快照的那一刻,會生成一個數(shù)據(jù)系統(tǒng)當(dāng)前的快照,記錄并維護(hù)系統(tǒng)當(dāng)前活躍事務(wù)的id,事務(wù)的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的最大作用是用來做可見性判斷的,也就是說當(dāng)某個事務(wù)在執(zhí)行快照讀的時候,對該記錄創(chuàng)建一個Read View的視圖,把它當(dāng)作條件去判斷當(dāng)前事務(wù)能夠看到哪個版本的數(shù)據(jù),有可能讀取到的是最新的數(shù)據(jù),也有可能讀取到的是最新的數(shù)據(jù),也有可能讀取的是當(dāng)前記錄的undolog中某個版本的數(shù)據(jù) read view遵循的可見性算法主要是將要被修改的數(shù)據(jù)的最新記錄中的DB_TRX_ID取出來,與系統(tǒng)當(dāng)前其他活躍事務(wù)的id去對比,如果DB_TRX_ID跟READ VIEW的屬性做了比較,不符合可見性,那么就通過DB_ROLL_PTR回滾指針去取出undolog中的DB_TRX_ID做比較,既遍歷鏈表中的DB_TRX_ID,直到找到滿足條件的DB_TRX_ID,這個DB_TRX_ID所在的舊記錄就是當(dāng)前事務(wù)能看到的最新老版本數(shù)據(jù)
可見性規(guī)則
了解可見性規(guī)則之前首先要了解下Read View中的三個全局屬性
trx_list: 一直數(shù)值列表,用來維護(hù)Read View生成時刻系統(tǒng)正活躍的事務(wù)ID up_limit_id: 記錄trx_list列表中事務(wù)ID的最小ID low_limit_id: Read View生成時刻系統(tǒng)尚未分配下一個事務(wù)ID
比較規(guī)則
1、首先判斷DB_TRX_ID < up_limit_id,如果小于,則當(dāng)前事務(wù)能看到DB_TRX_ID所在的記錄,如果大于等于進(jìn)入下一個判斷 2、判斷DB_TRX_ID >= low_limit_id,如果大于等于則代表DB_TRX_ID所在的記錄在Read View生成后才出現(xiàn)的,那么對于當(dāng)前事務(wù)肯定不可見,如果小于,進(jìn)入下一步判斷 3、判斷DB_TRX_ID是否在活躍事務(wù)中,如果在,則代表在Read View生成時刻,這個事務(wù)還是活躍狀態(tài),還沒有commit,修改的數(shù)據(jù),當(dāng)前事務(wù)也是看不到,如果不在,則說明這個事務(wù)在Read View生成之前就已經(jīng)開始commit,那么修改的結(jié)果是能夠看到的。
到此這篇關(guān)于mysql事務(wù)詳細(xì)介紹的文章就介紹到這了,更多相關(guān)mysql事務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mysql show processlist 顯示mysql查詢進(jìn)程
processlist命令的輸出結(jié)果顯示了有哪些線程在運(yùn)行,可以幫助識別出有問題的查詢語句,兩種方式使用這個命令2012-03-03Mysql在Windows系統(tǒng)快速安裝部署方法(綠色免安裝版)
這篇文章主要介紹了Mysql在Windows系統(tǒng)快速安裝部署方法(綠色免安裝版),需要的朋友可以參考下2017-06-06關(guān)于MySQL中datetime和timestamp的區(qū)別解析
在MySQL中一些日期字段的類型選擇為datetime和timestamp,那么對于這兩種類型不同的應(yīng)用場景是什么呢,這篇文章主要介紹了關(guān)于MySQL中datetime和timestamp的區(qū)別解析,需要的朋友可以參考下2023-06-06MySQL查看與修改當(dāng)前數(shù)據(jù)庫編碼的方法
這篇文章主要介紹了MySQL查看與修改當(dāng)前數(shù)據(jù)庫編碼的方法,需要的朋友可以參考下2016-04-04解析如何使用Zend Framework 連接數(shù)據(jù)庫
本篇文章是對如何使用Zend Framework 連接數(shù)據(jù)庫的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06