三大併發異常
| 異常 | 定義 | 生活比喻 |
| 髒讀 |
事務 A 修改未提交時,事務 B 讀取了該修改。A 後來可能回滾。 |
A 未提交代碼發給 B,A 後來撤銷修改,B 看到的代碼不再存在。 |
| 不可重複讀 |
事務 A 兩次讀取同一數據,期間事務 B 修改並提交,導致兩次結果不一致。 |
A 審查代碼時獲取兩次,中間 B 修改了已審查部分,A 發現不一致。 |
| 幻讀 |
事務 A 兩次查詢同一範圍,期間事務 B 新增/刪除滿足條件的行,導致結果集行數變化。 |
A 統計文件數兩次,中間 B 新增文件,兩次統計數不一致。 |
代碼驗證
使用 TransactionScope 指定隔離級別,多線程並發測試:
髒讀測試
[TestMethod] public void DirtyRead_Test()
{
var opt1 = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }; // 寫事務
var opt2 = new TransactionOptions { IsolationLevel = IsolationLevel.ReadUncommitted }; // 讀事務
using (var ts1 = new TransactionScope(TransactionScopeOption.Required, opt1))
{
using (var ctx = new TestEntities()) { ctx.Tables.Add(new Table{Name="段光偉"}); ctx.SaveChanges(); }
var evt = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(_ => {
using (var ts2 = new TransactionScope(TransactionScopeOption.Required, opt2))
using (var ctx = new TestEntities()) { Assert.AreEqual(2, ctx.Tables.Count()); } // 髒讀發生
evt.Set();
});
evt.WaitOne(); // 不 Complete,事務回滾
}
// 回滾後驗證數據未持久化
using (var ctx = new TestEntities()) Assert.AreEqual(1, ctx.Tables.Count());
}
不可重複讀測試
[TestMethod] public void NonRepeatableRead_Test()
{
var opt = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
using (var ts1 = new TransactionScope(TransactionScopeOption.Required, opt))
{
using (var ctx = new TestEntities()) Assert.AreEqual("李妞妞", ctx.Tables.First().Name);
var evt = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(_ => {
using (var ts2 = new TransactionScope(TransactionScopeOption.Required, opt))
using (var ctx = new TestEntities()) { ctx.Tables.First().Name = "段光偉"; ctx.SaveChanges(); ts2.Complete(); }
evt.Set();
});
evt.WaitOne();
using (var ctx = new TestEntities()) Assert.AreEqual("段光偉", ctx.Tables.First().Name); // 不可重複讀
}
}
幻讀測試
[TestMethod] public void PhantomRead_Test()
{
var opt1 = new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead };
var opt2 = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
using (var ts1 = new TransactionScope(TransactionScopeOption.Required, opt1))
{
using (var ctx = new TestEntities()) Assert.AreEqual(1, ctx.Tables.Count());
var evt = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(_ => {
using (var ts2 = new TransactionScope(TransactionScopeOption.Required, opt2))
using (var ctx = new TestEntities()) { ctx.Tables.Add(new Table{Name="段光偉"}); ctx.SaveChanges(); ts2.Complete(); }
evt.Set();
});
evt.WaitOne();
using (var ctx = new TestEntities()) Assert.AreEqual(2, ctx.Tables.Count()); // 幻讀
}
}
四大隔離級別對併發異常的控制能力
| 隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
| Read Uncommitted (讀未提交) | 允許 | 允許 | 允許 |
| Read Committed (讀已提交) | 禁止 | 允許 | 允許 |
| Repeatable Read (可重複讀) | 禁止 | 禁止 | 允許 |
| Serializable (串行化) | 禁止 | 禁止 | 禁止 |
選型建議
- 默認 Read Committed 平衡性能與一致性(SQL Server/Oracle/PostgreSQL 默認)。
- 需防止不可重複讀用 Repeatable Read(MySQL InnoDB 默認,通過 Next-Key Lock 部分防幻讀)。
- 極高一致性要求(如財務對賬)用 Serializable,但並發性能顯著下降。
- 避免使用 Read Uncommitted,除非極度容忍髒數據的分析類查詢。