2017-06-16 86 views
2

我正在使用Spring Boot在Java中實現一個REST API。我使用了嵌入式內存數據庫H2幾個星期,但在某些時候我發現事務隔離有問題。Spring JPA + MySQL和死鎖

更確切地說,我有一張表,我需要跟蹤「重複」記錄。重複只是一個記錄,這個記錄對於表格列的定義良好的子集是相同的。所以,基本上,當我插入新記錄時,我首先檢查它是否是重複的,並相應地標記它。一個布爾列「重複」用於此目的。

例如,假設B和C是我檢查的列以定義重複項。這是一個有效的狀態:

| A | B | C | duplicate | | - | - | - | --------- | | x | y | z | false | | z | y | z | true | | x | y | y | false | | x | y | y | true | | y | y | y | true |

,而這是不一個有效狀態:

| A | B | C | duplicate | | - | - | - | --------- | | x | y | z | false | | z | y | z | true | | x | y | y | false | | x | y | y | true | | y | y | y | false |

...因爲行3和行5對兩個B相同的值和C,因此必須將其中的一個標記爲重複。

換句話說,我的要求是將任何恰好已經使用過的值標記爲重複。對於給定的一組值,只有一行將被允許具有duplicate == false

但是,我的基於Spring的實現沒有按預期工作。例如,插入具有相同值的100行應該導致99個重複項,並且只有一個非重複項。但是當我試圖並行執行這些插入時,沒有檢測到很多重複項。

我嘗試了幾個修補程序,並且在某些時候我開始認爲H2沒有正確地實現SERIALIZABLE隔離級別。我創建了一個小應用程序進行論證:

@RestController 
public class NewFooCtrl { 

    @Autowired 
    private FooRepo repo; 

    @RequestMapping(value = "/foo", method = RequestMethod.POST) 
    @Transactional(isolation = Isolation.SERIALIZABLE) 
    public void newFoo(@RequestBody Foo foo) { 
    List<Foo> foos = repo.findByBar(foo.getBar()); 
    if (foos.isEmpty()) foo.setDuplicate(false); 
    else foo.setDuplicate(true); 
    repo.save(foo); 
    } 

} 

注:我省略了明顯的代碼,如模型和信息庫。 Foo模型具有標識符(類型UUID)bar屬性(字符串類型)和duplicate屬性(類型布爾值)。重複檢查基於bar屬性。

隨着H2我有很多錯過重複(通常10%)。使用MySQL我總是有正確的結果(即標記爲重複的行數爲,正好爲 N - 1,N爲插入行數)。唯一的問題是隻有一小部分插入成功(最多從1%到30%)。

我得到了大量死鎖相關的異常。這是爲什麼?這樣簡單的代碼怎麼會導致死鎖。我的意思是,這只是一個選擇,然後是插入。

有什麼建議嗎?

+0

您使用的H2和商店引擎的版本是什麼? – fg78nc

+0

我不知道如何檢查它。我發現了一些MySQL代碼來查詢給定表的引擎,但它似乎沒有在H2上工作。至於版本,我在我的'pom.xml'中列出了依賴項,沒有版本號,所以也許它是最新的(?)。 –

+1

請嘗試添加到數據庫URL'LOCK_MODE = 1; MVCC = TRUE;' – fg78nc

回答

1

我認爲死鎖相關的異常是由我測試演示程序的方式引起的。更確切地說,測試代碼是用JavaScript/Node.js編寫的,當啓動I/O任務時,它的速度非常快,爲。所有交易幾乎同時要求(也許自動同時重試?)。

通過在每個請求之間添加一個非常短的等待時間(例如10毫秒),我獲得了合理的吞吐量和非常低數量的死鎖相關異常。

我的猜測是根本沒有死鎖。只是非常高的鎖爭用,在數據庫級別的某種啓發式解釋爲可能的死鎖。實際上,通過禁用MySQL CLI中的死鎖檢測,我完全消除了那些與死鎖相關的異常(儘管它們被鎖等待超時取代)。

2

應用程序不應檢查事務中的重複密鑰本身。將其留給具有唯一索引的數據庫引擎,如果發生異常則捕獲該異常,並用另一個標識符再次嘗試。

如果您真的想在應用程序級別解決此問題,也許您應該在打開事務後手動鎖定表。隔離級別可以自動爲您執行此操作,但性能成本很高(您可能不想要)。

另一種解決方案是樂觀鎖定,使用@Version註釋,但是您將無法保證標識符的唯一性。


這是很難診斷您的僵局問題,但是當你有遞歸交易(交易在另一個事務中打開)它通常出現。檢查你的豆@Scope,他們可以創建這樣的問題。此外,請確保您只有一個TransactionManager和一個EntityManager bean。

+1

嗨Guillaume,謝謝。在數據庫級別檢查重複項是一個合理的選項,但我更願意將檢查保留在應用程序級別。至於死鎖,我的演示應用程序非常簡單,當然,我的bean沒有嵌套事務或非默認(單例)範圍。我的猜測是,所有那些與死鎖相關的異常都只是一些死鎖檢測啓發式的結果,但沒有實際的死鎖。 –