春節臨近,松哥也有點無心撸碼。不過很多時候,很多事情,我們不能由着自己的性子,還是要控制一下自己,這不,松哥最近又打算開一個坑,和大家聊一聊分布式事務,因為我們做微服務,分布式事務肯定是跳不過去的坎。這個坑有點大,年前先更幾篇。
說到分布式事務,很多小夥伴可能會想到 TCC ,但是實際上,分布式事務本身是一個比較大的話題,一步一步從頭開始講,會涉及到資料庫事務、Jdbc 事務、Spring 事務、消息驅動模式處理事務、事件溯源模式等等很多種。
松哥想寫一個類似于 SpringBoot 那樣成體系的教程,來和大家仔細的捋一捋分布式事務,今天我們就先從資料庫事務開始。(另外,大家在公衆号背景分别回複
spring
、
springmvc
、
mybatis
、
springboot
、
maven
可以下載下傳松哥手撸的幹貨教程。)
1. 理論
MySQL 中事務的隔離級别一共分為四種,分别如下:
- 序列化(SERIALIZABLE)
- 可重複讀(REPEATABLE READ)
- 送出讀(READ COMMITTED)
- 未送出讀(READ UNCOMMITTED)
四種不同的隔離級别含義分别如下:
- SERIALIZABLE
如果隔離級别為序列化,則使用者之間通過一個接一個順序地執行目前的事務,這種隔離級别提供了事務之間最大限度的隔離。
- REPEATABLE READ
在可重複讀在這一隔離級别上,事務不會被看成是一個序列。不過,目前正在執行事務的變化仍然不能被外部看到,也就是說,如果使用者在另外一個事務中執行同條 SELECT 語句數次,結果總是相同的。(因為正在執行的事務所産生的資料變化不能被外部看到)。
- READ COMMITTED
READ COMMITTED 隔離級别的安全性比 REPEATABLE READ 隔離級别的安全性要差。處于 READ COMMITTED 級别的事務可以看到其他事務對資料的修改。也就是說,在事務處理期間,如果其他事務修改了相應的表,那麼同一個事務的多個 SELECT 語句可能傳回不同的結果。
- READ UNCOMMITTED
READ UNCOMMITTED 提供了事務之間最小限度的隔離。除了容易産生虛幻的讀操作和不能重複的讀操作外,處于這個隔離級的事務可以讀到其他事務還沒有送出的資料,如果這個事務使用其他事務不送出的變化作為計算的基礎,然後那些未送出的變化被它們的父事務撤銷,這就導緻了大量的資料變化。
在 MySQL 資料庫種,預設的事務隔離級别是 REPEATABLE READ
2. 實戰
接下來通過幾條簡單的 SQL 向讀者驗證上面的理論。
2.1 檢視隔離級别
通過如下 SQL 可以檢視資料庫執行個體預設的全局隔離級别和目前 session 的隔離級别:
SELECT @@GLOBAL.tx_isolation, @@tx_isolation;
查詢結果如圖:

1-1
可以看到,預設的隔離級别為 REPEATABLE-READ,通過如下指令可以修改隔離級别(建議開發者在修改時修改目前 session 隔離級别即可):
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
上面這條 SQL 表示将目前 session 的資料庫隔離級别設定為 READ UNCOMMITTED,設定成功後,再次查詢隔離級别,發現目前 session 的隔離級别已經變了,如圖1-2:
1-2
注意,如果隻是修改了目前 session 的隔離級别,則換一個 session 之後,隔離級别又會恢複到預設的隔離級别
2.2 READ UNCOMMITTED
READ UNCOMMITTED 是最低隔離級别,這種隔離級别中存在髒讀、不可重複讀以及幻象讀問題,下面分别予以介紹。
首先建立一個簡單的表,預設兩條資料,如下:
1-3
表的資料很簡單,有 zhangsan 和 lisi 兩個使用者,兩個人的賬戶各有 1000 人民币。現在模拟這兩個使用者之間的一個轉賬操作。
注意,如果讀者使用的是 Navicat 的話,不同的查詢視窗就對應了不同的 session,如果讀者使用了 SQLyog 的話,不同查詢視窗對應同一個 session,是以如果使用 SQLyog,需要讀者再開啟一個新的連接配接,在新的連接配接種進行查詢操作。
2.2.1 髒讀
一個事務讀到另外一個事務還沒有送出的資料,稱之為髒讀。具體操作如下:
1.首先打開兩個SQL操作視窗,假設分别為 A 和 B,在 A 視窗中輸入如下幾條 SQL (輸入完成後不用執行):
START TRANSACTION;
UPDATE user set account=account+100 where username='zhangsan';
UPDATE user set account=account-100 where username='lisi';
COMMIT;
2.在 B 視窗執行如下 SQL,修改預設的事務隔離級别為 READ UNCOMMITTED,如下:
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
3.接下來在 B 視窗中輸入如下 SQL,輸入完成後,首先執行第一行開啟事務(注意隻需要執行一行即可):
START TRANSACTION;
SELECT * from user;
COMMIT;
4.接下來執行 A 視窗中的前兩條 SQL,即開啟事務,給 zhangsan 這個賬戶添加 100 元。
5.進入到 B 視窗,執行 B 視窗的第二條查詢 SQL(SELECT * from user;),結果如下:
1-4
可以看到,A 視窗中的事務,雖然還未送出,但是 B 視窗中已經可以查詢到資料的相關變化了。
這就是髒讀問題。
2.2.2 不可重複讀
不可重複讀是指一個事務先後讀取同一條記錄,但兩次讀取的資料不同,稱之為不可重複讀。具體操作步驟如下(操作之前先将兩個賬戶的錢都恢複為1000):
1.首先打開兩個查詢視窗 A 和 B ,并且将 B 的資料庫事務隔離級别設定為 READ UNCOMMITTED。具體 SQL 參考上文,這裡不贅述。
2.在 B 視窗中輸入如下 SQL,然後隻執行前兩條 SQL 開啟事務并查詢 zhangsan 的賬戶:
START TRANSACTION;
SELECT * from user where username='zhangsan';
COMMIT;
前兩條 SQL 執行結果如下:
1-5
3.在 A 視窗中執行如下 SQL,給 zhangsan 這個賬戶添加 100 塊錢,如下:
START TRANSACTION;
UPDATE user set account=account+100 where username='zhangsan';
COMMIT;
4.再次回到 B 視窗,執行 B 視窗的第二條 SQL 檢視 zhangsan 的賬戶,結果如下:
1-6
zhangsan 的賬戶已經發生了變化,即前後兩次檢視 zhangsan 賬戶,結果不一緻。不可重複讀強調的是其他事務對資料進行了修改或者删除,這一點注意和幻象讀進行區分。
2.2.3 幻象讀
幻象讀和不可重複讀比較像,強調了不同方面。幻象讀是指當一個事務根據條件查詢資料時,另外一個事務插入一條新記錄,這條新資料恰好可以滿足第一個事務的查詢條件,然後此時再次執行第一個事務,就會看到第二個事務插入的新記錄,這個新記錄就稱為“幻象”。
例如,執行如下SQL查詢目前表的使用者數量:
START TRANSACTION;
SELECT COUNT(*) FROM user;
COMMIT;
擷取到的結果為 2,表示有兩個使用者。
此時在一個新的事務中,向表中添加新記錄,然後再次執行這裡第二行統計 SQL,就會查到三個使用者,這個比較簡單,本文就不示範了。
2.3 READ COMMITTED
和 READ UNCOMMITTED 相比,READ COMMITTED 主要解決了髒讀的問題,對于不可重複讀和幻象讀則未解決。操作案例與上文一緻,這裡不再贅述。
2.4 REPEATABLE READ
和 READ COMMITTED 相比,REPEATABLE READ 進一步解決了不可重複讀的問題,但是幻象讀則未解決。操作案例與上文一緻,這裡不再贅述。
注意,REPEATABLE READ 也是 InnoDB 引擎的預設資料庫事務隔離級别
2.5 SERIALIZABLE
SERIALIZABLE 提供了事務之間最大限度的隔離,在這種隔離級别中,事務一個接一個順序的執行,不會發生髒讀、可不重複讀以及幻象讀問題。最安全。相關操作案例與上文一緻,這裡不再贅述。
3. 總結
總的來說,隔離級别和髒讀、不可重複讀以及幻象讀的對應關系如下:
隔離級别 | 髒讀 | 不可重複讀 | 幻象讀 |
---|---|---|---|
READ UNCOMMITTED | 允許 | 允許 | 允許 |
READ COMMITED | 不允許 | 允許 | 允許 |
REPEATABLE READ | 不允許 | 不允許 | 允許 |
SERIALIZABLE | 不允許 | 不允許 | 不允許 |
性能關系如圖:
1-7
好了,這篇文章就和小夥伴們先說這麼多,大家不妨寫幾行 SQL 試一試。
1. 全棧架構之打包推薦【建議收藏,常讀】
2. 探讨確定消息消費幂等性的幾種方式
3. 分布式系統中Session共享的常用方案
4. Java語言“坑爹”排行榜TOP 10
5. 我是一個Java類(附帶精彩吐槽)
6. mysql索引失效,差點我的工作涼了
7. 既生synchronized,何生volatile?
8. 微服務一直火,為什麼服務化要搞懂?
9. MySQL的COUNT語句,不簡單!
10. 漫畫:HashSet和TreeSet實作與原理
掃碼二維碼關注我
·end·
—如果本文有幫助,請分享到朋友圈吧—
我們一起愉快的玩耍!
你點的每個贊,我都認真當成了喜歡