Spring 預設不配置事務時,數據庫操作走 autocommit=ON,每條語句獨立提交。配置事務後,可將多個 DB 操作綁定在同一事務中,隔離級別直接影響鎖行為與並發效能。
<property name="transactionAttributes">
<props>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="delete*">PROPAGATION_REQUIRED</prop>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
關鍵注意點:
saveXxx),否則不生效readOnly 會將隔離級別從「提交讀」提升為「可重複讀」,減少鎖開銷| 隔離級別 | 常量 | 髒讀 | 不可重複讀 | 幻讀 | 並發效能 | 默認資料庫 |
|---|---|---|---|---|---|---|
| 讀未提交 | READ_UNCOMMITTED | ✅ 發生 | ✅ 發生 | ✅ 發生 | 最高 | 極少用 |
| 讀已提交 | READ_COMMITTED | ❌ 避免 | ✅ 發生 | ✅ 發生 | 高 | Oracle, SQL Server, PostgreSQL |
| 可重複讀 | REPEATABLE_READ | ❌ 避免 | ❌ 避免 | ⚠️ 可能 (InnoDB Next-Key Lock 解決) | 中 | MySQL InnoDB 默認 |
| 串行化 | SERIALIZABLE | ❌ 避免 | ❌ 避免 | ❌ 避免 | 最低 (全表鎖) | 極少用 |
場景: 本地 MySQL,同一事務內兩次查詢,中間有其他事務修改數據。
| 隔離級別 | 用戶 A 兩次查詢結果 | 鎖行為 |
|---|---|---|
| REPEATABLE_READ (默認) | **不一致** (第二次讀到 B 的修改) | 共享鎖持有到事務結束 |
| READ_COMMITTED | **一致** (第二次讀到 B 提交後的數據) | 語句級共享鎖,讀完即釋放 |
測試數據 (Task 模塊首頁,清緩存後 3 次平均):
結論: 對於**同一事務多次查詢同一結果集、中間無修改**的邏輯,降級到 READ_COMMITTED 可顯著減少鎖持有時間,提升並發。但若查詢邏輯簡單、無重複查詢,效能差異可忽略。
SELECT ... LOCK IN SHARE MODESELECT ... FOR UPDATE, UPDATE/DELETE| 隔離級別 | SELECT (普通) | SELECT ... FOR UPDATE | UPDATE/DELETE |
|---|---|---|---|
| READ_COMMITTED | 無鎖 (MVCC 快照讀) | 行 X 鎖 | 行 X 鎖 (僅鎖匹配行) |
| REPEATABLE_READ | 無鎖 (MVCC 快照讀) | 行 X 鎖 | 行 X 鎖 + **Gap Lock/Next-Key Lock** (防幻讀) |
| SERIALIZABLE | 行 S 鎖 (退化為鎖讀) | 行 X 鎖 | 行 X 鎖 + Gap Lock |
@Transactional(readOnly = true)
public User findById(Long id) { ... }
標註 readOnly=true 後:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED,或 Spring 配置 isolation=ISOLATION_READ_COMMITTEDInnodb_row_lock_waits, Innodb_row_lock_time_avg 關注鎖等待情況