1、概述
??事務是數據庫區別于文件系統的重要特征之一,其用于保證數據庫的完整性,事務能使批量的SQL語句要么完全執行,要么完全不執行。
??例如最簡單的轉賬示例:假設用戶A要給用戶B轉賬100元,用戶A的轉賬指令已經成功發出,而用戶B由于未知的原因接收失敗,如果兩個命令單獨執行,那么用戶A的賬戶少了100元,但是用戶B又沒收到這100元,顯然是不合理的;此時將用戶A向用戶B轉賬100元的這個過程當成一個事務處理,那么由于用戶B接收的失敗,整個轉賬事務都將失敗,用戶A不會減少100元,用戶B更不會增加100元,這才是合理的情況。
??事務的特性有以下幾點:
- 原子性(Atomicity):事務對數據的修改,要么全部執行,要么全部不執行;
- 一致性(Consistency):在事務開始和完成時,數據都必須保持一致狀態。這意味著所有相關的數據規則都必須應用于事務的修改,以保持數據的完整性;
- 隔離性(Isolation):數據庫系統提供一定的隔離機制,保證事務在不受外部并發操作影響的“獨立”環境執行;
- 持久性(Durability):事務完成之后,它對于數據的修改是永久性的,即使出現系統故障也能夠保持。
2、控制事務處理
??事務處理的關鍵在于將SQL語句組分解為邏輯塊,并明確規定在數據何時應該回退,何時不應該回退。
顯式的開啟一個事務
??START TRANSACTION;
??或
??BEGIN;
回滾
??回滾會結束用戶的事務,并撤銷正在進行的所有未提交的修改,分為直接回滾和部分回滾:
直接回滾:會回滾到設置BEGIN之前的狀態。
ROLLBACK;部分回滾:通過設置保存點,回滾到保存點設置前的狀態。
設置一個事務保存點及標識符名稱:
SAVEPOINT identifier;
回滾到設置保存點之前的狀態:
ROLLBACK TO SAVEPOINT identifier;
從當前事務的一組保存點中刪除指定的保存點:
RELEASE SAVEPOINT identifier;
使用COMMIT或ROLLBACK結束事務后,所有保存點會被刪除。
提交事務
??使已對數據庫進行的所有修改成為永久性的,分為自動提交和手動提交:
- 自動提交:MySQL默認是自動提交的,即執行SQL語句后就會馬上執行COMMIT操作。
-
手動提交:如果正在使用的是一個事務安全型的存儲引擎,如“InnoDB”,那么當顯式的開啟事務后,MySQL的自動提交會被禁用,直到使用COMMIT或ROLLBACK結束事務后,MySQL會恢復為自動提交。
查詢當前自動提交功能狀態:
SELECT @@AUTOCOMMIT;
改變MySQL的自動提交模式:
SET AUTOCOMMIT=0; 禁用
SET AUTOCOMMIT=1; 啟用
僅對當前連接生效。
3、案例演示
??創建數據表“users”,并查看其存儲引擎為“InnoDB”:

??查看此時的自動提交狀態,并修改為禁用自動提交:

??禁用自動提交后,查看數據表記錄,此時為空,添加兩條記錄,顯示成功,但注意目前并未提交,即這兩條記錄沒有被存儲到磁盤中,因此調用另一個MySQL窗口查看該表時,其數據仍然為空:

??此時執行COMMIT語句,記錄被寫入磁盤,調用其他的MySQL窗口也可以查詢到數據:

??若此時執行ROLLBACK語句,則會回滾到添加記錄之前,查詢數據,顯示為空:

注意:
??之所以沒有回到創建數據表之前的狀態,而僅僅是記錄消失,是因為有些SQL語句會隱式的結束一個事務,即使用了COMMIT語句。
??例如:
ALTER FUNCTION、ALTER PROCEDURE、ALTER TABLE
BEGIN
CREATE DATABASE、CREATE FUNCTION、CREATE INDEX、CREATE PROCEDURE、CREATE TABLE
DROP DATABASE、DROP FUNCTION、DROP INDEX、DROP PROCEDURE、DROP TABLE
LOAD MASTER DATA、LOCK TABLES
RENAME TABLE
SET AUTOCOMMIT=1
START TRANSACTION
TRUNCATE TABLE
UNLOCK TABLES
??之后修改為自動提交,添加兩條記錄后,數據會被直接寫入磁盤,調用其他的MySQL窗口也可以查詢到數據:

??此時顯示的創建一個事務,會默認禁用自動提交,因此添加記錄后,在其他的MySQL窗口無法查詢到數據:

??在添加用戶“Alice”后,創建保存點“sp1”;之后清空數據表記錄,查詢后顯示該表記錄為空,回滾到保存點“sp1”的位置,再次查詢數據后,顯示用戶“Alice”也在數據表中;此時執行ROLLBACK語句,會直接回滾到創建事務時的狀態,即添加用戶“Alice”之前,因此查詢數據顯示只有4條記錄:

注意:
??有些語句不能被回滾,通常包括數據定義語言語句(DDL),例如創建或取消數據庫的語句,和創建、取消或更改表或存儲的子程序的語句。
??因此在設計事務時,不應包含這類語句。如果在事務的前部調用了一個不能被回滾的語句,則后部的其它語句會發生錯誤,在這些情況下,通過使用ROLLBACK語句不能回滾事務的全部效果。
4、鎖定機制
??上述操作都是針對單個連接進行的,但實際會存在多個連接同時操作數據的情況,舉例來說,如果用戶A和用戶B幾乎同時登錄到訂票APP,發現某場次電影的最佳觀影位置只剩最后一張票,于是用戶都紛紛下單,此時沖突就產生了。對于這種當多個連接對記錄進行修改的操作,為保證數據的一致性和完整性,需要用到鎖系統:
- 共享鎖(讀鎖):
在同一時間段內,多個用戶可以讀取同一個資源,讀取過程中數據不會有任何變化; - 排他鎖(寫鎖):
在任何時候只能有一個用戶寫入資源,當進行寫鎖時會阻塞其他的讀鎖或寫鎖操作。
語法結構
LOCK TABLES
tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}
[, tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}] ...
UNLOCK TABLES注意:
??寫鎖定通常比讀鎖定擁有更高的優先權,以確保更新被盡快地處理。這意味著,如果一個線程獲得了一個讀鎖定,則另一個線程會申請一個寫鎖定,后續的讀鎖定申請會等待,直到寫線程獲得鎖定并釋放鎖定。此時可以使用LOW_PRIORITY WRITE鎖定來允許其它線程在該線程正在等待寫鎖定時獲得讀鎖定。前提是當此時沒有線程擁有讀鎖定時,才應該使用LOW_PRIORITY WRITE鎖定。
??對于鎖系統還需要了解鎖的粒度,即鎖定時的單位。原則上只需要對待修改的數據精確加鎖即可,而不是所有資源都枷鎖,以減少系統的開銷:
- 表鎖,是一種占有系統開銷最小的鎖策略;
- 行鎖,是一種占有系統開銷最大的鎖策略。
5、隔離級別
??在SQL的標準中,定義了四種隔離級別。每一種級別都規定了,在一個事務中所做的修改,哪些在事務內和事務間是可見的,哪些是不可見的:
- READ UNCOMMITTED (未提交讀) :可以讀取未提交的記錄,會出現臟讀。
- READ COMMITTED (提交讀) :事務中只能看到已提交的修改。但不可重復讀,會出現幻讀。(在InnoDB中,會加行鎖,但是不會加間隙鎖)該隔離級別是大多數數據庫系統的默認隔離級別。
- REPEATABLE READ (可重復讀) :在InnoDB中是這樣的:RR隔離級別保證對讀取到的記錄加鎖 (行鎖),同時保證對讀取的范圍加鎖,新的滿足查詢條件的記錄不能夠插入 (間隙鎖),因此不存在幻讀現象,但是標準的RR只能保證在同一事務中多次讀取同樣記錄的結果是一致的,而無法解決幻讀問題。InnoDB的幻讀解決是依靠MVCC的實現機制做到的,該隔離級別是MySQL的默認隔離界別。
- SERIALIZABLE (可串行化):該隔離級別會在讀取的每一行數據上都加上鎖,退化為基于鎖的并發控制,即LBCC。
隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
---|---|---|---|
READ UNCOMMITTED | √ | √ | √ |
READ COMMITTED | × | √ | √ |
REPEATABLE READ | × | × | √ |
SERIALIZABLE | × | × | × |
查看隔離級別
全局隔離:SELECT @@global.tx_isolation;
會話隔離:SELECT @@tx_isolation;修改隔離級別
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL
{READ UNCOMMITTED | READ COMMITTED
| REPEATABLE READ | SERIALIZABLE};
??低級別的隔離可以執行更高級別的并發,性能好,但是會出現臟讀、幻讀等錯誤的現象:
更新丟失
??當兩個或多個事務選擇同一條記錄,然后基于最初選定的值更新該行時,由于每個事務都不知道其他事務的存在,就會發生更新丟失的問題,即最后的更新覆蓋了之前由其他事務所做的更新。
臟讀
??一個事務正在對一條記錄做修改,在這個事務完成并提交前,這條記錄的數據就處于不一致狀態;此時,另一個事務也來讀取同一條記錄,如果不加控制,第二個事務會讀取到尚未提交的臟數據,并據此做進一步的處理,這種現象被形象地叫做"臟讀"。

??如案例所示,將A窗口隔離級別修改為允許臟讀后,顯式創建事務,并修改用戶名,此時A窗口始終未執行提交指令,意味著數據其實并沒有寫入磁盤,因此在B窗口首次查詢時并沒有出現修改后的記錄,但是將B窗口的隔離級別也修改至允許臟讀后,再次查詢發現臟數據被讀出來了。
不可重復讀
??一個事務在讀取某些數據后,再次讀取之前讀過的數據,卻發現這些數據已經被另一個已提交的事務修改過,這種現象被稱為“不可重復讀”。

??如案例所示,將A窗口隔離級別修改為允許不可重復讀后,顯式創建事務,并修改用戶名,假設在A窗口執行提交指令前,B窗口進行了第一次查詢,顯示的是原始記錄,但在B窗口查詢不久后,A窗口執行了提交指令,因此當B窗口再次查詢相同的數據時,數據發生了變化,即為不可重復讀。
幻讀
??一個事務按相同的查詢條件重新讀取以前檢索過的數據,卻發現其他事務插入了滿足其查詢條件的新數據,這種現象就稱為“幻讀”。

??如案例所示,將A窗口隔離級別修改為允許幻讀后,顯式創建事務,并添加新用戶,假設在A窗口執行提交指令前,B窗口進行了第一次查詢,顯示的是原始記錄,但在B窗口查詢不久后,A窗口執行了提交指令,因此當B窗口再次執行相同的查詢指令時,出現了新的記錄,即為幻讀。
5、死鎖
??死鎖是指兩個或者多個事務在同一資源上相互作用,并請求鎖定對方占用的資源,從而導致惡性循環的現象。當多個事務試圖以不同的順序鎖定資源時,就可能產生死鎖。多個事務同時鎖定同一個資源時,也會產生死鎖。

??如案例所示,在A窗口顯式創建事務,并修改id為3的用戶名稱,之后在B窗口顯式創建事務并修改id為6的用戶名稱,此時在A窗口再次修改id為6的用戶名稱,A窗口會等待B窗口提交后釋放鎖定,在B窗口修改id為3的用戶名稱,B窗口也會等待A窗口提交后釋放鎖定,此時互相等待陷入死鎖,這時MySQL會使B窗口強制解除鎖定,使A窗口繼續執行。
6、事務處理的SQL語句匯總
顯式的開啟一個事務
START TRANSACTION;
或
BEGIN;回滾
直接回滾:
ROLLBACK;
部分回滾:
設置一個事務保存點及標識符名稱:SAVEPOINT identifier;
回滾到設置保存點之前的狀態:ROLLBACK TO SAVEPOINT identifier;
從當前事務的一組保存點中刪除指定的保存點:RELEASE SAVEPOINT identifier;
提交事務
COMMIT;
查詢當前自動提交功能狀態:
SELECT @@AUTOCOMMIT;
改變MySQL的自動提交模式:
SET AUTOCOMMIT=0; 禁用
SET AUTOCOMMIT=1; 啟用鎖定
LOCK TABLES
tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}
[, tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}] ...
UNLOCK TABLES隔離級別
查看隔離級別:
全局隔離:SELECT @@global.tx_isolation;
會話隔離:SELECT @@tx_isolation;
修改隔離級別:
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL
{READ UNCOMMITTED | READ COMMITTED
| REPEATABLE READ | SERIALIZABLE};
版權聲明:歡迎轉載,歡迎擴散,但轉載時請標明作者以及原文出處,謝謝合作! ↓↓↓