如果在數(shù)據(jù)庫發(fā)生更換的情況下,只需要修改配置文件的數(shù)據(jù)提供程序名稱這一行即可事務(wù)
事務(wù)是一組必須全部成功或者全部失敗的操作。事務(wù)的目標(biāo)是保證數(shù)據(jù)總能處于有效一致的狀態(tài)。例如,轉(zhuǎn)賬操作。
事務(wù)有 4 個(gè)被稱為 ACID 屬性的特征,ACID 是以下概念的縮寫:
- Atomic(原子性):事務(wù)中所有步驟必須同時(shí)成功或失敗
- Consist(一致性):事務(wù)使底層數(shù)據(jù)庫在穩(wěn)定狀態(tài)間轉(zhuǎn)換
- Isolated(隔離性):每個(gè)事務(wù)都是獨(dú)立的實(shí)體,一個(gè)事務(wù)不應(yīng)該影響同時(shí)運(yùn)行的其他事物。
- Durable(持久性):在事務(wù)成功前,事務(wù)產(chǎn)生的變化永久的存儲在媒質(zhì)上,同時(shí)也必須維護(hù)日志以保證出現(xiàn)硬件故障數(shù)據(jù)庫也能得以恢復(fù)。
這些事事務(wù)的理想特征,它們未必總能達(dá)到。執(zhí)行事務(wù)時(shí) RDBMS 需要鎖定數(shù)據(jù),這樣其他用戶就不能訪問它了。鎖越多,粒度就越大,執(zhí)行事務(wù)時(shí)其他用戶就更不可能完成某些任務(wù)。也就是說,需要在用戶并發(fā)性和隔離性間做出權(quán)衡。
事務(wù)和 ASP.NET 應(yīng)用程序
ASP.NET 可以使用 3 種基本類型的事務(wù):
- 存儲過程事務(wù):這些事務(wù)完全在數(shù)據(jù)庫中處理,存儲過程事務(wù)提供最佳的性能,因?yàn)橹恍柰狄淮螖?shù)據(jù)庫。缺點(diǎn)是需要用 SQL 語句編寫事務(wù)處理邏輯。
- 客戶端引發(fā)(ADO.NET)的事務(wù):這些事務(wù)由代碼通過編程來控制。它們使用和存儲過程事務(wù)一樣的命令,代碼使用了封裝這些細(xì)節(jié)的 ADO.NET 對象。缺點(diǎn)是在事務(wù)開始和提交時(shí)需要額外往返數(shù)據(jù)庫。
- COM+ 事務(wù):COM+ 采用兩步提交協(xié)議,總會帶來額外開銷。只有當(dāng)事務(wù)需要跨越多個(gè)資源管理器的時(shí)候才需要使用 COM+事務(wù)。一個(gè)COM+可以跨越 SQL Server 及 Oracle 數(shù)據(jù)庫中的交互。
盡管ADO.NET提供對事務(wù)的良好支持,但還是不應(yīng)該隨便使用。每次使用都會為系統(tǒng)帶來額外的負(fù)擔(dān)。另外,事務(wù)會鎖定表的某些行,不必要的事務(wù)會損害你的應(yīng)用程序性能。
使用事務(wù)應(yīng)遵循這些實(shí)踐原則以獲得最佳效果:
- 事務(wù)要盡量短
- 不要在事務(wù)中間使用 SELECT 查詢返回?cái)?shù)據(jù)
- 如果事務(wù)中確實(shí)要獲取記錄,應(yīng)該只獲取確實(shí)需要的記錄,這樣可以減少鎖定的資源數(shù)
- 可能的情況下,在存儲過程中使用事務(wù),而不是在 ADO.NET 中使用事務(wù)。這樣的事務(wù)可以被更快的啟動和編譯,因?yàn)閿?shù)據(jù)庫服務(wù)器不需要與客戶端(Web 應(yīng)用程序)交互。
- 避免使用具有多個(gè)獨(dú)立批處理任務(wù)的事務(wù),把各個(gè)批處理任務(wù)作為單個(gè)事務(wù)
- 盡量避免影響大批量記錄的更新
1. 存儲過程事務(wù)
只要可能,事務(wù)存放的最佳地點(diǎn)是存儲過程代碼。它最有可能獲得最佳性能,所有的活動在數(shù)據(jù)源進(jìn)行而不需要任何網(wǎng)絡(luò)通信。總之,事務(wù)的跨度越短,數(shù)據(jù)庫的并發(fā)性越好,被序列化的數(shù)據(jù)庫請求就越少。
下面這段偽代碼演示了如何在兩個(gè)賬戶間轉(zhuǎn)移資金,它是一個(gè)簡化的版本,允許賬戶村礦為負(fù)數(shù):
CREATE Procedure TransferAmount
(
@Amount Money,
@ID_A int,
@ID_B int
)
as
begin transaction
update Accounts set balance = balance + @Amount where AccountID = @ID_A
if(@@error > 0)
GOTO Problem
update Accounts set balance = balance - @Amount where AccountID = @ID_B
if(@@error > 0)
GOTO Problem
-- 沒有錯(cuò)誤
commit
return
-- 有錯(cuò)誤
Problem:
rollback
raiserror('Could not update.',16,1)
當(dāng)在 Transact-SQL 中使用 @@error 值時(shí),必須在每步操作完成后立即檢查該值!!!因?yàn)?@@error 在一條 SQL 語句成功執(zhí)行后自動被重設(shè)為 0 。
如果使用 SQL Server2005 或更新的版本,有一個(gè)更為現(xiàn)代的 try/catch 結(jié)構(gòu)的優(yōu)勢來實(shí)現(xiàn)上面的示例。(GOTO 語句畢竟非議頗多)
CREATE Procedure TransferAmount
(
@Amount Money,
@ID_A int,
@ID_B int
)
as
begin try
begin transaction
update Accounts set balance = balance + @Amount where AccountID = @ID_A
update Accounts set balance = balance - @Amount where AccountID = @ID_B
-- 出錯(cuò)會進(jìn)入 catch 塊,這里可以直接提交
commit
end try
begin catch
if(@@trancount > 0)
rollback
-- 記錄異常信息
declare @ErrMsg nvarchar(4000),@ErrSeverity int
select @ErrMsg = ERROR_MESSAGE(),@ErrSeverity = ERROR_SEVERITY()
raiserror(@ErrMsg,@ErrSeverity,1)
end catch
這個(gè)示例檢查 @@trancount 以確定是否有事務(wù)在進(jìn)行中。(變量 @@trancount 計(jì)算當(dāng)前連接中進(jìn)行的事務(wù)數(shù),begin transaction 語句使之加1,而 rollback 和 commit 使之減1 。)
為了防止錯(cuò)誤被 catch 塊吞噬,使用了 raiserror 語句。 ADO.NET 把該消息封裝成 SqlException 對象,后者需要你在 .NET 中進(jìn)行捕獲。
2. 客戶端引發(fā)的 ADO.NET 事務(wù)
大多數(shù) ADO.NET 數(shù)據(jù)提供程序都提供對數(shù)據(jù)庫事務(wù)的支持。
Transaction 類有兩個(gè)關(guān)鍵方法:
- Commit()
- Rollback()
下面這個(gè)示例演示向 Employees 表中插入兩條記錄:
protected void Page_Load(object sender, EventArgs e)
{
string connStr = WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
SqlConnection conn = new SqlConnection(connStr);
SqlCommand cmd1 = new SqlCommand("insert into Employees(LastName,FirstName) values('Joe','Tester')", conn);
SqlCommand cmd2 = new SqlCommand("insert into Employees(LastName,FirstName) values('Harry','Sulivan')", conn);
SqlTransaction tran = null;
try
{
// Open the connection and create the transaction
conn.Open();
tran = conn.BeginTransaction();
// Enlist two commands in the transaction
cmd1.Transaction = tran;
cmd2.Transaction = tran;
// Execute both commands
cmd1.ExecuteNonQuery();
cmd2.ExecuteNonQuery();
// Commit the transaction
tran.Commit();
}
catch
{
// In the case of error,rool back the transaction
tran.Rollback();
}
finally
{
conn.Close();
}
}
僅僅創(chuàng)建和提交事務(wù)是不夠的,一定要設(shè)置 Command.Transaction 屬性為 Transaction 對象從而把 Command 對象顯式的列入事務(wù)中,事務(wù)進(jìn)行中執(zhí)行一個(gè)不在當(dāng)前事務(wù)中的命令,會產(chǎn)生錯(cuò)誤。
事務(wù)的隔離級別
隔離級別決定了事務(wù)對其他事務(wù)影響的數(shù)據(jù)的敏感度。默認(rèn)情況下,當(dāng)兩個(gè)事務(wù)獨(dú)立運(yùn)行時(shí),在第一個(gè)事務(wù)結(jié)束前,第一個(gè)事務(wù)插入的數(shù)據(jù)對其他事務(wù)不可見。
隔離級別的概念和鎖的概念緊密相關(guān),因?yàn)榇_定事務(wù)的隔離級別就是確定所需的鎖的類型。
- 共享鎖:是事務(wù)讀取數(shù)據(jù)庫中的數(shù)據(jù)時(shí)產(chǎn)生的鎖。當(dāng)表,行,或某個(gè)范圍內(nèi)有共享鎖時(shí),其他事務(wù)就不可用修改相應(yīng)的數(shù)據(jù),但多個(gè)用戶可以使用共享鎖并發(fā)讀取數(shù)據(jù)。
- 獨(dú)占鎖:禁止多個(gè)事務(wù)同時(shí)修改數(shù)據(jù)。當(dāng)事務(wù)更新數(shù)據(jù)且沒有其他事物已經(jīng)鎖定數(shù)據(jù)的同時(shí),會產(chǎn)生獨(dú)占鎖。當(dāng)有獨(dú)占鎖時(shí),其他用戶不能讀取或更新數(shù)據(jù)。
在 SQL Server 存儲過程中,使用 SET TRANSACTION ISOLATION LEVEL 命令來設(shè)置隔離級別。在 ADO.NET 中,可向 Connection.BeginTransaction()方法傳入 IsolationLevel 枚舉值,見下表:
ReadUncommitted 無共享鎖,也不會有獨(dú)占鎖。會導(dǎo)致臟數(shù)據(jù)讀,但可以提升性能。 ReadCommitted 數(shù)據(jù)被事務(wù)讀時(shí)會產(chǎn)生共享鎖,避免了臟數(shù)據(jù)讀,但數(shù)據(jù)在事務(wù)結(jié)束前可能已經(jīng)被修改,這樣可能會產(chǎn)生非重復(fù)讀或虛幻行。(SQL Server 默認(rèn)隔離級別) Snapshot 保存一份事務(wù)正在訪問的數(shù)據(jù)的副本,因此一個(gè)事務(wù)不會看到其他事務(wù)所做的修改。這個(gè)隔離級別減少了堵塞,因?yàn)楫?dāng)其他事務(wù)正在讀取被快照隔離事務(wù)鎖鎖定的數(shù)據(jù)時(shí),它可以從數(shù)據(jù)副本中讀取數(shù)據(jù)。該選項(xiàng)僅被 SQL Server 2005 支持,而且需要通過數(shù)據(jù)庫級別的設(shè)定才能夠啟用。 RepeatableRead 查詢中涉及的所有數(shù)據(jù)均被加上共享鎖。這樣避免了他人修改數(shù)據(jù),同時(shí)也避免了不可重復(fù)的讀,還是可能會出現(xiàn)虛幻行。 Serializable 在所使用的數(shù)據(jù)上的一系列鎖禁止了其他用戶在該范圍內(nèi)更新或插入行。它是唯一可以刪除虛幻行的隔離級別,但是給并發(fā)訪問帶來非常消極的影響,多用戶場景中很少使用。
相關(guān)的數(shù)據(jù)庫術(shù)語:
- 臟讀:讀取了其他尚未提交的事務(wù)中的數(shù)據(jù),但該事務(wù)可能被回滾。
- 不可重復(fù)讀:如果允許不可重復(fù)讀,那么在同一個(gè)事務(wù)中執(zhí)行多次查詢可能會得到不同的數(shù)據(jù)。這是因?yàn)槭聞?wù)在進(jìn)行過程中只讀取數(shù)據(jù)并不能阻止其他用戶修改數(shù)據(jù)。為了防止不可重復(fù)讀,數(shù)據(jù)庫服務(wù)器需要鎖定事務(wù)讀取的行。
- 虛幻行:指在初始讀取中沒有出現(xiàn)但在相同事務(wù)內(nèi)后續(xù)讀取時(shí)出現(xiàn)的行。事務(wù)進(jìn)行過程中其他用戶插入了記錄,就可能出現(xiàn)幻行。為了防止幻行,事務(wù)進(jìn)行過程中查詢數(shù)據(jù)庫時(shí)要根據(jù) WHERE 子句使用一個(gè)范圍鎖。
這些現(xiàn)象究竟是無害的小缺陷還是潛在的錯(cuò)誤取決于你的特定需求。大多數(shù)情況下,不可重復(fù)讀和幻行只是小問題,使用鎖阻止它們發(fā)生并發(fā)的代價(jià)有點(diǎn)太高了,不太值得。ReadCommitted 對于大多數(shù)事務(wù)都適用。看一下不同隔離級別的對比:
隔離級別
臟 讀
不可重復(fù)讀
虛幻數(shù)據(jù)
并發(fā)性
未提交讀(Read uncommitted) 是
是
是
最佳
提交度(Read committed) 否
是
是
好
快照(Snapshot) 否
否
否
好
重復(fù)讀(Repeatable read) 否
否
是
一般
序列化(Serializable) 否
否
否
最差
事務(wù)是一組必須全部成功或者全部失敗的操作。事務(wù)的目標(biāo)是保證數(shù)據(jù)總能處于有效一致的狀態(tài)。例如,轉(zhuǎn)賬操作。
事務(wù)有 4 個(gè)被稱為 ACID 屬性的特征,ACID 是以下概念的縮寫:
- Atomic(原子性):事務(wù)中所有步驟必須同時(shí)成功或失敗
- Consist(一致性):事務(wù)使底層數(shù)據(jù)庫在穩(wěn)定狀態(tài)間轉(zhuǎn)換
- Isolated(隔離性):每個(gè)事務(wù)都是獨(dú)立的實(shí)體,一個(gè)事務(wù)不應(yīng)該影響同時(shí)運(yùn)行的其他事物。
- Durable(持久性):在事務(wù)成功前,事務(wù)產(chǎn)生的變化永久的存儲在媒質(zhì)上,同時(shí)也必須維護(hù)日志以保證出現(xiàn)硬件故障數(shù)據(jù)庫也能得以恢復(fù)。
這些事事務(wù)的理想特征,它們未必總能達(dá)到。執(zhí)行事務(wù)時(shí) RDBMS 需要鎖定數(shù)據(jù),這樣其他用戶就不能訪問它了。鎖越多,粒度就越大,執(zhí)行事務(wù)時(shí)其他用戶就更不可能完成某些任務(wù)。也就是說,需要在用戶并發(fā)性和隔離性間做出權(quán)衡。
事務(wù)和 ASP.NET 應(yīng)用程序
ASP.NET 可以使用 3 種基本類型的事務(wù):
- 存儲過程事務(wù):這些事務(wù)完全在數(shù)據(jù)庫中處理,存儲過程事務(wù)提供最佳的性能,因?yàn)橹恍柰狄淮螖?shù)據(jù)庫。缺點(diǎn)是需要用 SQL 語句編寫事務(wù)處理邏輯。
- 客戶端引發(fā)(ADO.NET)的事務(wù):這些事務(wù)由代碼通過編程來控制。它們使用和存儲過程事務(wù)一樣的命令,代碼使用了封裝這些細(xì)節(jié)的 ADO.NET 對象。缺點(diǎn)是在事務(wù)開始和提交時(shí)需要額外往返數(shù)據(jù)庫。
- COM+ 事務(wù):COM+ 采用兩步提交協(xié)議,總會帶來額外開銷。只有當(dāng)事務(wù)需要跨越多個(gè)資源管理器的時(shí)候才需要使用 COM+事務(wù)。一個(gè)COM+可以跨越 SQL Server 及 Oracle 數(shù)據(jù)庫中的交互。
盡管ADO.NET提供對事務(wù)的良好支持,但還是不應(yīng)該隨便使用。每次使用都會為系統(tǒng)帶來額外的負(fù)擔(dān)。另外,事務(wù)會鎖定表的某些行,不必要的事務(wù)會損害你的應(yīng)用程序性能。
使用事務(wù)應(yīng)遵循這些實(shí)踐原則以獲得最佳效果:
- 事務(wù)要盡量短
- 不要在事務(wù)中間使用 SELECT 查詢返回?cái)?shù)據(jù)
- 如果事務(wù)中確實(shí)要獲取記錄,應(yīng)該只獲取確實(shí)需要的記錄,這樣可以減少鎖定的資源數(shù)
- 可能的情況下,在存儲過程中使用事務(wù),而不是在 ADO.NET 中使用事務(wù)。這樣的事務(wù)可以被更快的啟動和編譯,因?yàn)閿?shù)據(jù)庫服務(wù)器不需要與客戶端(Web 應(yīng)用程序)交互。
- 避免使用具有多個(gè)獨(dú)立批處理任務(wù)的事務(wù),把各個(gè)批處理任務(wù)作為單個(gè)事務(wù)
- 盡量避免影響大批量記錄的更新
1. 存儲過程事務(wù)
只要可能,事務(wù)存放的最佳地點(diǎn)是存儲過程代碼。它最有可能獲得最佳性能,所有的活動在數(shù)據(jù)源進(jìn)行而不需要任何網(wǎng)絡(luò)通信。總之,事務(wù)的跨度越短,數(shù)據(jù)庫的并發(fā)性越好,被序列化的數(shù)據(jù)庫請求就越少。
下面這段偽代碼演示了如何在兩個(gè)賬戶間轉(zhuǎn)移資金,它是一個(gè)簡化的版本,允許賬戶村礦為負(fù)數(shù):
CREATE Procedure TransferAmount
(
@Amount Money,
@ID_A int,
@ID_B int
)
as
begin transaction
update Accounts set balance = balance + @Amount where AccountID = @ID_A
if(@@error > 0)
GOTO Problem
update Accounts set balance = balance - @Amount where AccountID = @ID_B
if(@@error > 0)
GOTO Problem
-- 沒有錯(cuò)誤
commit
return
-- 有錯(cuò)誤
Problem:
rollback
raiserror('Could not update.',16,1)
當(dāng)在 Transact-SQL 中使用 @@error 值時(shí),必須在每步操作完成后立即檢查該值!!!因?yàn)?@@error 在一條 SQL 語句成功執(zhí)行后自動被重設(shè)為 0 。
如果使用 SQL Server2005 或更新的版本,有一個(gè)更為現(xiàn)代的 try/catch 結(jié)構(gòu)的優(yōu)勢來實(shí)現(xiàn)上面的示例。(GOTO 語句畢竟非議頗多)
CREATE Procedure TransferAmount
(
@Amount Money,
@ID_A int,
@ID_B int
)
as
begin try
begin transaction
update Accounts set balance = balance + @Amount where AccountID = @ID_A
update Accounts set balance = balance - @Amount where AccountID = @ID_B
-- 出錯(cuò)會進(jìn)入 catch 塊,這里可以直接提交
commit
end try
begin catch
if(@@trancount > 0)
rollback
-- 記錄異常信息
declare @ErrMsg nvarchar(4000),@ErrSeverity int
select @ErrMsg = ERROR_MESSAGE(),@ErrSeverity = ERROR_SEVERITY()
raiserror(@ErrMsg,@ErrSeverity,1)
end catch
這個(gè)示例檢查 @@trancount 以確定是否有事務(wù)在進(jìn)行中。(變量 @@trancount 計(jì)算當(dāng)前連接中進(jìn)行的事務(wù)數(shù),begin transaction 語句使之加1,而 rollback 和 commit 使之減1 。)
為了防止錯(cuò)誤被 catch 塊吞噬,使用了 raiserror 語句。 ADO.NET 把該消息封裝成 SqlException 對象,后者需要你在 .NET 中進(jìn)行捕獲。
2. 客戶端引發(fā)的 ADO.NET 事務(wù)
大多數(shù) ADO.NET 數(shù)據(jù)提供程序都提供對數(shù)據(jù)庫事務(wù)的支持。
Transaction 類有兩個(gè)關(guān)鍵方法:
- Commit()
- Rollback()
下面這個(gè)示例演示向 Employees 表中插入兩條記錄:
protected void Page_Load(object sender, EventArgs e)
{
string connStr = WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
SqlConnection conn = new SqlConnection(connStr);
SqlCommand cmd1 = new SqlCommand("insert into Employees(LastName,FirstName) values('Joe','Tester')", conn);
SqlCommand cmd2 = new SqlCommand("insert into Employees(LastName,FirstName) values('Harry','Sulivan')", conn);
SqlTransaction tran = null;
try
{
// Open the connection and create the transaction
conn.Open();
tran = conn.BeginTransaction();
// Enlist two commands in the transaction
cmd1.Transaction = tran;
cmd2.Transaction = tran;
// Execute both commands
cmd1.ExecuteNonQuery();
cmd2.ExecuteNonQuery();
// Commit the transaction
tran.Commit();
}
catch
{
// In the case of error,rool back the transaction
tran.Rollback();
}
finally
{
conn.Close();
}
}
僅僅創(chuàng)建和提交事務(wù)是不夠的,一定要設(shè)置 Command.Transaction 屬性為 Transaction 對象從而把 Command 對象顯式的列入事務(wù)中,事務(wù)進(jìn)行中執(zhí)行一個(gè)不在當(dāng)前事務(wù)中的命令,會產(chǎn)生錯(cuò)誤。
事務(wù)的隔離級別
隔離級別決定了事務(wù)對其他事務(wù)影響的數(shù)據(jù)的敏感度。默認(rèn)情況下,當(dāng)兩個(gè)事務(wù)獨(dú)立運(yùn)行時(shí),在第一個(gè)事務(wù)結(jié)束前,第一個(gè)事務(wù)插入的數(shù)據(jù)對其他事務(wù)不可見。
隔離級別的概念和鎖的概念緊密相關(guān),因?yàn)榇_定事務(wù)的隔離級別就是確定所需的鎖的類型。
- 共享鎖:是事務(wù)讀取數(shù)據(jù)庫中的數(shù)據(jù)時(shí)產(chǎn)生的鎖。當(dāng)表,行,或某個(gè)范圍內(nèi)有共享鎖時(shí),其他事務(wù)就不可用修改相應(yīng)的數(shù)據(jù),但多個(gè)用戶可以使用共享鎖并發(fā)讀取數(shù)據(jù)。
- 獨(dú)占鎖:禁止多個(gè)事務(wù)同時(shí)修改數(shù)據(jù)。當(dāng)事務(wù)更新數(shù)據(jù)且沒有其他事物已經(jīng)鎖定數(shù)據(jù)的同時(shí),會產(chǎn)生獨(dú)占鎖。當(dāng)有獨(dú)占鎖時(shí),其他用戶不能讀取或更新數(shù)據(jù)。
在 SQL Server 存儲過程中,使用 SET TRANSACTION ISOLATION LEVEL 命令來設(shè)置隔離級別。在 ADO.NET 中,可向 Connection.BeginTransaction()方法傳入 IsolationLevel 枚舉值,見下表:
ReadUncommitted | 無共享鎖,也不會有獨(dú)占鎖。會導(dǎo)致臟數(shù)據(jù)讀,但可以提升性能。 |
ReadCommitted | 數(shù)據(jù)被事務(wù)讀時(shí)會產(chǎn)生共享鎖,避免了臟數(shù)據(jù)讀,但數(shù)據(jù)在事務(wù)結(jié)束前可能已經(jīng)被修改,這樣可能會產(chǎn)生非重復(fù)讀或虛幻行。(SQL Server 默認(rèn)隔離級別) |
Snapshot | 保存一份事務(wù)正在訪問的數(shù)據(jù)的副本,因此一個(gè)事務(wù)不會看到其他事務(wù)所做的修改。這個(gè)隔離級別減少了堵塞,因?yàn)楫?dāng)其他事務(wù)正在讀取被快照隔離事務(wù)鎖鎖定的數(shù)據(jù)時(shí),它可以從數(shù)據(jù)副本中讀取數(shù)據(jù)。該選項(xiàng)僅被 SQL Server 2005 支持,而且需要通過數(shù)據(jù)庫級別的設(shè)定才能夠啟用。 |
RepeatableRead | 查詢中涉及的所有數(shù)據(jù)均被加上共享鎖。這樣避免了他人修改數(shù)據(jù),同時(shí)也避免了不可重復(fù)的讀,還是可能會出現(xiàn)虛幻行。 |
Serializable | 在所使用的數(shù)據(jù)上的一系列鎖禁止了其他用戶在該范圍內(nèi)更新或插入行。它是唯一可以刪除虛幻行的隔離級別,但是給并發(fā)訪問帶來非常消極的影響,多用戶場景中很少使用。 |
相關(guān)的數(shù)據(jù)庫術(shù)語:
- 臟讀:讀取了其他尚未提交的事務(wù)中的數(shù)據(jù),但該事務(wù)可能被回滾。
- 不可重復(fù)讀:如果允許不可重復(fù)讀,那么在同一個(gè)事務(wù)中執(zhí)行多次查詢可能會得到不同的數(shù)據(jù)。這是因?yàn)槭聞?wù)在進(jìn)行過程中只讀取數(shù)據(jù)并不能阻止其他用戶修改數(shù)據(jù)。為了防止不可重復(fù)讀,數(shù)據(jù)庫服務(wù)器需要鎖定事務(wù)讀取的行。
- 虛幻行:指在初始讀取中沒有出現(xiàn)但在相同事務(wù)內(nèi)后續(xù)讀取時(shí)出現(xiàn)的行。事務(wù)進(jìn)行過程中其他用戶插入了記錄,就可能出現(xiàn)幻行。為了防止幻行,事務(wù)進(jìn)行過程中查詢數(shù)據(jù)庫時(shí)要根據(jù) WHERE 子句使用一個(gè)范圍鎖。
這些現(xiàn)象究竟是無害的小缺陷還是潛在的錯(cuò)誤取決于你的特定需求。大多數(shù)情況下,不可重復(fù)讀和幻行只是小問題,使用鎖阻止它們發(fā)生并發(fā)的代價(jià)有點(diǎn)太高了,不太值得。ReadCommitted 對于大多數(shù)事務(wù)都適用。看一下不同隔離級別的對比:
隔離級別 | 臟 讀 | 不可重復(fù)讀 | 虛幻數(shù)據(jù) | 并發(fā)性 |
未提交讀(Read uncommitted) | 是 | 是 | 是 | 最佳 |
提交度(Read committed) | 否 | 是 | 是 | 好 |
快照(Snapshot) | 否 | 否 | 否 | 好 |
重復(fù)讀(Repeatable read) | 否 | 否 | 是 | 一般 |
序列化(Serializable) | 否 | 否 | 否 | 最差 |
數(shù)據(jù)提供程序無關(guān)的代碼
創(chuàng)建工廠
工廠模型的基本思想是借助一個(gè)單一的工廠對象創(chuàng)建所有需要使用的提供程序相關(guān)的對象。然后就可以通過一組共同的基類以完全通用的方式與這些提供程序相關(guān)的對象交互。
有一個(gè)動態(tài)發(fā)現(xiàn)并創(chuàng)建你需要的工廠的標(biāo)準(zhǔn)類,這個(gè)類是 System.Data.Common.DbProviderFactories 。它提供了一個(gè) GetFactory()方法,這個(gè)方法根據(jù)提供程序的名字返回相應(yīng)的工廠。
string factory = "System.Data.SqlClient";
DbProviderFactory provider = DbProviderFactories.GetFactory(factory);
所有的工廠都是繼承自 DbProviderFactory 的,如果你只是用 DbProviderFactory 的成員,你編寫的代碼就可以喝其他工廠一起正常使用。前面代碼的不足在于傳遞的提供程序名稱的字符串,通常這應(yīng)該配置在 web.config 中。
要使 DbProviderFactory 類正常工作,提供程序要有一個(gè)在 machine.config 或 web.config 中注冊過的工廠。
用工廠創(chuàng)建對象
再次強(qiáng)調(diào),你必須假設(shè)你不知道正在使用的提供程序,這樣你就只能通過標(biāo)準(zhǔn)的基類來與工廠所創(chuàng)建的對象交互。一個(gè)簡單的示例可以幫助你更好的裂解這些零碎的概念是如何協(xié)作的。
在 web.config 文件中為示例配置 連接字符串,提供程序名稱,以及查詢語句:
<configuration>
<connectionStrings>
<add name="Northwind" connectionString="Data Source=localhost;Initial Catalog=Northwind;Integrated Security=SSPI"/>
</connectionStrings>
<appSettings>
<add key="factory" value="System.Data.SqlClient" />
<add key="employeeQuery" value="select * from employees" />
</appSettings>
</configuration>
基于工廠的代碼:
protected void EmployeesQuery()
{
// Get the factory
string factory = WebConfigurationManager.AppSettings["factory"];
DbProviderFactory provider = DbProviderFactories.GetFactory(factory);
// Use this factory to create a connection
DbConnection conn = provider.CreateConnection();
conn.ConnectionString = WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
// Create the command
DbCommand cmd= provider.CreateCommand();
cmd.Connection = conn;
cmd.CommandText = WebConfigurationManager.AppSettings["employeeQuery"];
// Open the connection and get the DataReader
conn.Open();
DbDataReader reader = cmd.ExecuteReader();
} 如果在數(shù)據(jù)庫發(fā)生更換的情況下,只需要修改配置文件的數(shù)據(jù)提供程序名稱這一行即可