我們有一個表,叫做,USER_INVOICE和被插入的地方。該表具有 pk 約束。發票的狀態可以是或。一個用戶在任何時候只能有一張 ACTIVE 發票,但可以有多個 CLOSED 發票。由于無法向表中添加唯一約束,因此我們創建了一個名為的臨時表,該表在添加新發票之前創建新的鎖定條目,并在插入發票并完成對發票的操作后釋放條目鎖定。user_idinvoice_idinvoice_statusACTIVECLOSEDLOCK_TABLE
最近,我看到仍然有一些重復的條目以某種方式添加到USER_INVOICE一些并發的請求中。用戶多次單擊添加發票并創建鎖定,添加活動發票,然后顯示約束違規LOCK_TABLE并顯示錯誤:
"HHH000010: On release of batch it still contained JDBC statements"
之后添加另一個有效發票。我還沒有弄清楚如何添加發票的第二個條目。
uj5u.com熱心網友回復:
一個用戶在任何時候只能有一張 ACTIVE 發票,但可以有多個 CLOSED 發票。由于無法向表添加唯一約束
那么有一種方法可以利用Oracle忽略索引中的s這一事實null,您可以定義一個基于函式的索引,如下所示:
create unique index USER_INVOICE_UX1 on USER_INVOICE( decode(invoice_status,'ACTIVE',user_id));
請注意,在狀態為'ACTIVE'索引鍵的行中,將映射到user_id,因為其他狀態'CLOSED'將被null忽略(未編入索引) - 這允許將唯一性限制為活動狀態。
完整示例
create table USER_INVOICE
(user_id int,
invoice_id int,
invoice_status varchar2(10),
CONSTRAINT CHK_Status CHECK (invoice_status in ('ACTIVE','CLOSED'))
);
alter table USER_INVOICE add primary key (invoice_id);
create unique index USER_INVOICE_UX1 on USER_INVOICE( decode(invoice_status,'ACTIVE',user_id));
-- user_id (1) can have more CLOSED invoices
insert into USER_INVOICE(user_id,invoice_id,invoice_status) values (1,1,'CLOSED');
insert into USER_INVOICE(user_id,invoice_id,invoice_status) values (1,2,'CLOSED');
-- but only one ACTIVE invioce
insert into USER_INVOICE(user_id,invoice_id,invoice_status) values (1,3,'ACTIVE');
insert into USER_INVOICE(user_id,invoice_id,invoice_status) values (1,4,'ACTIVE');
-- ORA-00001: unique constraint (XXXX.USER_INVOICE_UX1) violated
uj5u.com熱心網友回復:
您需要檢查資料庫的所謂事務隔離級別,當然,使用事務。
有4個級別。
您可能想要TRANSACTION_SERIALIZABLE,但請注意這意味著什么。只有兩種方法可以進行可序列化,有點像你不能比光速更快(這不是“沒有人寫過更好的東西”的問題,而是:“這是宇宙的基礎”) :
- 鎖定很多東西,即使只是選擇。這意味著,您的應用程式將非常緩慢。
- 使用樂觀鎖定。這意味著重試。
以下是樂觀鎖定的含義:
當您啟動事務時,沒有其他事務被鎖定。每個執行緒都在做它的事情。當您commit()進行事務時,資料庫會回傳并檢查您是否要及時回傳并表現得像您從未運行過一樣,然后您再次運行您在此事務中運行的每個資料庫陳述句,如果您會得到相同的結果,那么沒關系。如果不這樣做,則提交失敗,回傳代碼:只需..再做一次。這稱為重試。
聽起來很瘋狂?不要敲它——它為以太網供電,通常比“鎖定所有東西”的方法快得多!
換句話說,給定建筑物中的 ATM 和銀行出納員,同時在雙方運行此操作:
- 用戶想要 50 歐元,-
- 開始交易
- 獲取用戶余額
- 減去 50 歐元
- 將此調整后的余額寫回資料庫
- 提交事務
- 交出50歐元,-
那么這里只有 SERIALIZABLE是安全的。要么我們鎖定所有的東西,要么我們使用樂觀鎖定。使用樂觀鎖定,他們都同時做他們的事情,但他們中的一個人將失敗他們的提交,因為“獲取用戶余額”操作將不再回傳與之前相同的結果。
解決方案是您需要在閉包塊中捕獲所有事務,因為您的資料庫“運行者”需要捕獲表示“重試”的 SQLException,然后再次從頂部開始事務。這也意味著所有這樣的代碼都需要是冪等的(它可能運行不止一次)。例如,如果你吐出 €50,- 作為其中的一部分,那不是冪等的(連續吐出 50,- 10 次與做一次不同。與在同一行中寫 'hello' 形成對比資料庫 10 倍一遍又一遍 - 最終結果與這樣做一次沒有什么不同)。我把hand over the 50,-交易放在外面是有原因的——如果你把它放在里面,它可能會被濫用來騙取銀行數百萬美元!
所以,如果你沒有從SERIALIZABLE 開始,你不能“只是”引入它,因為這樣做會破壞大量的東西(起初,日志中的 SQLExceptions 應該通過重試來解決,如果你將所有的資料庫代碼包裝在回圈中,你包裝的代碼不是冪等的問題),或者讓事情變得像糖蜜一樣慢。
然而,替代方案很棘手。正如您所發現的那樣,測驗您是否真正 100% 保護您的應用程式不會出現問題是非常困難的(測驗并發性總是非常非常困難)。在這 4 個級別和資料庫實際執行的操作之間的轉換中丟失了很多東西(例如,JDBC 抽象不知道重試是什么,因此要撰寫代碼來檢測某些 SQLException 是重試,需要為每個資料庫引擎自定義代碼! )。
因此,請回傳您的資料庫檔案并找出如何安全地執行此操作。例如,在較低級別,SELECT * FROM foo FOR UPDATE(該FOR UPDATE部分)會生成讀/寫鎖,當您SELECT在LOCK_TABLE.
總結一下建議:
- 閱讀 SERIALIZABLE 并考慮將所有資料庫互動重寫為重試塊,例如,請參閱JDBI 的主頁,該主頁立即展示了可序列化資料庫互動模型的作業原理(
.withHandle(db -> {...})部分)。了解什么是臟讀、幻讀和不可重復讀。即使您還沒有準備好更改代碼庫以切換到它,也要了解 SERIALIZABLE 的用途。 - 無論你做什么,一切都是一個事務(有一個原因它被呼叫
.setAutoCommit()而不是.setTransactions()- 如果你在沒有事務的情況下運行,新聞快訊:你是,資料庫只是在每個陳述句之后注入一個提交)。 - 除非切換到 SERIALIZABLE 是一個選項,否則
SELECT .. FOR UPDATE請查看 lock_table 上的讀取內容并檢查您的資料庫引擎檔案關于此類事情
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/418570.html
標籤:
上一篇:將Hibernate版本從4.3.11.Final升級到5.6.3.Final導致ClassCastException:java.lang.Integercannotbecasttojava.lang
下一篇:將谷歌表格的標簽下載為CSV檔案
