MYSQL的事務管理
1. 事務的應用場景
在實際的開發過程中,一個業務操作如:轉賬,往往是要多次訪問資料庫才能完成的。轉賬是一個使用者扣錢,另一個使用者加錢。如果其中有一條 SQL 語句出現異常,這條 SQL 就可能執行失敗。
例如:
-- 建立資料表 CREATE TABLE account ( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(10), balance DOUBLE ); -- 新增資料 INSERT INTO account (NAME, balance) VALUES ('張三', 1000), ('李四', 1000); -- 模擬張三給李四轉 500 元錢 -- 張三賬號-500 update account set balance = balance - 500 where name='張三'; -- 李四賬號+500 update account set balance = balance + 500 where name='李四';
假設當張三賬號上-500 元,伺服器崩潰了。李四的賬號並沒有+500 元,資料就出現問題了。我們需要保證其中 一條 SQL 語句出現問題,整個轉賬就算失敗。只有兩條 SQL 都成功了轉賬才算成功。這個時候就需要用到事務。
事務執行是一個整體,所有的 SQL 語句都必須執行成功。如果其中有 1 條 SQL 語句出現異常,則所有的 SQL 語句都要回滾,整個業務執行失敗。
2. 事務的使用
MYSQL 中可以有兩種方式進行事務的操作:
- 手動提交事務
- 自動提交事務
2.1 手動提交事務
手動提交事務的 SQL 語句:
- 開啟事務 :start transaction;
- 提交事務 :commit;
- 回滾事務 :rollback;
手動提交事務使用過程:
- 執行成功的情況: 開啟事務 ==>執行多條 SQL 語句 ==> 成功提交事務
- 執行失敗的情況: 開啟事務 ==>執行多條 SQL 語句 ==> 事務的回滾
(1)模擬事務提交成功
-- 第一步:開啟事務 start transaction; -- 第二步:張三賬號-500 update account set balance = balance - 500 where name='張三'; -- 第三步:李四賬號+500 update account set balance = balance + 500 where name='李四'; -- 第四步:事務提交 commit; -- 第五步:查詢資料庫,發現數據發生改變 select * from account;
(2)模擬出現異常事務回滾
-- 第一步:開啟事務
start transaction;
-- 第二步:張三賬號-500
update account set balance = balance - 500 where name='張三';
-- 第三步:假設此時發生異常,李四賬戶沒有+500
-- update account set balance = balance + 500 where name='李四';
-- 第四步:事務回滾
rollback;
-- 第五步:查詢資料庫,發現數據沒有改變
select * from account;
2.2 自動提交事務
MySQL 預設每一條 DML(增刪改)語句都是一個單獨的事務,每條語句都會自動開啟一個事務,語句執行完畢自動提交事務,MySQL 預設開始自動提交事務。
我們用程式碼演示一下自動提交事務:
-- 第一步:張三賬號-500
update account set balance = balance - 500 where name='張三';
-- 第二步:查詢資料庫,發現數據發生改變,證明事務已自動提交
select * from account;
(1)取消自動提交事務
-- 第一步:檢視MySQL 是否開啟自動提交事務
-- @@表示表示全域性變數,查詢結果為1,1 表示開啟,0 表示關閉。
select @@autocommit;
-- 第二步: 取消自動提交事務
set @@autocommit=0;
-- 第三步:張三賬號-500
update account set balance = balance - 500 where name='張三';
-- 第四步:查詢資料庫,發現數據沒有改變,證明已取消事務自動提交
select * from account;
3. 事務的原理
事務開啟之後, 所有的操作都會臨時儲存到事務日誌中, 事務日誌只有在得到 commit 命令才會同步到資料表中,其他任何情況都會清空事務日誌(如rollback,斷開連線) 。
原理圖如下:
由上圖可知事務的實現步驟:
- 客戶端連線資料庫伺服器,建立連線時建立此使用者臨時日誌檔案
- 開啟事務以後,所有的操作都會先寫入到臨時日誌檔案中
- 所有的查詢操作從表中查詢,但會經過日誌檔案加工後才返回
- 如果事務提交則將日誌檔案中的資料寫到表中,否則清空日誌檔案。
4. 事務回滾點
在某些成功的操作完成之後,後續的操作有可能成功有可能失敗,但是不管成功還是失敗,前面操作都已經成 功,可以在當前成功的位置設定一個回滾點。可以供後續失敗操作返回到該位置,而不是返回所有操作,這個點稱 之為回滾點。
回滾點的SQL語句:
- 設定回滾點 :savepoint 回滾點名稱
- 回到回滾點 :rollback to 回滾點名稱
使用示例:
-- 1.開啟事務
start transaction;
-- 2.讓張三賬號減 100 塊
update account set balance = balance - 100 where name='張三';
-- 3.設定回滾點:savepoint first_time;
savepoint first_time;
-- 4.讓張三賬號減 2 次錢,每次 200 塊
update account set balance = balance - 200 where name='張三';
update account set balance = balance - 200 where name='張三';
-- 5.回到回滾點:rollback to first_time;
rollback to first_time;
注意:設定回滾點可以讓我們在失敗的時候回到回滾點,而不是回到事務開啟的時候。
5. 事務的隔離級別
5.1 事務的四大特性(ACID)
瞭解事務的隔離級別前,我們先熟悉一下事務的四大特性(ACID):
5.2 事務的隔離級別
由於事務四大特性中的隔離性,所有的事務之間保持隔離,互不影響。但是因為併發操作,多個使用者同時訪問同一個 資料,可能引發併發訪問的問題:
為了解決併發訪問時引發的問題,各大資料庫都對其進行了相應的處理:
上面的級別最低,下面的級別最高。“是”表示會出現這種問題,“否”表示不會出現這種問題。
注:隔離級別越高,效能越差,安全性越高。
5.3 MySQL 事務隔離級別相關的命令
(1)查詢全域性事務隔離級別
select @@tx_isolation;
(2)設定全域性事務隔離級別
-- 級別字串:read uncommitted、read committed、repeatable read、serializable
set global transaction isolation level 級別字串;
5.4 髒讀演示
mysql預設的隔離級別為repeatable read,可以避免髒讀和幻讀。
為了演示髒讀效果,我們把mysql隔離級別調到最低:read uncommitted
set global transaction isolation level read uncommitted;
(1)開啟A、B兩個查詢視窗,並且都開啟事務
(2)A 視窗更新 2 個人的賬戶資料,未提交
(3)B視窗查詢account表資料
我們發現B視窗讀到了A視窗未提交的資料
(4)A視窗回滾後,B視窗再查詢account表資料
A視窗回滾後,B視窗再查詢,發現錢又回到了原來的樣子。
髒讀是非常危險的,比如張三向李四購買商品,張三開啟事務,向李四賬號轉入 500 塊,然後打電話給李四說錢 已經轉了。李四一查詢錢到賬了,發貨給張三。張三收到貨後回滾事務,李四的再檢視錢沒了。
我們將隔離級別提升至read committed,可以解決髒讀這個問題:
set global transaction isolation level read committed;
5.5 不可重複讀演示
將隔離級別提升至read committed後,髒讀問題解決了。但是還有一個問題我們來看下:
(1)A、B兩視窗都開啟事務後,B視窗先查詢資料
(2)在 A 視窗更新資料並提交後,B視窗再查詢一次
我們發現,在同一事務中,B視窗兩次查詢輸出的結果不同,到底哪次是對的?不知道以哪次為準。 很多人認為這種情況就對了,無須困惑, 當然是後面的為準。我們可以考慮這樣一種情況,比如銀行程式需要將查詢結果分別輸出到電腦螢幕和發簡訊給客 戶,結果在一個事務中針對不同的輸出目的地進行的兩次查詢不一致,導致檔案和螢幕中的結果不一致,銀行工作人員就不知道以哪個為準了。
解決不可重複讀的問題: 將全域性的隔離級別進行提升為repeatable read
即可
set global transaction isolation level repeatable read;
5.6 幻讀演示
幻讀出現在可重複讀(repeatable read)隔離級別下,普通的SELECT查詢就是快照讀,是不會看到別的事務插入的資料的。因此,幻讀在“當前讀”下才會出現。
語法:
-- 在查詢語句後面加上 for update,即為當前讀,可以看到幻讀資料
select count(*) from account for update;
接下來我們來演示一下幻讀:
(1)A、B兩視窗都開啟事務後,B視窗先查詢acount表記錄的數量
(2)然後A視窗插入一條新紀錄並提交事務
(3)B視窗使用當前讀,再次查詢acount表記錄的數量
我們發現,B視窗在同一事務中,兩次讀取到表記錄的數量不一致,此即為幻讀。
要解決幻讀問題,就要將隔離級別進行提升到最高等級:serializable
set global transaction isolation level serializable;
注意:隔離級別越高,資料庫效率也就越低,我們要根據實際需求來選擇合理的隔離級別。