1.前言
同樣另外一個非常有意思的題目,值得我們思考。大概背景是這個樣子的。如果有一個事務A進行插入 id > 100, 同時另外一個事務B進行更新update id > 100。那麼事務B是否會更新成功。我們來畫一個時序圖:
time | 事務A | 事務B | 備註 |
---|---|---|---|
T1 | insert id > 100 set status = 1 | ||
T2 | update id > 100 set status = 2 | ||
T3 | 最後id > 100 status是為1 還是為2呢 |
2.程式碼
我們從事務的四個隔離性來分別討論這個問題。所有程式碼如下,僅僅是隔離性級別修改。修改是status狀態。
以下所有操作類似於開啟兩個瀏覽器,首先請求事務A,事務A執行過程中,在請求事務B,觀察結果。
- 事務A 進行插入
@RequestMapping("/test/publish/submit")
public String testPublish1() {
log.info("start...");
transactionTemplate.execute(new TransactionCallback<String>() {
@Override
public String doInTransaction(TransactionStatus status) {
for (long i = 1000; i < 3000; i++) {
TElement element = new TElement();
element.setfElementId(i);
element.setfElementName("111");
element.setfElementStatus((byte) 1);
mapper.insertSelective(element);
}
return "OK";
}
});
log.info("end...");
return "ok";
}
- 事務B 進行更新
@RequestMapping("/test/publish/submit2")
public String testPublish2() {
log.info("start...");
transactionTemplate.execute(new TransactionCallback<String>() {
@Override
public String doInTransaction(TransactionStatus status) {
// @Update({"UPDATE t_element SET f_element_status=#{status} WHERE f_element_id > #{elementId}"})
mapper.update(1000L, (byte) 2);
return "OK";
}
});
log.info("end...");
return "ok";
}
2.1讀未提交(READ UNCOMMITTED)級別
經過以上步驟測試,我們得出瞭如下日誌
[INFO 2023-04-13 11:39:29.941] [http-nio-8099-exec-2] [df9c9572-a906-4f5c-91a2-c1fb4a87adcf] - [TransactionInsertUpdateTest.java.testPublishInsert:36] [start...]
[INFO 2023-04-13 11:39:31.708] [http-nio-8099-exec-3] [5651390e-f3be-4d35-81dc-cc7bb0062f40] - [TransactionInsertUpdateTest.java.testPublishUpdate:57] [start...]
[INFO 2023-04-13 11:39:31.760] [http-nio-8099-exec-3] [5651390e-f3be-4d35-81dc-cc7bb0062f40] - [TransactionInsertUpdateTest.java.testPublishUpdate:66] [end...]
[INFO 2023-04-13 11:39:42.952] [http-nio-8099-exec-2] [df9c9572-a906-4f5c-91a2-c1fb4a87adcf] - [TransactionInsertUpdateTest.java.testPublishInsert:50] [end...]
我們可以看到事務A在29秒開始執行,並且在42秒執行完成。
事務B在31秒開始執行,並且沒有阻塞,立刻執行完成。
我們再來觀察以下資料庫中的資料,我們發覺status=1,也就是事務B沒有更新成功
因此在READ UNCOMMITTED下,事務會有問題。
2.2讀已提交(READ COMMITTED)
經過以上步驟測試,我們得出瞭如下日誌。我們可以看到在RC級別下,出現了和READ UNCOMMITTED同樣的現象。
具體原因如上,就是沒有加鎖。
[INFO 2023-04-13 11:46:59.684] [http-nio-8099-exec-3] [a11f29f2-0658-42b4-8eba-28b5f94f9037] - [TransactionInsertUpdateTest.java.testPublishInsert:36] [start...]
[INFO 2023-04-13 11:47:01.029] [http-nio-8099-exec-2] [a9d04f6e-4efe-4ed7-a2df-c2454d8d7946] - [TransactionInsertUpdateTest.java.testPublishUpdate:57] [start...]
[INFO 2023-04-13 11:47:01.090] [http-nio-8099-exec-2] [a9d04f6e-4efe-4ed7-a2df-c2454d8d7946] - [TransactionInsertUpdateTest.java.testPublishUpdate:66] [end...]
[INFO 2023-04-13 11:47:12.574] [http-nio-8099-exec-3] [a11f29f2-0658-42b4-8eba-28b5f94f9037] - [TransactionInsertUpdateTest.java.testPublishInsert:50] [end...]
2.3可重複讀(REPEATABLE READ)
經過以上步驟測試,我們得出瞭如下日誌。注意以下testPublishUpdate日誌和以上日誌的不同。
[INFO 2023-04-13 11:50:47.428] [http-nio-8099-exec-3] [83cac49b-f44e-44bd-a8ef-9d60714016f6] - [TransactionInsertUpdateTest.java.testPublishInsert:36] [start...]
[INFO 2023-04-13 11:50:48.851] [http-nio-8099-exec-2] [f66d62af-aa3f-4d3e-9f66-c97307d6e38e] - [TransactionInsertUpdateTest.java.testPublishUpdate:57] [start...]
[INFO 2023-04-13 11:50:53.872] [http-nio-8099-exec-3] [83cac49b-f44e-44bd-a8ef-9d60714016f6] - [TransactionInsertUpdateTest.java.testPublishInsert:50] [end...]
[INFO 2023-04-13 11:50:53.895] [http-nio-8099-exec-2] [f66d62af-aa3f-4d3e-9f66-c97307d6e38e] - [TransactionInsertUpdateTest.java.testPublishUpdate:66] [end...]
在事務A insert的時候,事務B update的時候阻塞了。這時候其實是間隙鎖發揮了作用,也就是必須等事務A執行完畢之後,事務B才會獲取鎖,去update更新。那這時候就會更新成功了。資料庫資料如下:
2.4序列化
隔離級別最嚴格的級別。同2.3會阻塞然後更新成功。
3.結論
在隔離級別為 讀未提交(READ UNCOMMITTED)以及讀已提交(READ COMMITTED)情況下,會出現事務更新失敗的情況。
本質上就是沒有加鎖導致的,而在RR級別,給事務A加上了間隙鎖,事務B必須等待才能update成功。是用了鎖的的方式來解決的,但可能存在效率的問題。所以鎖儘量細化,比如行鎖 > 間隙鎖 > 記錄鎖 > 表鎖。都是平衡效率以及安全的一種手段。