掌握 MySQL 事務(wù):ACID、隔離級別詳解
一、前言
- 事務(wù)是關(guān)系型數(shù)據(jù)庫中的重要概念,用于保證一組數(shù)據(jù)庫操作作為一個單獨的工作單元來執(zhí)行。無論是銀行轉(zhuǎn)賬、訂單處理還是復(fù)雜的數(shù)據(jù)修改,事務(wù)都能保證操作的一致性和完整性。
- 本文將帶您從基礎(chǔ)概念到高級技巧,全面了解 MySQL 事務(wù)的工作原理、使用方法和優(yōu)化策略。
二、事務(wù)基礎(chǔ)
什么是事務(wù)?
- 定義:事務(wù)是一組操作的集合,這些操作要么全部執(zhí)行,要么全部不執(zhí)行。
- 事務(wù)的目的:確保數(shù)據(jù)庫操作的一致性、完整性、隔離性和持久性(ACID 屬性)。
事務(wù)的 ACID 屬性
原子性 (Atomicity)
事務(wù)是不可分割的最小操作單位,要么全部執(zhí)行,要么全部不執(zhí)行。主要是commit、rollback、autocommit來保證原子性。
一致性 (Consistency)
所謂一致性,就是保證數(shù)據(jù)的一致,也就是保證數(shù)據(jù)不要丟失,不會因為突然的斷電等導(dǎo)致數(shù)據(jù)與想要的數(shù)據(jù)不一致。主要體現(xiàn)在:
- 雙寫 保證內(nèi)存跟磁盤之間同步的數(shù)據(jù)安全
- 基于RedoLog的數(shù)據(jù)恢復(fù)
隔離性 (Isolation)一個事務(wù)的執(zhí)行不應(yīng)受其他事務(wù)的干擾。主要體現(xiàn)在:
- 事務(wù)的隔離級別
- InnoDb的鎖機制
持久性 (Durability)
一旦事務(wù)提交,對數(shù)據(jù)庫的修改是永久性的,即使系統(tǒng)崩潰也不會丟失。InnoDB去保證持久性主要體現(xiàn)在:
- 雙寫 保證內(nèi)存同步到磁盤,就算page損壞的情況下也能恢復(fù)
- RedoLog的同步機制
- binlog的同步機制
事務(wù)的開啟、提交、回滾
START TRANSACTION -- 開始一個事務(wù)。
COMMIT -- 提交事務(wù),保存所有的更改。
ROLLBACK -- 回滾事務(wù),撤銷事務(wù)中的所有更改。
自動提交
大家想一下我們平時寫sql為什么沒加上面的那些事務(wù)語句呢?這是因為我們Mysql里面,Mysql默認以自動提交模式運行的,簡單來講,就是每個語句,當(dāng)沒有START TRANSACTION開啟事務(wù)的時候,每個語句都默認START TRANSACTION、commit包圍,并且不能用ROLLBACK來回滾。但是如果執(zhí)行期間發(fā)生了錯誤,則進行回滾。
查詢是否開啟自動提交
show SESSION VARIABLES like 'autocommit'; -- 查詢(會話)是否開啟自動提交
show GLOBAL VARIABLES like 'autocommit'; -- 查詢(全局)自動開啟提交
三、MySQL 中事務(wù)
大家想想,如果沒有事務(wù),數(shù)據(jù)的查詢會有哪些問題呢?
數(shù)據(jù)一致性問題
臟讀: 能讀取到其他線程還沒有提交的數(shù)據(jù);但是這些數(shù)據(jù)可能是會回滾的。
圖片
不可重復(fù)讀: 在開啟事務(wù)之后,讀取到其他事務(wù)進行修改或者刪除提交的的數(shù)據(jù)。
圖片
幻讀: 在開啟事務(wù)之后,讀到了其他事務(wù)新添加的新數(shù)據(jù)。
圖片
非鎖定一致性讀取(MVCC,快照讀)
既然有上面那些問題,那么我們看看mysql是怎么解決這些問題的,首先我們需要先來了解幾個知識點。
事務(wù)的隔離級別
- READ UNCOMMITTED:最低隔離級別,允許事務(wù)讀取未提交的數(shù)據(jù)(臟讀)。
- READ COMMITTED:保證事務(wù)只能讀取已提交的數(shù)據(jù),避免臟讀,但可能會發(fā)生不可重復(fù)讀。
- REPEATABLE READ:事務(wù)中的查詢在整個事務(wù)期間保持一致,避免了不可重復(fù)讀,但可能會有幻讀。(innodb默認事務(wù)隔離級別)
- SERIALIZABLE:最高隔離級別,強制事務(wù)串行執(zhí)行,避免所有并發(fā)問題,但性能開銷大。
ReadView結(jié)構(gòu)
m_low_limit_id:即將要分配的下一個事務(wù)ID。
m_up_limit_id:所有存活的(沒有提交的)事務(wù)ID中最小值。
m_creator_trx_id:創(chuàng)建這個readView的事務(wù)ID。
m_ids:創(chuàng)建readView時,所有存活的事務(wù)ID列表。
行隱藏字段
DB_TRX_ID:更新這行數(shù)據(jù)的事務(wù)ID。
DB_ROLL_PTR :回滾指針,被改動前的undolog日志指針。
判斷是否可見邏輯
有了上面幾個知識點,我們來看看ReadView怎么跟我的行數(shù)據(jù)的DB_TRX_ID來配合 做到解決我的不可
重復(fù)讀或者幻讀問題呢?(這里就不畫圖了,畫圖太復(fù)雜了,大家多理解下下面的規(guī)則)
- 如果數(shù)據(jù)的DB_TRX_ID < m_up_limit_id, 都小于存活的事務(wù)ID了,那么肯定不存活了,說明在創(chuàng)建ReadView的時候已經(jīng)提交了,可見。意思就是,A事務(wù)已經(jīng)提交了,B事務(wù)才開始查詢,那么肯定可以查詢到最新的數(shù)據(jù)。
- 如果數(shù)據(jù)的DB_TRX_ID >=m_low_limit_id, 大于等于我即將分配的事務(wù)ID, 那么表明修改這條數(shù)據(jù)的事務(wù)是在創(chuàng)建了ReadView之后開啟的,不可見。
- 如果 m_up_limit_id<= DB_TRX_ID< m_low_limit_id, 表明修改這條數(shù)據(jù)的事務(wù)在第一次快照之前就創(chuàng)建好了,但是不確定提沒提交,判斷有沒有提交,直接可以根據(jù)活躍的事務(wù)列表 m_ids判斷
DB_TRX_ID如果在m_ids中,表面在創(chuàng)建ReadView之時還沒提交,不可見
DB_TRX_ID如果不在m_ids,表面在創(chuàng)建ReadView之時已經(jīng)提交,可見
UndoLog日志查詢以往版本的數(shù)據(jù)
所謂UndoLog,也就是回滾日志,簡單點,當(dāng)我需要回滾的時候,能找到之前相關(guān)的數(shù)據(jù)。比如,我們rollback或者異常中斷的時候,能找到改動之前的數(shù)據(jù)進行恢復(fù)。當(dāng)然,我們在mvcc中也需要用到undolog來找到以往的數(shù)據(jù)來解決不可重復(fù)讀跟幻讀問題。
那么undolog到底怎么記錄的,我們來看下面這個圖:
圖片
四、事務(wù)的并發(fā)控制
鎖機制
**行級鎖 (Row-level Lock)**:InnoDB 默認使用行級鎖,它允許更多的并發(fā)操作,減少鎖沖突。
**表級鎖 (Table-level Lock)**:MyISAM 使用表級鎖,可能導(dǎo)致較大的鎖競爭。
鎖的類型
記錄鎖(Record Locks)
記錄鎖,顧名思義,是鎖在索引記錄上的鎖,索引掃描某些數(shù)據(jù)的時候,在這些索引數(shù)據(jù)上加鎖。
SELECT * FROM user where id=1 FOR UPDATE; -- 索引掃描到id=1的數(shù)據(jù),那么會鎖id=1的數(shù)據(jù),其他事務(wù)不能進行操作
間隙鎖(Gap Locks)
間隙鎖是對索引記錄之間的間隙的鎖。所謂間隙,就是索引數(shù)據(jù)之間的間隙,那么間隙鎖,就是鎖住數(shù)據(jù)之間的間隙,不允許間隙之內(nèi)添加數(shù)據(jù)??纯聪旅娴睦?
SELECT * FROM user where id > 1 AND id < 5 FOR UPDATE; -- 這里因為沒有命中索引,所以索引1 和 5 之間不能添加id為2,3,4的數(shù)據(jù)
臨鍵鎖(Next-Key Locks)
臨鍵鎖是索引記錄上的記錄鎖和索引記錄之前的間隙上的間隙鎖的組合。就是我掃描的數(shù)據(jù),既包含索引中存在的數(shù)據(jù),又是掃描的一個區(qū)間。看看下面的例子:
SELECT * FROM user where id > 4 AND id < 8 FOR UPDATE; -- 這里命中了索引4,所以索引1 和 5 之間和5和9之間不能添加id為2,3,4,6,7,8的數(shù)據(jù),并且id=4這條數(shù)據(jù)不能修改
五、總結(jié)
- 事務(wù)是 MySQL 數(shù)據(jù)庫中重要的概念,它保證了操作的原子性、一致性、隔離性和持久性(ACID 屬性)。
- MySQL 提供了豐富的事務(wù)控制功能,包括事務(wù)隔離級別、鎖機制、死鎖檢測等。