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




























