深入sql server中的事务
一. 概述... 1
二. 并發(fā)訪問(wèn)的不利影響... 1
1. 臟讀(dirty read)... 1
2. 不可重復(fù)讀(nonrepeatable read)... 1
3. 幻讀(phantom read)... 1
三. 并發(fā)訪問(wèn)的控制機(jī)制... 2
1. 鎖... 2
2. 行版本控制... 2
四. 隔離級(jí)別... 2
五. 事務(wù)... 3
1. 事務(wù)的模式... 3
1.1. 顯式事務(wù)(Explicit Transactions)... 3
1.2. 自動(dòng)提交事務(wù)(Autocommit Transactions)... 4
1.3. 隱式事務(wù)(Implicit Transactions)... 4
2. 事務(wù)的編程... 5
2.1. Transact-SQL腳本... 5
2.2. ADO.NET應(yīng)用程序接口... 5
一. 概述
當(dāng)多個(gè)用戶同時(shí)訪問(wèn)數(shù)據(jù)庫(kù)的同一資源時(shí),叫做并發(fā)訪問(wèn)。如果并發(fā)的訪問(wèn)中有用戶對(duì)數(shù)據(jù)進(jìn)行修改,很可能就會(huì)對(duì)其他訪問(wèn)同一資源的用戶產(chǎn)生不利的影響??赡墚a(chǎn)生的并發(fā)不利影響有以下幾類:臟讀、不可重復(fù)讀和幻讀。
為了避免并發(fā)訪問(wèn)產(chǎn)生的不利影響,sql server設(shè)計(jì)有兩種并發(fā)訪問(wèn)的控制機(jī)制:鎖、行版本控制。
二. 并發(fā)訪問(wèn)的不利影響
并發(fā)訪問(wèn),如果沒(méi)有并發(fā)訪問(wèn)控制機(jī)制,可能產(chǎn)生的不利影響有以下幾種
1. 臟讀(dirty read)
如果一個(gè)用戶在更新一條記錄,這時(shí)第二個(gè)用戶來(lái)讀取這條更新了的記錄,但是第一個(gè)用戶在更新了記錄后又反悔了,不修改了,回滾了剛才的更新。這樣,導(dǎo)致了第二個(gè)用戶實(shí)際上讀取到了一條根本就沒(méi)有存在過(guò)的修改后的記錄。如果第一個(gè)用戶在修改記錄期間,把所修改的記錄鎖住,在修改完成前別的用戶讀取不到記錄,就能避免這種情況。
2. 不可重復(fù)讀(nonrepeatable read)
第一個(gè)用戶在一次事務(wù)中讀取同一記錄兩次,第一次讀取一條記錄后,又有第二個(gè)用戶來(lái)訪問(wèn)這條記錄,并修改了這條記錄,第一個(gè)用戶第二次讀取這條記錄時(shí),得到的是跟第一次不同的數(shù)據(jù)了。如果第一個(gè)用戶在兩次讀取之間鎖住要讀取的記錄,別的用戶不能去修改相應(yīng)的記錄就能避免這種情況。
3. 幻讀(phantom read)
第一個(gè)用戶在一次事務(wù)中兩次讀取同樣滿足條件的一批記錄,第一次讀取一批記錄后,又有第二個(gè)用戶來(lái)訪問(wèn)這個(gè)表,并在這個(gè)表中插入或者刪除了一些記錄,第一個(gè)用戶第二次以同樣條件讀取這批記錄時(shí),可能得到的結(jié)果有些記錄是在第一次讀取時(shí)有,第二次的結(jié)果中沒(méi)有了,或者是第二次讀取的結(jié)果中有的記錄在第一次讀取的結(jié)果中沒(méi)有的。如果第一個(gè)用戶在兩次讀取之間鎖住要讀取的記錄,別的用戶不能去修改相應(yīng)的記錄,也不能增刪記錄,就能避免這種情況。
三. 并發(fā)訪問(wèn)的控制機(jī)制
Sql server中提供了兩種并發(fā)控制的機(jī)制以避免在并發(fā)訪問(wèn)時(shí)可能產(chǎn)生的不利影響。這兩種機(jī)制是:
1. 鎖
每個(gè)事務(wù)對(duì)所依賴的資源(如行、頁(yè)或表)請(qǐng)求不同類型的鎖。鎖可以阻止其他事務(wù)以某種可能會(huì)導(dǎo)致事務(wù)請(qǐng)求鎖出錯(cuò)的方式修改資源。當(dāng)事務(wù)不再依賴鎖定的資源時(shí),它將釋放鎖。
根據(jù)需要鎖定資源的粒度和層次,鎖有許多類型,主要的有幾種:
表類型:鎖定整個(gè)表
行類型:鎖定某個(gè)行
文件類型:鎖定某個(gè)數(shù)據(jù)庫(kù)文件
數(shù)據(jù)庫(kù)類型:鎖定整個(gè)數(shù)據(jù)庫(kù)
頁(yè)類型:鎖定8K為單位的數(shù)據(jù)庫(kù)頁(yè)
鎖的粒度越小,鎖定的范圍越小,對(duì)別的訪問(wèn)的阻塞就越小,但是所用的鎖可能會(huì)比較多,鎖的消耗就比較大。鎖的粒度越大,對(duì)別的訪問(wèn)的阻塞可能性就越大,但是所用的鎖就會(huì)比較少,鎖的消耗就比較小。
對(duì)于編程人員來(lái)說(shuō),不用手工去設(shè)置控制鎖,sql server通過(guò)設(shè)置事務(wù)的隔離級(jí)別自動(dòng)管理鎖的設(shè)置和控制。
Sql server專門管理鎖的是鎖管理器,鎖管理器通過(guò)查詢分析器分析待執(zhí)行的sql語(yǔ)句,來(lái)判斷語(yǔ)句將會(huì)訪問(wèn)哪些資源,進(jìn)行什么操作,然后結(jié)合設(shè)定的隔離級(jí)別自動(dòng)分配管理需要用到的鎖。
2. 行版本控制
當(dāng)啟用了基于行版本控制的隔離級(jí)別時(shí),數(shù)據(jù)庫(kù)引擎 將維護(hù)修改的每一行的版本。應(yīng)用程序可以指定事務(wù)使用行版本查看事務(wù)或查詢開始時(shí)存在的數(shù)據(jù),而不是使用鎖保護(hù)所有讀取。通過(guò)使用行版本控制,讀取操作阻止其他事務(wù)的可能性將大大降低。
四. 隔離級(jí)別
上面提到了,sql server通過(guò)設(shè)置隔離級(jí)別來(lái)控制鎖的使用,從而實(shí)現(xiàn)并發(fā)法訪問(wèn)控制。
Microsoft SQL Server 數(shù)據(jù)庫(kù)引擎支持所有這些隔離級(jí)別:
l 未提交讀(隔離事務(wù)的最低級(jí)別,只能保證不讀取物理上損壞的數(shù)據(jù))
l 已提交讀(數(shù)據(jù)庫(kù)引擎的默認(rèn)級(jí)別)
l 可重復(fù)讀
l 可序列化(隔離事務(wù)的最高級(jí)別,事務(wù)之間完全隔離)
這幾種隔離級(jí)別,對(duì)應(yīng)上面三種并發(fā)訪問(wèn)可能產(chǎn)生的不利影響,分別有不同的效果,見下表:
| 隔離級(jí)別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
| 未提交讀 | 是 | 是 | 是 |
| 已提交讀 | 否 | 是 | 是 |
| 可重復(fù)讀 | 否 | 否 | 是 |
| 快照 | 否 | 否 | 否 |
| 可序列化 | 否 | 否 | 否 |
五. 事務(wù)
事務(wù)是一個(gè)邏輯上的單個(gè)的工作單元,其中可以包括許多操作,但是它們?cè)谶壿嬌鲜且粋€(gè)整體,要么全部完成,要么全部失敗,就好像什么操作都沒(méi)進(jìn)行似的。
事務(wù)是十分可靠堅(jiān)固的機(jī)制,它能保證事務(wù)要么全部完成,要么能全部回滾。
l 鎖:使用鎖的機(jī)制盡可能的保證并發(fā)事務(wù)的隔離性,避免并發(fā)的不利影響。
l 事務(wù)日志:事務(wù)日志記錄著整個(gè)事務(wù)的所有操作步驟,必要的時(shí)候靠日志重新開始事務(wù)或者回滾事務(wù)。不管出現(xiàn)什么狀況,哪怕是網(wǎng)絡(luò)中斷,機(jī)器斷電,甚至是數(shù)據(jù)庫(kù)引擎本身出問(wèn)題了,通過(guò)事務(wù)日志都能保證事務(wù)的完整性。
l 事務(wù)管理:保證一個(gè)事務(wù)的原子性和數(shù)據(jù)的一致性。一個(gè)事務(wù)開始后,它要么成功的完成,要么失敗,回滾到事務(wù)沒(méi)開始前的那個(gè)狀態(tài),事務(wù)開始做的所有修改都將復(fù)原。
1. 事務(wù)的模式
控制事務(wù)的開始結(jié)束的時(shí)間點(diǎn)和事務(wù)的范圍,有幾種事務(wù)模式:
1.1.顯式事務(wù)(Explicit Transactions)
顯式事務(wù)通過(guò)sql腳本的BEGIN TRANSACTION或者編程接口(API)的開始事務(wù)語(yǔ)句啟動(dòng)事務(wù),以sql腳本的COMMIT 或 ROLLBACK語(yǔ)句提交或回滾事務(wù),編程接口(API)的提交事務(wù)或回滾事務(wù)語(yǔ)句結(jié)束事務(wù)。都是通過(guò)顯式的命令控制事務(wù)的開始和結(jié)束。
從事務(wù)開始到事務(wù)提交或者回滾是一個(gè)完整的事務(wù)周期,事務(wù)一旦開始,結(jié)果要么是提交,要么是回滾。
如果事務(wù)范圍內(nèi)發(fā)生錯(cuò)誤,錯(cuò)誤分為幾種類型,不同類型的錯(cuò)誤有不同的行為。
l 嚴(yán)重錯(cuò)誤
比如,客戶端到服務(wù)端的網(wǎng)絡(luò)中斷了,或者客戶的機(jī)器被關(guān)機(jī)了,數(shù)據(jù)引擎會(huì)被通知數(shù)據(jù)連接已中斷,這樣嚴(yán)重的錯(cuò)誤數(shù)據(jù)引擎會(huì)自動(dòng)在服務(wù)端回滾整個(gè)事務(wù)。
l 運(yùn)行時(shí)錯(cuò)誤
語(yǔ)句之間的“GO”命令形成的區(qū)域?yàn)槊钆巍?shù)據(jù)引擎編譯和執(zhí)行語(yǔ)句是以批次為單位的。一次編譯一個(gè)批次的命令,編譯完成后執(zhí)行這個(gè)批次的命令。存儲(chǔ)過(guò)程是整個(gè)被一次編譯的,所以一個(gè)存儲(chǔ)過(guò)程內(nèi)不分批次,整個(gè)過(guò)程就是一個(gè)批次。
大多數(shù)情況下,在一個(gè)批次中一條語(yǔ)句發(fā)生運(yùn)行時(shí)錯(cuò)誤,這個(gè)語(yǔ)句將被中止,同時(shí)同一批次的所有后續(xù)語(yǔ)句也不再執(zhí)行,但同一批次前面已經(jīng)執(zhí)行的命令依然有效。但是可以使用了try…catch捕獲錯(cuò)誤,并進(jìn)行相應(yīng)處理,比如執(zhí)行事務(wù)回滾命令。
有一些運(yùn)行時(shí)錯(cuò)誤,比如插入了一個(gè)主鍵重復(fù)的記錄,只中止當(dāng)前出錯(cuò)的這條語(yǔ)句,后續(xù)的語(yǔ)句照樣繼續(xù)執(zhí)行。這類錯(cuò)誤也能被try…catch捕獲到。
為了保證整個(gè)事務(wù)中,任何語(yǔ)句出現(xiàn)錯(cuò)誤都回滾整個(gè)事務(wù),最簡(jiǎn)單的方法是在事務(wù)開始前設(shè)置SET XACT_ABORT 為 ON,這個(gè)設(shè)置指示數(shù)據(jù)引擎,在一個(gè)事務(wù)中遇到一個(gè)錯(cuò)誤后,不再執(zhí)行后續(xù)的事務(wù),并回滾整個(gè)事務(wù)。
l 編譯錯(cuò)誤
遇到編譯錯(cuò)誤時(shí),錯(cuò)誤語(yǔ)句所在的批次不被執(zhí)行,并不會(huì)受SET XACT_ABORT設(shè)置的影響。
1.2.自動(dòng)提交事務(wù)(Autocommit Transactions)
這個(gè)模式是數(shù)據(jù)引擎的缺省模式,也是各種編程接口的事務(wù)缺省模式。每個(gè)單獨(dú)的語(yǔ)句在完成后被提交,失敗后被回滾,編程人員不需要指定任何命令。
每個(gè)單獨(dú)的語(yǔ)句就是一個(gè)事務(wù)的單位,成功了就提交,這句語(yǔ)句執(zhí)行錯(cuò)誤就回滾這條語(yǔ)句,對(duì)其他語(yǔ)句的執(zhí)行不產(chǎn)生影響。注意這里說(shuō)的執(zhí)行錯(cuò)誤是運(yùn)行時(shí)錯(cuò)誤,如果語(yǔ)句本身有編譯錯(cuò)誤,比如sql語(yǔ)句的關(guān)鍵詞拼寫錯(cuò)誤了,那么發(fā)生編譯錯(cuò)誤語(yǔ)句所在的那個(gè)批次的語(yǔ)句都將不被執(zhí)行。比如:
| USE AdventureWorks; GO CREATE TABLE TestBatch (Cola INT PRIMARY KEY, Colb CHAR(3)); GO INSERT INTO TestBatch VALUES (1, 'aaa'); INSERT INTO TestBatch VALUES (2, 'bbb'); INSERT INTO TestBatch VALUSE (3, 'ccc'); -- Syntax error. GO SELECT * FROM TestBatch; -- Returns no rows. GO |
上面這段sql中的第三個(gè)insert語(yǔ)句values關(guān)鍵字拼寫錯(cuò)誤,將導(dǎo)致編譯錯(cuò)誤,結(jié)果是跟這個(gè)語(yǔ)句在同一批次的所有三條insert語(yǔ)句都將不被執(zhí)行。
如果上面第三個(gè)insert語(yǔ)句是這樣的:
INSERT INTO TestBatch VALUES (1, 'ccc'); -- Duplicate key error.
這將產(chǎn)生一個(gè)運(yùn)行時(shí)錯(cuò)誤“重復(fù)的主鍵”,這條語(yǔ)句將被回滾,但是不影響前面兩條insert語(yǔ)句。從這點(diǎn)可以看出,自動(dòng)提交模式是每條單獨(dú)的語(yǔ)句要么完成要么回滾,不影響其他語(yǔ)句的執(zhí)行。
1.3.隱式事務(wù)(Implicit Transactions)
在SET IMPLICIT_TRANSACTIONS ON命令之后的第一條語(yǔ)句開始,就開始一個(gè)新的事務(wù),直到遇到COMMIT 或 ROLLBACK語(yǔ)句結(jié)束這個(gè)事務(wù),下一個(gè)語(yǔ)句又是一個(gè)新的事務(wù),同樣直到遇到COMMIT 或 ROLLBACK語(yǔ)句結(jié)束這個(gè)事務(wù)。這樣形成了一個(gè)事務(wù)鏈,直到SET IMPLICIT_TRANSACTIONS OFF結(jié)束隱式事務(wù),回到默認(rèn)的自動(dòng)提交事務(wù)模式。
事務(wù)中的行為跟顯式事務(wù)模式是一致的。
事務(wù)體現(xiàn)在connection的水平,一個(gè)connection具有事務(wù)模式,自動(dòng)提交模式是connection的缺省事務(wù)模式,直到BEGIN TRANSACTION語(yǔ)句開始顯式事務(wù)模式,或者隱式事務(wù)被SET IMPLICIT_TRANSACTIONS ON設(shè)置,連接的事務(wù)模式被置為顯式或隱式事務(wù)模式,當(dāng)顯示事務(wù)被提交或者回滾,隱式事務(wù)被置為關(guān)閉后,這個(gè)連接的事務(wù)模式又被置為自動(dòng)提交模式。
2. 事務(wù)的編程
數(shù)據(jù)庫(kù)的編程有兩種方式,一種應(yīng)用程序接口(API),包括ODBC、ADO 、ado.net等等編程接口,一種是Transact-SQL腳本,典型的是存儲(chǔ)過(guò)程。
2.1.Transact-SQL腳本
BEGIN TRANSACTION
標(biāo)記顯式連接事務(wù)的起始點(diǎn)。
COMMIT TRANSACTION 或 COMMIT WORK
如果沒(méi)有遇到錯(cuò)誤,可使用該語(yǔ)句成功地結(jié)束事務(wù)。該事務(wù)中的所有數(shù)據(jù)修改在數(shù)據(jù)庫(kù)中都將永久有效。事務(wù)占用的資源將被釋放。
ROLLBACK TRANSACTION 或 ROLLBACK WORK
用來(lái)回滾遇到錯(cuò)誤的事務(wù)。該事務(wù)修改的所有數(shù)據(jù)都返回到事務(wù)開始時(shí)的狀態(tài)。事務(wù)占用的資源將被釋放。
2.2.ADO.NET應(yīng)用程序接口
對(duì) SqlConnection 對(duì)象使用 BeginTransaction 方法可以啟動(dòng)一個(gè)顯式事務(wù)。若要結(jié)束事務(wù),可以對(duì) SqlTransaction 對(duì)象調(diào)用 Commit() 或 Rollback() 方法。
下面主要以在存儲(chǔ)過(guò)程中使用事務(wù)的編程詳加說(shuō)明
使用事務(wù)的目的是保持一段sql語(yǔ)句執(zhí)行的完整性,要么全部執(zhí)行成功,只要有一條語(yǔ)句失敗就能完全回滾,回到事務(wù)開始前的狀態(tài)。
事務(wù)有起點(diǎn),即通過(guò)BEGIN TRANSACTION啟動(dòng)一個(gè)事務(wù),其后執(zhí)行事務(wù)中的各個(gè)語(yǔ)句,最后要判斷,全部語(yǔ)句執(zhí)行都成功了,就用COMMIT TRANSACTION提交事務(wù),把事務(wù)中執(zhí)行的語(yǔ)句的結(jié)果固定下來(lái);如果事務(wù)中有任何錯(cuò)誤,要能捕獲到錯(cuò)誤,并執(zhí)行ROLLBACK TRANSACTION回滾整個(gè)事務(wù)。
下面是一段示例代碼:
| USE AdventureWorks; BEGIN TRANSACTION; BEGIN TRY -- 產(chǎn)生一個(gè)違反約束的錯(cuò)誤. DELETE FROM Production.Product WHERE ProductID = 980; END TRY BEGIN CATCH SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_SEVERITY() AS ErrorSeverity, ERROR_STATE() as ErrorState, ERROR_PROCEDURE() as ErrorProcedure, ERROR_LINE() as ErrorLine, ERROR_MESSAGE() as ErrorMessage; IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION; END CATCH; IF @@TRANCOUNT > 0 COMMIT TRANSACTION; |
把事務(wù)中要執(zhí)行的語(yǔ)句都放在TRY語(yǔ)句塊中,保證所有語(yǔ)句產(chǎn)生錯(cuò)誤都能被捕獲到。如果事務(wù)中的語(yǔ)句一旦產(chǎn)生錯(cuò)誤,事務(wù)中的后續(xù)語(yǔ)句不再被執(zhí)行,直接跳到CATCH語(yǔ)句塊執(zhí)行,進(jìn)行出錯(cuò)后的后續(xù)處理過(guò)程。
CATCH語(yǔ)句塊中的最主要的工作就是執(zhí)行事務(wù)回滾語(yǔ)句,以回滾整個(gè)事務(wù)。也可以進(jìn)行一些其他輔助性的工作,顯示錯(cuò)誤,記錄錯(cuò)誤等等。
如果事務(wù)中所有語(yǔ)句都沒(méi)有出錯(cuò),順利執(zhí)行完成,程序就跳過(guò)CATCH語(yǔ)句塊,執(zhí)行最后的COMMIT TRANSACTION提交事務(wù)。
經(jīng)常看到有些人使用@@error來(lái)捕獲錯(cuò)誤,判斷是否需要回滾事務(wù),代碼大概如下:
| BEGIN TRANSACTION; Select xxx from yyyy; --事務(wù)中的sql語(yǔ)句 …… If @@error > 0 ROLLBACK TRANSACTION; Else COMMIT TRANSACTION; |
這里使用@@error來(lái)判斷事務(wù)中所有的語(yǔ)句是否發(fā)生錯(cuò)誤,并以此來(lái)決定是回滾事務(wù),還是提交事務(wù)。實(shí)際上這么做是是十分錯(cuò)誤的。
第一,@@error是針對(duì)每個(gè)sql語(yǔ)句執(zhí)行結(jié)果,反映的是當(dāng)前執(zhí)行的語(yǔ)句出錯(cuò)狀態(tài),當(dāng)執(zhí)行到下一句,@@error又被重置以反應(yīng)下一句語(yǔ)句的執(zhí)行結(jié)果。所以用@@error來(lái)判斷所有語(yǔ)句是否出錯(cuò)是不行的。
第二,sql語(yǔ)句的運(yùn)行時(shí)錯(cuò)誤有兩類,一類是語(yǔ)句發(fā)生了錯(cuò)誤,此語(yǔ)句被中止,但后續(xù)語(yǔ)句還能被繼續(xù)執(zhí)行,一類是語(yǔ)句發(fā)生錯(cuò)誤后,一個(gè)命令批次中的后續(xù)的所有語(yǔ)句也不再被執(zhí)行。當(dāng)事務(wù)中的語(yǔ)句發(fā)生這種錯(cuò)誤,那么放在最后的If @@error > 0判斷語(yǔ)句都不會(huì)有機(jī)會(huì)被執(zhí)行了。
這樣的做法可能導(dǎo)致很嚴(yán)重的后果:如果事務(wù)中有語(yǔ)句產(chǎn)生第一類的錯(cuò)誤,后續(xù)語(yǔ)句都不被執(zhí)行,原來(lái)設(shè)計(jì)的ROLLBACK TRANSACTION或COMMIT TRANSACTION都沒(méi)有機(jī)會(huì)被執(zhí)行,就是說(shuō)被這個(gè)事務(wù)鎖了的資源都將得不到釋放,產(chǎn)生的后果是,如果這個(gè)事務(wù)對(duì)某些記錄設(shè)置了共享鎖,那這些記錄再也不能被修改,更慘的是如果這個(gè)事務(wù)對(duì)某些記錄設(shè)置了排他鎖,那么讀取這些記錄的語(yǔ)句一直會(huì)被堵塞,執(zhí)行不下去了。程序也就死在那里了。
所以,在事務(wù)中用來(lái)捕獲語(yǔ)句錯(cuò)誤還是需要使用try…catch語(yǔ)句塊。
總結(jié)
以上是生活随笔為你收集整理的深入sql server中的事务的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: SQL Server在存储过程中编写事务
- 下一篇: Sql Server事务日志