docker 搭建Mycat環境實現Mysql資料庫的讀寫分離
MySQL的事務
本文為《MySQL技術內幕: InnoDB儲存引擎》的閱讀筆記。
前言
事務就是一組原子性的SQL查詢,或者說一個獨立的工作單元。
事務內的語句,要麼全部執行成功,要麼全部執行失敗。
一個執行良好的事務處理系統,具備以下特徵。
- 原子性 (atomicity)
- 一致性 (consistency)
- 隔離性 (isolation)
- 永續性 (durability)
其中,ISO 和 ANIS SQL 標準制定了四種事務隔離級別的標準,但是很少有資料庫廠商遵循這些標準。比如 Oracle 資料庫就不支援 READ_UNCOMMITTED 和 REPEATABLE_READ
事務的實現
注:redo 和 undo 的作用都可以視為一種恢復操作,redo 恢復提交事務修改的頁操作,而 undo 回滾行記錄到某個特定的版本。redo 通常是物理日誌,記錄的是頁的物理修改操作,undo 是邏輯日誌,根據每行記錄進行記錄。
redo
重做日誌有兩部分組成:一是記憶體中的重做日誌緩衝 (redo log buffer),其是易失的;二是重做日誌檔案 (redo log file),其是持久的。
InnoDB 是事務的儲存引擎,當事務提交時,必須先將該事務的所有日誌寫入到重做日誌檔案進行持久化,待事務的提交操作完成才算完成。在 InnoDB
undo
undo log 用來幫助事務回滾以及 MVCC 的功能。redo 存放在重做日誌檔案中,與 redo 不同,undo 存放在資料庫內部的一個特殊段(segment)中,這個段稱為 undo 段。undo 段位於共享表空間內。
undo 是邏輯日誌,因此只是將資料庫邏輯地恢復到原來的樣子。當 InnoDB 儲存引擎回滾時,它實際做的是與先前相反的工作。對於每個 INSERT,InnoDB 儲存引擎會完成一個 DELETE;對於每個 DELETE,InnoDB 儲存引擎會執行一個 INSERT。
在SQL標準中定義了四種隔離級別
undo 的另一個作用是 MVCC,即在 InnoDB 儲存引擎中的 MVCC 的實現是通過 undo 來完成。當用戶讀取一行記錄時,若該記錄已經被其他事務佔用,當前事務可以通過 undo 讀取之前的版本資訊,以此實現非鎖定讀取。
undo log 會產生 redo log。
四種隔離級別分別是:
- 未提交讀 (READ_UNCOMMITTED)
- 提交讀 (READ_COMMITTED)
- 可重複讀 (REPEATABLE_READ)
- 可序列化 (SERIALIZABLE)
未提交讀
READ_UNCOMMITTED 級別,事務中的修改,即使沒有提交,對其他事務也都是可見的。事務可以讀取未提交的資料,也稱為髒讀。從效能上來說,READ_UNCOMMITTED 不會比其他級別好太多,但卻缺乏其他級別的很多好處,一般很少使用。
髒讀
將兩個會話的隔離級別都修改為未提交讀。(我試了將一個會話設定為未提交讀,另一個隔離級別預設,但是沒有效果)
## 初始資料
mysql> SELECT * FROM t_bank;
+----+-------+---------+
| id | name | balance |
+----+-------+---------+
| 1 | Tom | 1600.00 |
| 2 | Cindy | 100.00 |
| 3 | Jerry | 100.00 |
+----+-------+---------+
3 rows in set (0.02 sec)
會話執行例項:下表第5行,兩個事務中,會話B可以讀取到會話A的修改結果。
Time | 會話A | 會話B |
---|---|---|
1 | SELECT @@tx_isolation; | SELECT @@tx_isolation; |
2 | SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; | SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; |
3 | START TRANSACTION; | START TRANSACTION; |
4 | UPDATE t_bank SET balance = balance - 100 WHERE id = 1; | |
5 | SELECT * FROM t_bank; | |
6 | ROLLBACK; | ROLLBACK; |
提交讀
大多數資料庫系統預設隔離級別都是 READ_COMMITTED (但 MySQL 不是)。一個事務從開始直到提交之前,所做的任何修改對其他事務都是不可見的。這個級別有時候也叫做不可重複讀, 因為兩次執行同樣的查詢,可能會得到不一樣的結果。
復現未提交讀
會話執行例項:下表第7行,兩個事務中,會話A讀取不到會話B的修改結果。可是一旦會話B提交了事務,會話A就可以讀取到會話B修改的結果了。
Time | 會話A | 會話B |
---|---|---|
1 | SELECT @@tx_isolation; | SELECT @@tx_isolation; |
2 | SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; | SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; |
3 | START TRANSACTION; | START TRANSACTION; |
4 | SELECT * FROM t_bank; | |
5 | UPDATE t_bank SET balance = balance - 100 WHERE id = 1; | |
6 | SELECT * FROM t_bank; // 1700 | |
7 | SELECT * FROM t_bank; // 1800 | |
8 | COMMIT; | |
9 | SELECT * FROM t_bank; // 1700 | |
8 | ROLLBACK; | ROLLBACK; |
可重複讀
REPEATABLE_READ 解決了髒讀的問題。該級別保證了在同一事務中多次讀取同樣記錄的結果是一致的。但是沒有解決幻讀問題。所謂幻讀,指的是當某個事務在讀取某個範圍內的記錄時,會產生幻行。InnoDB 和 XtraDB 儲存引擎通過多版本併發控制解決了幻讀問題。
可重複讀是 MySQL 的預設事務隔離級別。
驗證 REPEATABLE_READ 解決了可重複讀
會話執行例項:下表第6行,說明解決了髒讀。下表第8行,說明解決了不可重複讀。注意第9行,是在會話B提交的結果上修改的。
Time | 會話A | 會話B |
---|---|---|
1 | SELECT @@tx_isolation; | SELECT @@tx_isolation; |
2 | SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; | SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
3 | START TRANSACTION; | START TRANSACTION; |
4 | SELECT * FROM t_bank; | SELECT * FROM t_bank; |
5 | UPDATE t_bank SET balance = balance + 100 WHERE id = 1; | |
6 | SELECT * FROM t_bank; // 1600 | SELECT * FROM t_bank; // 1700 |
7 | COMMIT; | |
8 | SELECT * FROM t_bank; // 1600 | SELECT * FROM t_bank; // 1700 |
9 | UPDATE t_bank SET balance = balance + 100 WHERE id = 1; | |
10 | SELECT * FROM t_bank; // 1800 | SELECT * FROM t_bank; // 1700 |
11 | COMMIT; | |
12 | SELECT * FROM t_bank; // 1800 |
關於幻讀的操作,在一次事務裡面,多次查詢之後,結果集的個數不一致的情況叫做幻讀,而多出來或者少出來的那一行叫做幻行。
在快照讀的情況下,MySQL 通過 MVCC 來避免幻讀。
在當前讀的情況下,MySQL 通過 next-key 來避免幻讀。
mysql> select * from t_bank;
+----+-------+---------+
| id | name | balance |
+----+-------+---------+
| 1 | Tom | 2100.00 |
| 2 | Cindy | 100.00 |
| 3 | Jerry | 100.00 |
+----+-------+---------+
3 rows in set (0.03 sec)
Time | 會話A | 會話B |
---|---|---|
1 | SELECT @@tx_isolation; | SELECT @@tx_isolation; |
2 | SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; | SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
3 | START TRANSACTION; | |
4 | # 使用當前讀(加共享鎖) SELECT * FROM t_bank LOCK IN SHARE MODE; |
|
5 | START TRANSACTION; | |
6 | INSERT INTO t_bank VALUES(4, "new_man", 500); // 執行此條命令阻塞 | |
7 | ROLLBACK; | 強行停止 |
InnoDB可重複讀隔離級別下如何避免幻讀
RR級別下
- 表象:快照讀(非阻塞讀) -- 偽MVCC
- 內在:next-key鎖 (行鎖+gap鎖)
什麼是當前讀
加了鎖(共享鎖和排他鎖)的增刪改查語句
- 當前讀:
select …… lock in share mode
、select……for update
- 當前讀:
update
、delete
、insert
什麼是快照讀
- 快照讀:不加鎖的非阻塞讀,
select
[非序列化
隔離級別下 ]
RC、RR 級別下的 InnoDB 的非阻塞讀如何實現
資料行裡的DB_TRX_ID
、DB_ROLL_PTR
、DB_ROW_ID
欄位。