告別Spring事務(wù)Bug!詳解傳播行為與隔離級別的組合陷阱與最佳實踐
在 Spring 事務(wù)管理中,傳播行為 (Propagation Behavior) 和 隔離級別 (Isolation Level) 是兩個獨立但可以組合使用的核心概念。它們分別解決了不同維度的事務(wù)控制問題:
- 傳播行為 (Propagation Behavior): 定義了一個事務(wù)方法被另一個事務(wù)方法調(diào)用時,事務(wù)應(yīng)該如何傳播。它解決了事務(wù)的作用域和邊界問題(例如,是加入現(xiàn)有事務(wù),還是開啟一個新事務(wù),還是非事務(wù)執(zhí)行)。
- 隔離級別 (Isolation Level): 定義了多個并發(fā)事務(wù)同時訪問和修改相同數(shù)據(jù)時,一個事務(wù)能看到其他事務(wù)未提交或已提交數(shù)據(jù)的程度。它解決了事務(wù)之間的可見性問題,旨在防止臟讀、不可重復(fù)讀、幻讀等并發(fā)問題。
組合方式
關(guān)鍵點:傳播行為和隔離級別是正交的,理論上可以任意組合使用。 沒有固定的“官方組合列表”,因為選擇哪種組合完全取決于你的具體業(yè)務(wù)場景和并發(fā)控制需求。
傳播行為 (7種)
REQUIRED(默認): 如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則創(chuàng)建一個新事務(wù)。SUPPORTS: 如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則以非事務(wù)方式執(zhí)行。MANDATORY: 如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則拋出異常。REQUIRES_NEW: 創(chuàng)建一個新事務(wù),并掛起當(dāng)前事務(wù)(如果存在)。新事務(wù)獨立提交或回滾,不受外部事務(wù)影響。NOT_SUPPORTED: 以非事務(wù)方式執(zhí)行操作,并掛起當(dāng)前事務(wù)(如果存在)。NEVER: 以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。NESTED: 如果當(dāng)前存在事務(wù),則在當(dāng)前事務(wù)內(nèi)創(chuàng)建一個嵌套事務(wù)(保存點)。嵌套事務(wù)可以獨立于外部事務(wù)進行回滾。如果當(dāng)前沒有事務(wù),其行為與REQUIRED相同。
隔離級別 (5種)
DEFAULT(默認): 使用底層數(shù)據(jù)庫的默認隔離級別。對于大多數(shù)數(shù)據(jù)庫(如 MySQL, PostgreSQL),通常是READ_COMMITTED。READ_UNCOMMITTED: 最低的隔離級別。允許讀取其他事務(wù)尚未提交的更改(臟讀)??赡軐?dǎo)致臟讀、不可重復(fù)讀和幻讀。READ_COMMITTED: 保證一個事務(wù)只能讀取到其他事務(wù)已提交的數(shù)據(jù)??煞乐古K讀,但可能出現(xiàn)不可重復(fù)讀和幻讀。這是許多數(shù)據(jù)庫的默認級別。REPEATABLE_READ: 保證在同一個事務(wù)中多次讀取同一數(shù)據(jù)的結(jié)果是一致的(即使其他事務(wù)修改并提交了該數(shù)據(jù))。可防止臟讀和不可重復(fù)讀,但可能出現(xiàn)幻讀(MySQL InnoDB 通過 MVCC 機制在這個級別也避免了幻讀)。SERIALIZABLE: 最高的隔離級別。所有事務(wù)按順序依次執(zhí)行。完全防止臟讀、不可重復(fù)讀和幻讀,但性能開銷最大,并發(fā)性最低。
如何組合與選擇?
你可以為任何使用了 @Transactional 注解的方法(或類)同時指定propagation 和 isolation 屬性。
@Service
publicclass MyService {
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
public void performCriticalUpdate() {
// 這個方法總是在一個新的事務(wù)中執(zhí)行,該事務(wù)的隔離級別是 REPEATABLE_READ
// 外部事務(wù)(如果存在)會被掛起
// ...
}
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED) // 顯式聲明默認值
public void standardOperation() {
// 這個方法加入現(xiàn)有事務(wù)或創(chuàng)建新事務(wù)(REQUIRED),使用 READ_COMMITTED 隔離級別
// ...
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void nonTransactionalOperation() {
// 這個方法以非事務(wù)方式執(zhí)行,不關(guān)心隔離級別(因為沒事務(wù))
// ...
}
}組合時的注意事項
- 傳播行為主導(dǎo)事務(wù)邊界: 傳播行為決定了方法執(zhí)行時是否有事務(wù)、是新事務(wù)還是加入現(xiàn)有事務(wù)。隔離級別只在事務(wù)存在時才生效。對于
NOT_SUPPORTED,NEVER,SUPPORTS(當(dāng)沒有當(dāng)前事務(wù)時) 這些傳播行為,方法內(nèi)的操作是在非事務(wù)上下文中執(zhí)行的,指定的隔離級別將被忽略。 - 新事務(wù)與隔離級別: 當(dāng)傳播行為導(dǎo)致創(chuàng)建新事務(wù)時(如
REQUIRED在無事務(wù)時、REQUIRES_NEW,NESTED),新事務(wù)會使用該方法指定的隔離級別(如果未指定,則使用默認值,通常是DEFAULT-> 數(shù)據(jù)庫默認)。 - 加入現(xiàn)有事務(wù)與隔離級別: 當(dāng)傳播行為導(dǎo)致加入現(xiàn)有事務(wù)時(如
REQUIRED,SUPPORTS,MANDATORY在有事務(wù)時),該方法指定的隔離級別通常會被忽略。它會沿用現(xiàn)有事務(wù)的隔離級別。這是為了保持整個事務(wù)的一致性。你不能在一個已經(jīng)運行的事務(wù)中途改變其隔離級別。 - 嵌套事務(wù) (
NESTED):NESTED在現(xiàn)有事務(wù)內(nèi)創(chuàng)建的是一個保存點。雖然它有自己的“回滾點”,但它仍然運行在外部事務(wù)的隔離級別下。你不能為嵌套事務(wù)指定一個不同于外部事務(wù)的隔離級別。隔離級別作用于整個外部事務(wù)。 - 性能與正確性的權(quán)衡: 隔離級別越高(如
SERIALIZABLE),并發(fā)控制越嚴格,數(shù)據(jù)一致性越好,但性能開銷(鎖競爭、回滾段管理)也越大。傳播行為如REQUIRES_NEW能提供獨立的事務(wù)邊界,但頻繁創(chuàng)建新事務(wù)也有開銷。選擇組合時需要根據(jù)業(yè)務(wù)邏輯對數(shù)據(jù)一致性的要求和對性能的敏感度進行平衡。 - 數(shù)據(jù)庫支持: 并非所有數(shù)據(jù)庫都支持所有的隔離級別或傳播行為(尤其是
NESTED)。例如,Oracle 不支持REPEATABLE_READ,JDBC 規(guī)范也不要求支持NESTED。在使用前需確認你的數(shù)據(jù)庫和 JDBC 驅(qū)動支持所選的組合。
常見的組合場景示例
REQUIRED+READ_COMMITTED(最常見默認組合): 適用于大多數(shù)業(yè)務(wù)邏輯,平衡了數(shù)據(jù)一致性和性能。REQUIRES_NEW+READ_COMMITTED/REPEATABLE_READ:
- 場景: 記錄審計日志、發(fā)送通知消息。即使主業(yè)務(wù)邏輯失敗回滾,這些輔助操作也需要成功提交。
- 說明: 使用
REQUIRES_NEW確保日志/消息獨立提交。隔離級別根據(jù)日志讀取需求選擇,通常READ_COMMITTED足夠。
REQUIRED+REPEATABLE_READ/SERIALIZABLE:
- 場景: 對數(shù)據(jù)一致性要求極高的核心操作,如金融交易、庫存扣減(防止超賣)。
- 說明: 提高隔離級別防止不可重復(fù)讀或幻讀對業(yè)務(wù)邏輯造成的干擾。
SUPPORTS+DEFAULT:
- 場景: 查詢方法。如果有事務(wù)(例如在寫操作中調(diào)用查詢),則加入事務(wù)保證查詢到最新提交的數(shù)據(jù)(
READ_COMMITTED);如果沒有事務(wù),則直接執(zhí)行簡單查詢。
NOT_SUPPORTED+ (忽略):
- 場景: 執(zhí)行一些與事務(wù)無關(guān)、或必須繞過事務(wù)的耗時操作(如文件 I/O、調(diào)用外部非事務(wù) API)。
- 說明: 掛起當(dāng)前事務(wù)(如果存在),避免長事務(wù)持有鎖,提高并發(fā)性。隔離級別無意義。
NESTED+REPEATABLE_READ(理想配合):
- 場景: 一個復(fù)雜操作中的子操作,該子操作可能失敗但不希望導(dǎo)致整個大操作回滾(只需回滾到子操作前的保存點)。
- 說明:
REPEATABLE_READ或SERIALIZABLE能更好地保證在嵌套事務(wù)回滾點之后,外部事務(wù)再次讀取數(shù)據(jù)時的一致性(避免讀到嵌套事務(wù)內(nèi)已回滾的中間狀態(tài))。但需注意數(shù)據(jù)庫支持情況(如 MySQL InnoDB 支持NESTED)。
總結(jié)
Spring 事務(wù)的傳播特性和隔離級別提供了靈活且強大的并發(fā)控制手段。它們可以自由組合使用,沒有預(yù)定義的固定組合列表。選擇哪種組合取決于:
- 業(yè)務(wù)邏輯的事務(wù)邊界需求 (傳播行為): 操作是否需要事務(wù)?需要新事務(wù)還是加入現(xiàn)有事務(wù)?
- 業(yè)務(wù)邏輯的數(shù)據(jù)一致性需求 (隔離級別): 操作對臟讀、不可重復(fù)讀、幻讀的容忍度如何?
- 性能考量: 更高的隔離級別和創(chuàng)建新事務(wù) (
REQUIRES_NEW) 通常帶來更大的開銷。 - 數(shù)據(jù)庫支持: 確認選用的組合在你的數(shù)據(jù)庫和 JDBC 驅(qū)動上有效。
理解每個傳播行為和隔離級別的含義及其組合時的相互作用(特別是關(guān)于新事務(wù)創(chuàng)建和加入現(xiàn)有事務(wù)時隔離級別的處理規(guī)則),是正確配置 Spring 事務(wù)的關(guān)鍵。務(wù)必根據(jù)具體業(yè)務(wù)場景仔細權(quán)衡選擇。





























