目錄
事務
事務的概念
什麼是事務:
一個業務操作,如:轉賬操作。需要執行多條SQL語句,如果有一條語句失敗,已經執行的語句必須進行回滾,要回到沒有執行前的狀態。要麼所有的語句全部執行成功,要麼就全部失敗,稱爲事務Transaction。
事務的四大特性
事務特性 |
含義 |
原子性(Atomicity) |
事務中所有的代碼必須是一個整體,要麼全部成功,要麼全部失敗。 |
一致性(Consistency) |
事務在執行前或執行後,數據庫的狀態必須保持一致。如轉賬前總金額與轉賬後總金額要相同。 |
隔離性(Isolation) |
如果同時有多個事務在數據庫中執行,事務與事務之間不能相互影響。 |
持久性(Durability) |
如果事務執行成功,對數據庫影響是持久的。 |
事務的應用場景說明
- 轉賬的操作
-- 創建數據表
CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10),
balance DOUBLE
);
-- 添加數據
INSERT INTO account (NAME, balance) VALUES ('Jack', 1000), ('Rose', 1000);
- 轉賬需求:
模擬Jack給Rose轉500元錢,一個轉賬的業務操作最少要執行下面的2條語句:
Jack賬號-500
Rose賬號+500
-- 從jack中扣錢500塊
update account set balance=balance-500 where name='Jack';
-- 給rose加錢
update account set balance=balance+500 where name='Rose';
假設當Jack賬號上-500元,服務器崩潰了。Rose的賬號並沒有+500元,數據就出現問題了。我們需要保證其中一條SQL語句出現問題,整個轉賬就算失敗。只有兩條SQL都成功了轉賬纔算成功。這個時候就需要用到事務。
手動提交事務
MYSQL中可以有兩種方式進行事務的操作:
- 手動提交事務
- 自動提交事務
手動提交事務的SQL語句
功能 |
SQL語句 |
開啓事務 |
start transaction |
提交事務 |
commit |
回滾事務 |
rollback |
手動提交事務使用過程:
案例演示1:事務提交
模擬Jack給Rose轉500元錢(成功) 目前數據庫數據如下:
- 使用DOS控制檯進入MySQL
- 執行以下SQL語句: 1.開啓事務, 2.Jack賬號-500, 3.Rose賬號+500
- 使用SQLYog查看數據庫:發現數據並沒有改變
- 在控制檯執行commit提交任務:
- 使用SQLYog查看數據庫:發現數據改變
案例演示2:事務回滾
模擬Jack給Rose轉500元錢(失敗)
- 在控制檯執行以下SQL語句:1.開啓事務, 2.Jack賬號-500
- 使用SQLYog查看數據庫:發現數據並沒有改變
- 在控制檯執行rollback回滾事務:
- 使用SQLYog查看數據庫:發現數據沒有改變
結論: 如果事務中SQL語句沒有問題,commit提交事務,會對數據庫數據的數據進行改變。 如果事務中SQL語句有問題,rollback回滾事務,會回退到開啓事務時的狀態。
自動提交事務
MySQL默認每一條DML(增刪改)語句都是一個單獨的事務,每條語句都會自動開啓一個事務,執行完畢自動提交事務,MySQL默認開始自動提交事務
案例演示3:自動提交事務
- 將金額重置爲1000
- 更新其中某一個賬戶
- 使用SQLYog查看數據庫:發現數據已經改變
取消自動提交
- 查看MySQL是否開啓自動提交事務
在mysql中@@開頭,表示mysql中全局變量,可以直接使用
- 取消自動提交事務
- 執行更新語句,使用SQLYog查看數據庫,發現數據並沒有改變,在控制檯執行commit提交任務
事務原理
事務開啓之後, 所有的操作都會臨時保存到事務日誌中, 事務日誌只有在得到commit命令纔會同步到數據表中,其他任何情況都會清空事務日誌(rollback,斷開連接)
原理圖:
事務的原理解釋:
- 用戶創建連接,創建日誌文件
- 如果用戶沒有開啓事務,所有的操作直接寫到數據庫中
- 如果開啓事務,所有增刪改操作寫到日誌文件中,並沒有寫到數據庫中。
- 如果進行了查詢操作,將表中的數據經過日誌文件加工以後返回。
- 如果執行commit命令,把日誌文件中的數據寫到數據庫
- 如果執行rollback命令,把日誌文件清空,數據並沒有寫到數據庫中。
回滾點
什麼是回滾點
之前我們所有的事務操作都是回滾到事務開始之前,有時我們並不希望回滾到最前面,可能只是回滾到其中的某個點,設置回滾點。不用回滾到最開始的狀態。
回滾點的操作語句
回滾點的操作語句 |
語句 |
設置回滾點 |
savepoint 名字 |
回到回滾點 |
rollback to 名字 |
具體操作:
- 將數據還原到1000
- 開啓事務
- 讓Jack賬號減2次錢,每次10塊
- 設置回滾點:savepoint two;
- 讓Jack賬號減2次錢,每次10塊
- 回到回滾點:rollback to two;
- 分析執行過程
事務小結
事務的操作 |
MySQL操作事務的語句 |
開啓事務 |
start transaction |
提交事務 |
commit |
回滾事務 |
rollback |
查詢事務的自動提交情況 |
select @@autocommit |
設置事務的自動提交方式 |
set @@autocommit = 0; -- 手動提交 |
設置回滾點 |
savepoint 名字 |
回到回滾點 |
rollback to 名字 |
事務的隔離級別
事務的四大特性ACID
事務特性 |
含義 |
原子性(Atomicity) |
一個事務不可再分割,事務中所有的SQL語句是一個整體,要麼全部成功,要麼全部失敗。 |
一致性(Consistency) |
事務在操作數據之前與操作數據之後,數據庫的狀態應該是一致。如:轉賬前與轉賬後兩個人的總金額應該是一樣的。 |
隔離性(Isolation) |
事務與事務之間不應該相互影響,一個事務的執行不應該影響到另一個事務。 |
持久性(Durability) |
如果事務提交,它對數據庫的影響是持久的,就算服務器關閉,數據也是存在的。 |
事務的隔離級別
事務在操作時的理想狀態:
併發訪問的問題 |
含義 |
髒讀 |
一個事務讀取到了另一個事務中尚未提交的數據 |
不可重複讀 |
一個事務中兩次讀取的數據內容不一致,要求的是一個事務中多次讀取時數據是一致的,這是事務update時引發的問題 |
幻讀 |
一個事務中兩次讀取的數據的數量不一致,要求在一個事務多次讀取的數據的數量是一致的,這是insert或delete時引發的問題 |
MySQL數據庫有四種隔離級別
上面的級別最低,下面的級別最高。“是”表示會出現這種問題,“否”表示不會出現這種問題。
級別 |
名字 |
隔離級別 |
髒讀 |
不可重複讀 |
幻讀 |
數據庫默認隔離級別 |
1 |
讀未提交 |
read uncommitted |
是 |
是 |
是 |
|
2 |
讀已提交 |
read committed |
否 |
是 |
是 |
Oracle和SQL Server |
3 |
可重複讀 |
repeatable read |
否 |
否 |
是 |
MySQL |
4 |
串行化 |
serializable |
否 |
否 |
否 |
|
MySQL事務隔離級別相關的命令
- 查詢全局事務隔離級別
查詢隔離級別 |
select @@tx_isolation; |
- 設置事務隔離級別,需要退出MySQL再重新登錄才能看到隔離級別的變化
設置隔離級別 |
set global transaction isolation level 級別字符串; |
髒讀的演示
將數據進行恢復:UPDATE account SET balance = 1000;
- 打開A窗口登錄MySQL,設置全局的隔離級別爲最低
mysql -uroot -proot
set global transaction isolation level read uncommitted;
- 打開B窗口,AB窗口都開啓事務
use day23;
start transaction;
- A窗口更新2個人的賬戶數據,未提交
update account set balance=balance-500 where id=1;
update account set balance=balance+500 where id=2;
- B窗口查詢賬戶
select * from account;
- A窗口回滾
rollback;
- B窗口查詢賬戶,錢沒了
髒讀非常危險的,比如Jack向Rose購買商品,Jack開啓事務,向Rose賬號轉入500塊,然後打電話給Rose說錢已經轉了。Rose一查詢錢到賬了,發貨給Jack。Jack收到貨後回滾事務,Rose的再查看錢沒了。
解決髒讀的問題:將全局的隔離級別進行提升
將數據進行恢復:
UPDATE account SET balance = 1000;
- 在A窗口設置全局的隔離級別爲read committed
set global transaction isolation level read committed;
B窗口退出MySQL,B窗口再進入MySQL
AB窗口同時開啓事務
- A更新2個人的賬戶,未提交
update account set balance=balance-500 where id=1;
update account set balance=balance+500 where id=2;
- B窗口查詢賬戶
A窗口commit提交事務
- B窗口查看賬戶
結論:read committed的方式可以避免髒讀的發生
不可重複讀的演示
將數據進行恢復:
UPDATE account SET balance = 1000;
- 開啓A窗口
set global transaction isolation level read committed;
- 開啓B窗口,在B窗口開啓事務
start transaction;
select * from account;
- 在A窗口開啓事務,並更新數據
start transaction;
update account set balance=balance+500 where id=1;
commit;
- B窗口查詢
select * from account;
兩次查詢輸出的結果不同,到底哪次是對的?不知道以哪次爲準。 很多人認爲這種情況就對了,無須困惑,當然是後面的爲準。我們可以考慮這樣一種情況,比如銀行程序需要將查詢結果分別輸出到電腦屏幕和發短信給客戶,結果在一個事務中針對不同的輸出目的地進行的兩次查詢不一致,導致文件和屏幕中的結果不一致,銀行工作人員就不知道以哪個爲準了。
解決不可重複讀的問題:
將全局的隔離級別進行提升爲:repeatable read
將數據進行恢復:
UPDATE account SET balance = 1000;
- A窗口設置隔離級別爲:repeatable read
set global transaction isolation level repeatable read;
- B窗口退出MySQL,B窗口再進入MySQL
start transaction;
select * from account;
- A窗口更新數據
start transaction;
update account set balance=balance+500 where id=1;
commit;
- B窗口查詢
select * from account;
結論:同一個事務中爲了保證多次查詢數據一致,必須使用repeatable read隔離級別,B窗口再次提交事務即可看到數據變化
幻讀的演示
我們可以將事務隔離級別設置到最高,以擋住幻讀的發生 將數據進行恢復:
UPDATE account SET balance = 1000;
- 開啓A窗口
set global transaction isolation level serializable; -- 設置隔離級別爲最高
- A窗口退出MySQL,A窗口重新登錄MySQL
start transaction;
select count(*) from account;
- 再開啓B窗口,登錄MySQL
- 在B窗口中開啓事務,添加一條記錄
start transaction; -- 開啓事務
insert into account (name,balance) values ('LaoWang', 500);
- 在A窗口中commit提交事務,B窗口中insert語句會在A窗口事務提交後立馬運行
- 在A窗口中接着查詢,發現數據不變
select count(*) from account;
- B窗口中commit提交當前事務
- A窗口就能看到最新的數據
結論:使用serializable隔離級別,一個事務沒有執行完,其他事務的SQL執行不了,可以擋住幻讀