MariaDb 在不同 isolation level 下的行為及解決的問題
此練習使用 Sql 與 MySqlWorkBench 練習實作不同的 Isolation Level 對資料造成的影響,分別有 Dirty Data, Non-Repeatable, Phantom Data 的問題.
實驗資料庫的建立
相關程式碼皆在 demo repo 下的 demo-storage-scenarios
資料夾
使用範例程式資料夾 database
下的 docker-compose
啟動設定好的實驗資料庫
docker-compose up TestDB
00 Transaction Commit
基本 Transaction 使用並 Commit.
# step 1
SELECT * FROM User;
原始資料
# step 2
START TRANSACTION;
UPDATE User SET IsActive = 0 WHERE Code = 'Admin';
COMMIT;
SELECT * FROM User;
結果
01 Transaction Rollback
基本 Transaction 使用並 Rollback 放棄變更.
# step 1
SELECT * FROM User;
# step 2
START TRANSACTION;
UPDATE User SET IsActive = 1 WHERE Code = 'Admin';
SELECT * FROM User;
# step 3
ROLLBACK;
SELECT * FROM User;
02 Check current session status
檢查目前連線 session 的 isolation level 與有沒有執行中的 transaction.
# step 1 get current session isolation level setting.
SELECT @@TX_ISOLATION;
# step 2
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT @@TX_ISOLATION;
# step 3
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
# step 4 get current session working transaction.
SELECT @@IN_TRANSACTION;
03 DirtyRead
在 MySqlWorkBench 開啟兩個 Session 連線至資料庫,使用不同的 isolation level 同時操作一樣的資料.
Session B 更改資料,但在未送出 commit 前被 SessionA 使用 ReadUncommitted 讀到髒資料.
- Step 1 Session A
# step 1
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT @@tx_isolation;
SELECT * FROM User;
- Step 2 SessionB
建立一個 transaction 但不結束,觀察另一個 session 的使用狀況.
START TRANSACTION;
UPDATE User SET IsActive = 0 WHERE Code = 'Admin';
- Step 3 SessionA
讀取到尚未 commit 的資料.
SELECT * FROM User WHERE Code = 'Admin;
IsActive: 0
- Step 4 SessionA
改變 SessionA 的 isolation level 驗證該交易只能拿到 committed 過的資料.
SELECT * FROM User WHERE Code = 'Admin;
IsActive: 0
- Step 4 SessionA
改變 SessionA 的 isolation level 驗證該交易只能拿到 committed 過的資料.
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT @@tx_isolation;
SELECT * FROM User WHERE Code = 'Admin;
IsActive: 1
- Step 5 SessionB
將 transaction commit 送出
COMMIT;
- Step 6 SessionA get committed data.
SessionA 會取得 commit 後的資料.
SELECT * FROM User WHERE Code = 'Admin;
IsActive: 0
04 NonRepeatable Read
當兩個交易同時發生,交易 A 讀取完值後該值被交易 B 更改掉,但交易 A 仍未結束,會導致第二次讀取同一個值是已被交易 B 更改的.當資料前後不一致時,可能會造成邏輯判斷的錯誤.
- Step 1 SessionA
COMMIT;
- Step 6 SessionA get committed data.
SessionA 會取得 commit 後的資料.
SELECT * FROM User WHERE Code = 'Admin;
IsActive: 0
04 NonRepeatable Read
當兩個交易同時發生,交易 A 讀取完值後該值被交易 B 更改掉,但交易 A 仍未結束,會導致第二次讀取同一個值是已被交易 B 更改的.當資料前後不一致時,可能會造成邏輯判斷的錯誤.
- Step 1 SessionA
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT @@tx_isolation;
START TRANSACTION;
SELECT * FROM User;
Password: 1E867FA1A3A64AB5E1EE21BD76F05912
- Step 2 SessionB
START TRANSACTION;
UPDATE User SET Password = '0' WHERE Code = 'Test001';
COMMIT;
Password: 0
- Step 3 SessionA
更新時與第一次不一樣的值,結果仍是 SessionB 改的 0
SELECT * FROM User;
COMMIT;
SELECT * FROM User;
commit 前第二次讀取會取到被更改的值.Password: 0
Password: 0
- Step 4 SessionA
使用 REPEATABLE READ 防止此情況發生
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT @@tx_isolation;
UPDATE User SET Password = '1E867FA1A3A64AB5E1EE21BD76F05912' WHERE Code = 'Test001';
START TRANSACTION;
SELECT * FROM User;
Password: 1E867FA1A3A64AB5E1EE21BD76F05912
- Step 5 SessionB
START TRANSACTION;
UPDATE User SET Password = '1' WHERE Code = 'Test001';
COMMIT;
SELECT * FROM User;
Password: 1
- Step 6 SessionA
SELECT * FROM User;、
COMMIT;
SELECT * FROM User;
commit 前第二次讀取會取到跟第一次一樣的值.
Password: 1E867FA1A3A64AB5E1EE21BD76F05912
Password: 1
- Step 7 SessionA
使用lock in share mode
讓 SessionB 等待
UPDATE User SET Password = '1E867FA1A3A64AB5E1EE21BD76F05912' WHERE Code = 'Test001';
START TRANSACTION;
SELECT * FROM User LOCK IN SHARE MODE;
- Step 8 SessionB
START TRANSACTION;
UPDATE User SET Password = '1' WHERE Code = 'Test001';
COMMIT;
- Step 9 SessionA
COMMIT;
05 Phantom Read
當兩個交易進行時,B交易對資料做新增或刪除時,A交易不會知道有關新增刪除的資料.
- Step 1 SessionA
原始資料有三筆
COMMIT;
05 Phantom Read
當兩個交易進行時,B交易對資料做新增或刪除時,A交易不會知道有關新增刪除的資料.
- Step 1 SessionA
原始資料有三筆
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT @@tx_isolation;
UPDATE User SET IsActive = 1 WHERE Code = 'Admin';
START TRANSACTION;
SELECT * FROM User;
- Step 2 SessionB
新增一筆資料
START TRANSACTION;
INSERT INTO User VALUES(4,'testP0', '1E867FA1A3A64AB5E1EE21BD76F05912', 1);
COMMIT;
- Step 3 SessionA
仍是取到三筆資料
SELECT * FROM User;
COMMIT;
- Step 4 SessionA
使用 Serialiazable
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT @@tx_isolation;
START TRANSACTION;
SELECT * FROM User;
- Step 5 SessionB
新增一筆資料,但因為是 Serializable 進入等待.
START TRANSACTION;
INSERT INTO User VALUES(5, 'testP1', '1E867FA1A3A64AB5E1EE21BD76F05912', 1);
COMMIT;
SELECT * FROM User;
- Step 6 SessionA
結束 transaction 後,交易 B 才將資料新增進去.
COMMIT;
SELECT * FROM User;