1. 程式人生 > 實用技巧 >MYSQL的事務管理

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;

手動提交事務使用過程:

  1. 執行成功的情況: 開啟事務 ==>執行多條 SQL 語句 ==> 成功提交事務
  2. 執行失敗的情況: 開啟事務 ==>執行多條 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,斷開連線) 。

原理圖如下:

由上圖可知事務的實現步驟:

  1. 客戶端連線資料庫伺服器,建立連線時建立此使用者臨時日誌檔案
  2. 開啟事務以後,所有的操作都會先寫入到臨時日誌檔案中
  3. 所有的查詢操作從表中查詢,但會經過日誌檔案加工後才返回
  4. 如果事務提交則將日誌檔案中的資料寫到表中,否則清空日誌檔案。

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;

注意:隔離級別越高,資料庫效率也就越低,我們要根據實際需求來選擇合理的隔離級別。