標題:[MySQL初階]MySQL(9)事物機制
@水墨不寫bug
文章目錄
- 一、認識事務
- 1、多線程訪問數據庫出現的問題
- 2、對CURD的限制是通過事務機制實現的
- 3、事務的四個屬性
- 4、哪些引擎支持事務
- 二、事務的提交與autocommit設置
- 三、事務的隔離性和隔離級別
- (1) 查看當前隔離級別
- (2) 設置隔離級別
- (3) 事務隔離級別的作用
- (4)MySQL InnoDB 的四種隔離級別
- (1) 讀未提交(Read Uncommitted)
- (2) 讀已提交(Read Committed)
- (3) 可重復讀(Repeatable Read)
- (4) 串行化(Serializable)
- (5)各隔離級別對比表
- 四、事務的一致性
- 1. AID 特性與一致性的關系
- (1) 原子性(Atomicity)
- (2) 隔離性(Isolation)
- (3) 持久性(Durability)
- (4) 應用層邏輯(用戶的配合)
一、認識事務
1、多線程訪問數據庫出現的問題
MySQL是一款網絡服務,那么必定有多個客戶端同時訪問服務器的場景出現:假如有一個搶票系統,有兩個客戶端搶票,一個客戶端搶票之后,數據庫還沒有及時更新,這張票又被另一個客戶端搶了一次,一張票被賣了兩次。這不符合邏輯!
于是,必須要對數據庫的CURD操作進行一定的限制,才能解決上面的問題。
2、對CURD的限制是通過事務機制實現的
事務就是一組DML語句組成,這些語句在邏輯上存在相關性,這一組DML語句要么全部成功,要么全部失敗,是一個整體。
事務主要用于處理操作量大,復雜度高的數據。比如要刪除一個被開除的員工的所有歷史信息以及和他相關的信息,就需要多條 MySQL 語句構成,那么所有這些操作合起來,就構成了一個事務。
但是要知道,同一時刻并不是只有一個事務在運行,而是有多個事務同時在運行。如果大量的事務在不加保護的情況下訪問同一個表數據,就一定會出現問題。因為事務由多條sql語句組成,一個事務執行了一半,突然mysql客戶端掛了,那么這執行了一半的事務該怎么處理?
3、事務的四個屬性
于是,MySQL規定,一個完整的事務,不僅僅是簡單的sql語句的組合
,還要滿足下面的四個屬性
:
原子性(Atomicity,或稱不可分割性): 一個事務(transaction)中的所有操作,要么全部完成
,要么全部不完成
,不會結束在中間某個環節。事務在執行過程中發生錯誤(導致客戶端掛了),執行了一半的事務會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
一致性(Consistency): 在事務開始之前和事務結束以后,數據庫的完整性沒有被破壞。
隔離性(Isolation,又稱獨立性): 數據庫允許多個并發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務并發執行時由于交叉執行而導致數據的不一致。事務隔離分為不同級別,包括讀未提交( Read uncommitted )、讀提交( read committed )、可重復讀( repeatable read )和串行化( Serializable )。
持久性(Durability): 事務處理結束后,對數據的修改就是永久的,即便系統故障也不會丟失。
此外,事務本質上是為應用層服務的。因為我們并不是直接訪問數據庫的,而是通過上層的各種語言來范圍訪問數據庫的,于是多條sql語句封裝出來的事務可以簡化上層的編程模型和邏輯。
4、哪些引擎支持事務
使用指令查詢:
mysql> show engines\G;
*************************** 1. row ***************************Engine: ndbclusterSupport: NOComment: Clustered, fault-tolerant tables
Transactions: NULLXA: NULLSavepoints: NULL
*************************** 2. row ***************************Engine: MEMORYSupport: YESComment: Hash based, stored in memory, useful for temporary tables
Transactions: NOXA: NOSavepoints: NO
*************************** 3. row ***************************Engine: InnoDBSupport: DEFAULTComment: Supports transactions, row-level locking, and foreign keys
Transactions: YESXA: YESSavepoints: YES
*************************** 4. row ***************************Engine: PERFORMANCE_SCHEMASupport: YESComment: Performance Schema
Transactions: NOXA: NOSavepoints: NO
*************************** 5. row ***************************Engine: MyISAMSupport: YESComment: MyISAM storage engine
Transactions: NOXA: NOSavepoints: NO
*************************** 6. row ***************************Engine: FEDERATEDSupport: NOComment: Federated MySQL storage engine
Transactions: NULLXA: NULLSavepoints: NULL
*************************** 7. row ***************************Engine: ndbinfoSupport: NOComment: MySQL Cluster system information storage engine
Transactions: NULLXA: NULLSavepoints: NULL
*************************** 8. row ***************************Engine: MRG_MYISAMSupport: YESComment: Collection of identical MyISAM tables
Transactions: NOXA: NOSavepoints: NO
*************************** 9. row ***************************Engine: BLACKHOLESupport: YESComment: /dev/null storage engine (anything you write to it disappears)
Transactions: NOXA: NOSavepoints: NO
*************************** 10. row ***************************Engine: CSVSupport: YESComment: CSV storage engine
Transactions: NOXA: NOSavepoints: NO
*************************** 11. row ***************************Engine: ARCHIVESupport: YESComment: Archive storage engine
Transactions: NOXA: NOSavepoints: NO
11 rows in set (0.01 sec)
從查詢結果可知,目前只有InnodeDB引擎支持事務機制
。
二、事務的提交與autocommit設置
事務的提交(commit)
方式有兩種:
自動提交;
手動提交;
autocommit變量是設置是否自動提交。
查看autocommit變量:
show variables like 'autocommit';
設置是否自動提交:
SET AUTOCOMMIT=1; #SET AUTOCOMMIT=1 開啟自動提交
SET AUTOCOMMIT=0; #SET AUTOCOMMIT=0 禁止自動提交
當我們想要開始一個事務,需要指令:
begin;
#或者
start transaction;
當我們想要提交一個事務,需要指令:
commit;
而這兩個指令之間的sql語句就是事務的內容。
為了方便后面的演示,這里我們創建一個表結構如下:
create table if not exists account(id int primary key,name varchar(50) not null default '',blance decimal(10,2) not null default 0.0)ENGINE=InnoDB DEFAULT CHARSET=UTF8;
此時暫時把全局隔離級別
設置為讀未提交
【為了方便通過另一個客戶端查看表中的數據】:
set global transaction isolation level READ UNCOMMITTED;
查看隔離級別:
SELECT @@global.transaction_isolation AS '全局隔離級別', @@session.transaction_isolation AS '當前會話隔離級別';
重啟mysql,同步當前會話和全局的隔離級別,然后查看:
- 特性一:如果事務通過begin/start transation方式開始,則必須通過commit提交,這樣才會持久化,與是否設置autocommit無關(默認情況下autocommit是ON打開的,至于具體有什么用,在后文會講)。
如果有兩個客戶端同時訪問同一張表,同時啟動了兩個事務,一個客戶端事務執行了一半,崩潰了,MySQL會自動回滾
。
什么是回滾?
當我們開始一個事務之后,在事務執行的過程中,可以創建保存點,這個保存點可以理解為游戲中的存檔點,如果對于最新的操作不滿意,可以回滾(讀取存檔點):
savepoint s1; #創建一個保存點s1
首先,通過begin;開啟一個事務。
然后,我們在已經創建的表中插入數據:
此時設置一個savepoint s1;
然后再次進行一些增刪改操作(比如新插入一條數據):
但是發現在新進行的操作出錯了,不想要了,于是可以回滾(讀取存檔點s1)
:
rollback to s1;
結果:
于是,新插入的一條“haha”的數據就被刪除了。這就是回滾
。
回到特性一的演示:
證明:未commit,客戶端崩潰,MySQL自動會回滾[因為通過手動開始,autocommit不會自動提交]:
啟動兩個事務,左側插入一個新數據lisi,右側可以看到新插入的數據:
左側事務的客戶端被ctrl+\ 被發送abort信號,進程被殺死,右側客戶端發現新插入的lisi被MySQL自動回滾了。
證明:commit了,客戶端崩潰,MySQL數據不會再受影響,已經持久化。[因為通過手動開始,autocommit不會自動提交]
啟動兩個事務,左側客戶端插入數據jimmay,commit之后被信號殺死,右側查詢可以找到新插入的數據:
證明:沒有手動通過begin;/start transaction;啟動一個事務,autocommit會對提交產生影響。
在autocommit=1;(開啟)的情況下:
對于沒有手動通過begin;/start transaction;啟動一個事務,一條sql語句就是一個事務,因為每一條語句都會被包裝為一個事務。
即使一條語句被執行后,客戶端直接崩潰,語句仍然被持久化:
在autocommit=0;(開啟)的情況下:
需要手動commit,無論執行了多少操作,如果在客戶端退出之前沒有commit,則所有的操作都會被回滾:
如果commit了,做的操作就會被持久化:
到這里,我們可以總結一下:
- 事務開始是用
begin/start transaction
,需要手動commit
提交,才會持久化,不收到autocommit設置的影響。 - 事務可以通過設置
savepoint
手動回滾;當操作異常時,MySQL會自動回滾。 - InnoDB 每一條 SQL 語言都
默認封裝成事務
,自動提交
。(select有特殊情況,因為MySQL 有 MVCC )。 - 從上面的演示,我們能看到事務本身的原子性(回滾),持久性(commit)。
從哪里體現隔離性?
三、事務的隔離性和隔離級別
數據庫中,為了保證事務執行過程中盡量不受干擾,就有了一個重要特征:隔離性。
數據庫中,允許事務受不同程度的干擾,就有了一種重要特征:隔離級別。
(1) 查看當前隔離級別
SHOW VARIABLES LIKE 'transaction_isolation';
-- 輸出示例:transaction_isolation = REPEATABLE-READ
(2) 設置隔離級別
-- 全局設置(重啟后生效)
SET GLOBAL TRANSACTION ISOLATION LEVEL+隔離登記;
-- 當前會話設置
SET SESSION TRANSACTION ISOLATION LEVEL+隔離登記;
MySQL InnoDB 存儲引擎
支持四種標準的事務隔離級別,它們通過不同的鎖機制
和 多版本并發控制(MVCC)
實現事務之間的數據可見性和并發控制。
(3) 事務隔離級別的作用
事務隔離級別定義了多個并發事務之間的數據可見性規則,核心目標是解決以下問題:
- 臟讀(Dirty Read):讀到其他事務未提交的數據。
- 不可重復讀(Non-Repeatable Read):同一事務中多次讀取同一數據,結果不一致(其他事務修改了該行)。
- 幻讀(Phantom Read):在同一事務中,由于其他事務插入或刪除了符合查詢條件的行,導致多次相同查詢的結果集行數不一致。
(4)MySQL InnoDB 的四種隔離級別
按隔離級別從低到高排序:
(1) 讀未提交(Read Uncommitted)
- 特點:所有的事務都可以看到其他事務沒有提交的
執行結果(可能讀到“臟數據”)相當于沒有任何隔離性。 - 問題:存在 臟讀、不可重復讀、幻讀。
- 實現方式:幾乎不加鎖,依賴最低限度的鎖機制。
- 使用場景:極少使用,僅在需要最高并發且不關心數據一致性的場景。
(2) 讀已提交(Read Committed)
- 特點:一個事務只能看到其他的已經提交的事務所做的改變。
- 解決的問題:避免臟讀。
- 遺留問題:存在 不可重復讀、幻讀。
- 實現方式:
- 鎖機制:使用 行級鎖(Record Locks),寫操作鎖定當前行。
- MVCC:每次
SELECT
生成一個獨立的快照(一致性視圖),基于當前已提交的數據。
- 使用場景:適用于大多數 OLTP 系統(如 Oracle 默認級別)。
(3) 可重復讀(Repeatable Read)
- 特點: MySQL 默認的隔離級別,同一事務中多次讀取同一數據的結果一致。
- 解決的問題:避免臟讀、不可重復讀。
- 遺留問題:仍可能發生 幻讀(但 InnoDB 通過 間隙鎖 基本消除)。
- 實現方式:
- 鎖機制:使用 記錄鎖(Record Locks) + 間隙鎖(Gap Locks)(合稱 臨鍵鎖 Next-Key Locks)。
- MVCC:事務首次
SELECT
生成一致性視圖,后續讀取沿用該視圖。
- 默認級別:InnoDB 的默認隔離級別。
- 幻讀的解決:
-- 事務A BEGIN; SELECT * FROM users WHERE age > 20; -- 假設返回3條記錄-- 事務B INSERT INTO users (id, age) VALUES (4, 25); -- 提交-- 事務A再次查詢 SELECT * FROM users WHERE age > 20; -- 在可重復讀下,仍然返回3條記錄(避免幻讀)
(4) 串行化(Serializable)
- 特點:事務的最高隔離級別,所有事務串行執行,完全隔離。
- 解決的問題:避免臟讀、不可重復讀、幻讀。
- 實現方式:
- 鎖機制:所有
SELECT
自動轉換為SELECT ... FOR SHARE
,使用共享鎖,導致讀寫沖突時阻塞。 - MVCC:基本失效,強制串行化。
- 鎖機制:所有
- 使用場景:對數據一致性要求極高,但性能極低,很少使用。
(5)各隔離級別對比表
隔離級別 | 臟讀 | 不可重復讀 | 幻讀 | 鎖機制 | 性能 |
---|---|---|---|---|---|
讀未提交 | 可能 | 可能 | 可能 | 行級寫鎖 | 最高 |
讀已提交 | 無 | 可能 | 可能 | 行級寫鎖 + MVCC | 高 |
可重復讀(InnodeDB默認) | 無 | 無 | 無* | 臨鍵鎖 + MVCC | 中等 |
串行化 | 無 | 無 | 無 | 表級鎖或嚴格的行級鎖 | 最低 |
*InnoDB 在可重復讀級別下通過間隙鎖基本消除幻讀。
四、事務的一致性
事務的 一致性(Consistency) 需要 原子性(Atomicity)、隔離性(Isolation)、持久性(Durability) 以及 應用層的正確邏輯(用戶的配合) 共同維護。
1. AID 特性與一致性的關系
事務的 ACID
特性中,一致性(Consistency)是最終目標,而其他三個特性(原子性、隔離性、持久性)是實現一致性的技術手段。同時,應用層邏輯(用戶代碼)的合理設計是確保一致性的必要條件。
(1) 原子性(Atomicity)
- 作用:事務內的操作要么全部成功,要么全部失敗回滾。
- 對一致性的貢獻:防止部分操作失敗導致數據處于
“半完成”
狀態。-- 示例:轉賬操作(原子性保證) BEGIN; UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; -- 扣款 UPDATE accounts SET balance = balance + 100 WHERE user_id = 2; -- 入賬 COMMIT; -- 若任何一步失敗,整個事務回滾,避免數據不一致。
(2) 隔離性(Isolation)
- 作用:控制并發事務之間的相互影響。
- 對一致性的貢獻:防止
臟讀、不可重復讀、幻讀
等問題破壞數據邏輯。-- 示例:隔離級別避免中間狀態暴露 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; BEGIN; SELECT balance FROM accounts WHERE user_id = 1; -- 基于快照的一致性視圖 -- 其他事務的修改不會影響當前事務的查詢結果 COMMIT;
(3) 持久性(Durability)
- 作用:事務提交后,修改必須永久保存到存儲介質(如磁盤)。
- 對一致性的貢獻:確保提交后的數據在故障后仍可恢復,避免數據丟失導致不一致。
-- 提交后數據寫入磁盤(通過 Redo Log 等機制保證) COMMIT;
(4) 應用層邏輯(用戶的配合)
- 作用:開發者需確保業務規則被正確編碼。
- 對一致性的貢獻:數據庫無法自動理解業務規則,需通過代碼顯式維護。
-- 示例:應用層必須檢查轉賬的用戶減去金額,轉到的用戶加上相應的金額。 BEGIN; UPDATE accounts SET balance = balance - amount WHERE user_id = 1; UPDATE accounts SET balance = balance + amount WHERE user_id = 2; COMMIT;
完~
轉載請注明出處