前言
TCL:Transaction Control Language,事務控制語言
事務:在MySQL數據庫中表示一條或多條Sql語句組合在一起的一個執行單元。
這個執行單元要么全部執行,要么全部不執行,否則就會出現邏輯錯誤!
比如銀行里的轉賬這個事情:
A賬號余額:1000
B賬號余額:1000
現在A轉500元給B,那么完成這個轉賬的事務,數據中的SQL應該是這樣的執行過程:
步驟1:A賬號上要減少500元
update 儲蓄表 set A.余額=A.余額-500 where 賬號名='A';
步驟2:B賬號上要增加500元
update 儲蓄表 set B.余額=B.余額+500 where 賬號名='B';
如果沒有事務處理這個功能,上面的情況下,很可能會發生這樣的情況:
步驟1執行成功 A的余額變為:500
剛開始執行步驟2的時候,突然出現某系統錯誤,導致步驟2執行失敗!
步驟1成功 步驟2失敗:A的錢減少了,B的錢沒增加!
所以在類似的場景需求中我們需要事務處理:實現將步驟1和步驟2的SQL語句綁定在一起,要么都執行成功,要么不管是步驟1執行出錯還是2出錯,數據庫里的數據狀態會回滾到沒有執行任何步驟1或2的SQL語句之前!
1.MySQL數據中的存儲引擎
在具體講事務之前,還是說說MySQL數據中的存儲引擎:innoDB
1.什么是存儲引擎:在mysql中的數據使用各種不同的技術來存儲在磁盤文件(或內存)
當中的,這種具體的存儲技術就是我們說的存儲引擎。
2.我們可以通過show engines;命令來查看mysql支持的存儲引擎。
3.在mysql可以選擇的這些存儲引擎中,innodb,myisam,memory這三個是最常用的,但其中只有innodb支持事務處理,而其他是不支持事務處理的。
2.事務的ACID特點:
- 原子性(Atomicity): 組成事務的SQL語句不可再分,要么都執行,要么都不執行。
-
一致性(Consistency): 事務必須讓數據的數據狀態變化到另一個一致性的狀態,比如:
剛剛的例子中A和B的余額總和是2000,轉賬后,A和B的余額總和不能變。前后具有一致性。 - 隔離性(Isolation): 一個事務的執行,不受其他事務的干擾,相互應該是隔離的,但是實際上是很難做到的,要通過隔離級別做選擇!
- 持久性(Durability): 一個事務被提交,并成功執行,那么它對數據的修改就是永久性的,接下來的其他操作或出現的故障,不能影響到它執行的結果!
3.MySQL的事務的創建:
1.隱視事務:事務沒有明顯的開始和結束的標記,這時候像insert語句,update語句和delete語句,每一條SQL語句就默認是一個事務。
顯然,隱視事務在類似轉賬的邏輯業務需求的時候,就無法處理了!
2.顯示事務:說白了,這個事務模式,就要我們的中程序手動的用命令來開啟事務,和結束事務,并讓事務里的多余SQL語句去執行。
注意:默認MySQL是開啟自動提交事務的,用show variables like ‘autocommit’;命令可以查看到。所以,開啟顯示事務前,需要 關掉它,用set autocommit=0;只對本身回話有效。
Show ENGINES;
show VARIABLES like 'autocommit';
#@1.開始事務
SET autocommit = 0;
show VARIABLES like 'autocommit';
START TRANSACTION;#可選的,執行set autocommit=0已經默認開啟了!
#@2編寫事務中的SQL語句(主要是:SELECT UPDATE DELETE INSERT等語句)
語句1;語句;.......
#@3結束事務
END
commit; :提交事務去真正執行 # 或者 rollback; :回滾事務,恢復數據庫執行前等狀態!
示例:
DROP TABLE IF EXISTS account;
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20),
balance DOUBLE
);
INSERT INTO account(username,balance ) VALUES('A',1000),('B',1000);
START TRANSACTION;
#實現A賬號轉帳500元到B賬號
UPDATE account SET balance = 500 WHERE username = 'A';
UPDATE account SET balance = 1500 WHERE username = 'B';
ROLLBACK; #事務回滾
SELECT * FROM account;
START TRANSACTION;
#實現A賬號轉帳500元到B賬號
UPDATE account SET balance = 500 WHERE username = 'A';
UPDATE account SET balance = 1500 WHERE username = 'B';
COMMIT; #事務執行
SELECT * FROM account;
4.運行多事務導致多并發問題
同時運行的多個事務, 當這些事務訪問數據庫中相同的數據時, 如果沒有采取必要的隔離機制,就會導致各種并發問題 , 比如
4.1 臟讀(沒有被提交的操作)
對于兩個事務 T1,T2, T1 讀取了已經被 T2 更新但還沒有被提交的字段之后, 若T2 回滾, T1讀取的內容就是臨時且無效的。T1:張飛女朋友轉500元給張飛,但是沒有提交事務T1T2:張飛看賬戶余額500元(開心壞了)然后女朋友撤銷500元轉賬操作(T1回滾),那么張飛看到的500元是臨時無效的數據,是臟讀的數據。
4.2 不可重復讀(在臟讀基礎之上,更新update操作)
對于兩個事務T1, T2, T1讀取了一個字段, 然后T2更新了該字段之后, T1再次讀取同一個字段, 值就不同了。張飛第一次讀賬戶余額500元張飛第二次讀賬戶余額0元
4.3 幻讀(插入insert/刪除delete)
對于兩個事務T1, T2, T1 從一個表中讀取了一個字段, 然后T2在該表中插入了一些新的行之后, 如果T1 再次讀取同一個表, 就會多出幾行。張飛:請班級班上同學吃飯(班上就兩位同學)然后在沒有請客之前,班上有來了一位同學(由原來的請兩位同學吃飯、變成請三位同學吃飯,感覺出現了幻覺)
5.隔離級別
數據庫事務的隔離性: 數據庫系統必須具有隔離并發運行各個事務的能力,使它們不會相互影響, 避免各種并發問題。
當數據庫系統采用read Commited隔離級別時,會導致不可重復讀喝第二類丟失更新的并發問題,可以在應用程序中采用悲觀鎖或樂觀鎖來避免這類問題。從應用程序的角度,鎖可以分為以下幾類:
Serializable(串行化):一個事務在執行過程中完全看不到其他事務對數據庫所做的更新。
Repeatable Read(可重復讀):一個事務在執行過程中可以看到其他事務已經提交的新插入的記錄,但是不能看到其他事務對已有記錄的更新。
Read Commited(讀已提交數據):一個事務在執行過程中可以看到其他事務已經提交的新插入的記錄,而且能看到其他事務已經提交的對已有記錄的更新
Read Uncomitted(讀未提交數據):一個事務在執行過程中可以拷打其他事務沒有提交的新插入的記錄,而且能看到其他事務沒有提交的對已有記錄的更新。
隔離級別越高,越能保證數據的完整性和一致性,但是對并發性能的影響也越大。對于多數應用程序,可以有優先考慮把數據庫系統的隔離級別設為Read Commited,它能夠避免臟讀,而且具有較好的并發性能。盡管它會導致不可重復讀、虛讀和第二類丟失更新這些并發問題,在可能出現這類問題的個別場合,可以由應用程序采用悲觀鎖或樂觀鎖來控制。
當數據庫系統采用read Commited隔離級別時,會導致不可重復讀喝第二類丟失更新的并發問題,可以在應用程序中采用悲觀鎖或樂觀鎖來避免這類問題。從應用程序的角度,鎖可以分為以下幾類:
A.悲觀鎖:指在應用程序中顯示的為數據資源加鎖。盡管能防止丟失更新和不可重復讀這類并發問題,但是它會影響并發性能,因此應該謹慎地使用。
B.樂觀鎖:樂觀鎖假定當前事務操作數據資源時,不回有其他事務同時訪問該數據資源,因此完全依靠數據庫的隔離級別來自動管理鎖的工作。應用程序采用版本控制手段來避免可能出現的并發問題。
5.保存點(SAVEPOINT) 回滾
我們可以在MySQL處理過程中定義保存點(SAVEPOINT),然后回滾到指定的保存點前的狀態。
定義保存點,以及回滾到指定保存點前狀態的語法如下。
- 定義保存點---SAVEPOINT 保存點名;
- 回滾到指定保存點---ROLLBACK TO SAVEPOINT 保存點名:
下面演示將向表user中連續插入3條數據,在插入第2條數據的后面定義一個保存點,最后看看能否回滾到此保存點。
1.查看user表中的數據
SELECT * from account;
2.MySQL事務開始
BEGIN;
3.向表user中插入2條數據
INSERT INTO account(username,balance ) VALUES('C',1000),('D',1000);
SELECT * from account;
4.指定保存點,保存點名為test
SAVEPOINT test;
5.向表user中插入1條數據
INSERT INTO account(username,balance ) VALUES('E',1000);
SELECT * from account;
6.回滾到保存點test
ROLLBACK TO SAVEPOINT test;
SELECT * from account;