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