spring事務相關問題記錄
遇到情況:
在本地單體應用調試代碼時,發現在一個加了@transaction注解的方法里進行先更新后查詢的操作,查詢的結果是可以看到更新的內容的。而在微服務環境中同樣的代碼卻在后查詢時查不到前面更新的內容。
偽代碼如下:
@Transactional
public void call() {
bidPlanMapper.updateByPrimaryKey(plan);
List<BidPlan> all = systemService.findBidPlan();
System.out.println(all);//單體應用時可觀察到更改的結果
}
猜測本地可以而微服務上不可以,估計是因為微服務在執行call()方法時,update操作是由biz服務調用的,而findBidPlan()操作是實際是調用system服務進行的(該方法也有@Transactional),故產生的是兩個事務。
處理辦法:
- update操作后立刻提交事務,則另外一個事務可以查詢到處理的結果。
@Autowired
private DataSourceTransactionManager transactionManager;
public void call() {
//開啟新事務
DefaultTransactionDefinition transDefinition = new DefaultTransactionDefinition();
transDefinition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus transStatus = transactionManager.getTransaction(transDefinition);
try {
bidPlanMapper.updateByPrimaryKey(plan);//先提交更新的操作
transactionManager.commit(transStatus);
} catch (Exception e) {
transactionManager.rollback(transStatus);
}
}
擴展閱讀:
隔離級別:
隔離級別定義一個事務可能受其他並發事務活動活動影響的程度。另一種考慮一個事務的隔離級別的方式,是把它想象為那個事務對於事物處理數據的自私程度。
在一個典型的應用程序中,多個事務同時運行,經常會為了完成他們的工作而操作同一個數據。並發雖然是必需的,但是會導致以下問題:
-
臟讀(Dirty read)-- 臟讀發生在一個事務讀取了被另一個事務改寫但尚未提交的數據時。如果這些改變在稍后被回滾了,那么第一個事務讀取的數據就會是無效的。
-
不可重復讀(Nonrepeatable read)-- 不可重復讀發生在一個事務執行相同的查詢兩次或兩次以上,但每次查詢結果都不相同時。這通常是由於另一個並發事務在兩次查詢之間更新了數據。(即不可重復讀到同值)
-
幻影讀(Phantom reads)-- 幻影讀和不可重復讀相似。當一個事務(T1)讀取幾行記錄后,另一個並發事務(T2)插入了一些記錄時,幻影讀就發生了。在后來的查詢中,第一個事務(T1)就會發現一些原來沒有的額外記錄。
隔離級別 | 含義 |
---|---|
ISOLATION_DEFAULT | 使用后端數據庫默認的隔離級別。 |
ISOLATION_READ_UNCOMMITTED | 允許讀取尚未提交的更改。可能導致臟讀、幻影讀或不可重復讀。 |
ISOLATION_READ_COMMITTED | 允許從已經提交的並發事務讀取。可防止臟讀,但幻影讀和不可重復讀仍可能會發生。 |
ISOLATION_REPEATABLE_READ(MySQL默認隔離級別) | 對相同字段的多次讀取的結果是一致的,除非數據被當前事務本身改變。可防止臟讀和不可重復讀,但幻影讀仍可能發生。 |
ISOLATION_SERIALIZABLE | 完全服從ACID的隔離級別,確保不發生臟讀、不可重復讀和幻影讀。這在所有隔離級別中也是最慢的,因為它通常是通過完全鎖定當前事務所涉及的數據表來完成的。 |
1.查看當前會話隔離級別
select @@tx_isolation;
2.查看系統當前隔離級別
select @@gobal.tx_isolation;
模擬read committed隔離級別下出現不可重復讀的狀況:
開啟一個事務A進行三次查詢。事務B在事務A第一和第二次查詢中進行數據更新,事務B在事務A第二次和第三次查詢中進行事務提交。
- 事務A
mysql> SET SESSION TRANSACTION ISOLATION LEVEL read committed; #設置當前會話事務的隔離級別為read committed
mysql> select @@tx_isolation; #查詢當前會話事務的隔離級別
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
mysql> start transaction; #開啟事務A
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t_user; #事務A第一次查詢 (1)
+----+------+----------+-------+
| id | name | password | phone |
+----+------+----------+-------+
| 1 | 張三 | 123 | 000 |
+----+------+----------+-------+
###################### 事務B進行更新操作 ##############################
mysql> select * from t_user; #事務A第二次查詢,沒有看到事務B沒有提交的更新操作。說明已經防止了臟讀問題。(3)
+----+------+----------+-------+
| id | name | password | phone |
+----+------+----------+-------+
| 1 | 張三 | 123 | 000 |
+----+------+----------+-------+
###################### 事務B進行事務提交 ##############################
mysql> select * from t_user; #觀察到事務B提交的結果,說明無法在同個事務中多次查詢值都是一樣的,即無法避免不可重復讀問題。(5)
+----+------+----------+-------+
| id | name | password | phone |
+----+------+----------+-------+
| 1 | 張三 | 123 | 123 |
+----+------+----------+-------+
- 事務B
mysql> start transaction; #開啟事務B
mysql> update t_user set `phone` = '123' where id = 1; #進行更新操作(2)
mysql> commit; #提交事務B (4)
理解REPEATABLE READ 隔離級別下什么是可重復讀:
開啟兩個事務。
- 事務A
mysql> begin;
mysql> select * from t_user; #(1)
+----+------+----------+-------+
| id | name | password | phone |
+----+------+----------+-------+
| 1 | 張三 | 123 | 188 |
+----+------+----------+-------+
###################### 事務B進行事務提交 ##############################
mysql> select * from t_user; #可觀察到兩次查詢結果都一樣,不會因為事務B的更新提交而有影響(3)
+----+------+----------+-------+
| id | name | password | phone |
+----+------+----------+-------+
| 1 | 張三 | 123 | 188 |
+----+------+----------+-------+
- 事務B
mysql> begin;
mysql> update `t_user` set `phone` = '8888'; #(2)
mysql> commit; #(2)
雖然該隔離級別下是,無論事務B進行更新、新增、刪除,在事務A中仍然可以重復讀到相同值的。
但是如果事務A進行更新操作,則可以對事務B新增的數據進行直接更新。
可參考如下例子:
在REPEATABLE READ 隔離級別下,MySQL在session1執行UPDATE語句的時候對於session2的INSERT語句是可以看到的,也就是說發生了幻讀。
相關閱讀:MySQL在REPEATABLE READ 隔離級別下的工作方式