數據庫中事務機制的進階使用(整理)

       在前面的兩篇blog中,我寫了些關於數據庫中的鎖方面的一些內容,實際上鎖是和事務緊密聯繫的,在數據庫中事務這一塊是很重要,也是比較複雜的,而且各個數據庫產品的實現也不太相同,所以有必要在這裏詳細描述一下,雖然這些很細緻的內容我們在實際編程時用到的概率不是很大,但畢竟知道一些還是比較好的,萬一有用的到的時候就能解決大問題了。下面是一些google到的內容的整理和總結。

一 事務的屬性

事務具有ACID屬性
即 Atomic原子性, Consistent一致性, Isolated隔離性, Durable永久性

原子性

就是事務應作爲一個工作單元,事務處理完成,所有的工作要麼都在數據庫中保存下來,要麼完全
回滾,全部不保留

一致性
事務完成或者撤銷後,都應該處於一致的狀態

隔離性

多個事務同時進行,它們之間應該互不干擾.應該防止一個事務處理其他事務也要修改的數據時,
不合理的存取和不完整的讀取數據

永久性
事務提交以後,所做的工作就被永久的保存下來

二 事務併發處理會產生的問題

丟失更新

當兩個或多個事務選擇同一行,然後基於最初選定的值更新該行時,會發生丟失更新問題、
每個事務都不知道其它事務的存在。最後的更新將重寫由其它事務所做的更新,這將導致數據丟失。

髒讀
當第二個事務選擇其它事務正在更新的行時,會發生未確認的相關性問題。
第二個事務正在讀取的數據還沒有確認並且可能由更新此行的事務所更改。

不可重複讀

當第二個事務多次訪問同一行而且每次讀取不同的數據時,會發生不一致的分析問題。
不一致的分析與未確認的相關性類似,因爲其它事務也是正在更改第二個事務正在讀取的數據。
然而,在不一致的分析中,第二個事務讀取的數據是由已進行了更改的事務提交的。而且,不一致的分析涉及多次(兩次或更多)讀取同一行,而且每次信息都由其它事務更改;因而該行被非重複讀取。

幻像讀

當對某行執行插入或刪除操作,而該行屬於某個事務正在讀取的行的範圍時,會發生幻像讀問題。
事務第一次讀的行範圍顯示出其中一行已不復存在於第二次讀或後續讀中,因爲該行已被其它事務刪除。同樣,由於其它事務的插入操作,事務的第二次或後續讀顯示有一行已不存在於原始讀中。

三 事務處理類型

自動處理事務

系統默認每個T-SQL命令都是事務處理 由系統自動開始並提交

隱式事務

當有大量的DDL 和DML命令執行時會自動開始,並一直保持到用戶明確提交爲止,切換隱式事務可以用SET IMPLICIT_TRANSACTIONS
爲連接設置隱性事務模式.當設置爲 ON 時,SET IMPLICIT_TRANSACTIONS 將連接設置爲隱性事務模式。當設置爲 OFF 時,則使連接返回到自動提交事務模式

用戶定義事務

由用戶來控制事務的開始和結束 命令有: begin tran commit tran rollback tran 命令

分佈式事務
跨越多個服務器的事務稱爲分佈式事務,sql server 可以由DTc microsoft distributed transaction coordinator
來支持處理分佈式事務,可以使用 BEgin distributed transaction 命令啓動一個分佈式事務處理

四 事務處理的隔離級別

使用SET TRANSACTION ISOLATION LEVEL來控制由連接發出的所有語句的默認事務鎖定行爲

從低到高依次是

READ UNCOMMITTED

執行髒讀或 0 級隔離鎖定,這表示不發出共享鎖,也不接受排它鎖。當設置該選項時,可以對數據執行未提交讀或髒讀;在事務結束前可以更改數據內的數值,行也可以出現在數據集中或從數據集消失。該選項的作用與在事務內所有語句中的所有表上設置 NOLOCK 相同。這是四個隔離級別中限制最小的級別。

舉例

設table1(A,B,C)
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3

新建兩個連接
在第一個連接中執行以下語句
select * from table1
begin tran
update table1 set c='c'
select * from table1
waitfor delay '00:00:10' --等待10秒
rollback tran
select * from table1

在第二個連接中執行以下語句
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
print '髒讀'
select * from table1
if @@rowcount>0
begin
waitfor delay '00:00:10'
print '不重複讀'
select * from table1
end

第二個連接的結果

髒讀
A B C
a1 b1 c
a2 b2 c
a3 b3 c

'不重複讀'
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3

READ COMMITTED

指定在讀取數據時控制共享鎖以避免髒讀,但數據可在事務結束前更改,從而產生不可重複讀取或幻像數據。該選項是 SQL Server 的默認值。

在第一個連接中執行以下語句
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
begin tran
print '初始'
select * from table1
waitfor delay '00:00:10' --等待10秒
print '不重複讀'
select * from table1
rollback tran

在第二個連接中執行以下語句
SET TRANSACTION ISOLATION LEVEL READ COMMITTED

update table1 set c='c'

第一個連接的結果

初始
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3

不重複讀
A B C
a1 b1 c
a2 b2 c
a3 b3 c

REPEATABLE READ

鎖定查詢中使用的所有數據以防止其他用戶更新數據,但是其他用戶可以將新的幻像行插入數據集,且幻像行包括在當前事務的後續讀取中。因爲併發低於默認隔離級別,所以應只在必要時才使用該選項。

在第一個連接中執行以下語句
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
begin tran
print '初始'
select * from table1
waitfor delay '00:00:10' --等待10秒
print '幻像讀'
select * from table1
rollback tran

在第二個連接中執行以下語句
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
insert table1 select 'a4','b4','c4'

第一個連接的結果

初始
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3

幻像讀
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3
a4 b4 c4

SERIALIZABLE

在數據集上放置一個範圍鎖,以防止其他用戶在事務完成之前更新數據集或將行插入數據集內。這是四個隔離級別中限制最大的級別。因爲併發級別較低,所以應只在必要時才使用該選項。該選項的作用與在事務內所有 SELECT 語句中的所有表上設置 HOLDLOCK 相同。

在第一個連接中執行以下語句
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
begin tran
print '初始'
select * from table1
waitfor delay '00:00:10' --等待10秒
print '沒有變化'
select * from table1
rollback tran

在第二個連接中執行以下語句
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
insert table1 select 'a4','b4','c4'

第一個連接的結果

初始
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3

沒有變化
A B C
a1 b1 c1
a2 b2 c2
a3 b3 c3

五 事務處理嵌套的語法和對@@TRANCOUNT的影響

BEGIN TRAN @@TRANCOUNT+1
COMMIT TRAN @@TRANCOUNT-1
ROLLBACK TRAN

 下面再給一些具體的例子

--建立測試表的語句
CREATE TABLE 帳戶表
(
帳號 CHAR(4),
餘額 INT
)
GO
INSERT 帳戶表
SELECT 'A',100
UNION ALL
SELECT 'B',200

1、排它鎖測試

--在第一個連接中執行以下語句
BEGIN TRAN
UPDATE 帳戶表 SET 餘額=300 WHERE 帳號='A'
WAITFOR DELAY '00:00:10' --等待10秒
COMMIT TRAN

--在第二個連接中執行以下語句
BEGIN TRAN
SELECT * FROM 帳戶表 WHERE 帳號<>'A'
COMMIT TRAN

/**說明:
若同時執行上述兩個語句,只要第一句更新的行數大於零,則第二句必須等待10秒。
疑問:如果帳戶A的餘額本來就是300,第二個連接也會立即返回
如果連接二中把SELECT * FROM 帳戶表 改成 SELECT * FROM 帳戶表 WITH ( NOLOCK),讀取任何記錄,都不需要等待
或者在連接二使用READ UNCOMMITTED隔離級別也可

執行語句一時,系統會在帳戶表上使用TABLOCKX排它鎖。該鎖可以防止其它事務讀取或更新表,並在語句或事務結束前一直持有。
NOLOCK選項:該選項不發出共享鎖,並且不要提供排它鎖。當此選項生效時,可能會讀取未提交的事務或一組在讀取中間回滾的頁面。有可能發生髒讀。僅應用於 SELECT 語句。
**/


2、
--在第一個連接中執行以下語句
BEGIN TRAN
SELECT * FROM 帳戶表 WHERE 帳號='A'
WAITFOR DELAY '00:00:10'
COMMIT TRAN

--在第二個連接中執行以下語句
--A句
UPDATE 帳戶表 SET 餘額=10 WHERE 帳號='A' --要等待10秒,從而避免非重複讀
--B句
UPDATE 帳戶表 SET 餘額=10 WHERE 帳號='B' --不需要等待,立即執行

--如果想在連接一中鎖住整個表,不允許其他事務更新表中任何記錄,但可以讀取記錄,可使用HOLDLOCK選項,即(HOLDLOCK 等同於 SERIALIZABLE)
BEGIN TRAN
SELECT * FROM 帳戶表 WITH(HOLDLOCK) WHERE 帳號='A'
WAITFOR DELAY '00:00:10'
COMMIT TRAN

--如果想在連接一中鎖住整個表,不允許其他事務更新表中任何記錄甚至讀取表中任何記錄,可使用TABLOCKX選項,即(SET TRANSACTION ISOLATION LEVEL SERIALIZABLE做不到)
BEGIN TRAN
SELECT * FROM 帳戶表 WITH(TABLOCKX) WHERE 帳號='A'
WAITFOR DELAY '00:00:10'
COMMIT TRAN


--如果想在連接一中不鎖定表,允許其他事務更新表中任何行,使用NOLOCK選項,即
BEGIN TRAN
SELECT * FROM 帳戶表 WITH(NOLOCK) WHERE 帳號='A'
WAITFOR DELAY '00:00:10'
COMMIT TRAN


SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN
SELECT * FROM 帳戶表 WITH (ROWLOCK) WHERE 帳號='A'
WAITFOR DELAY '00:00:10'
COMMIT TRAN



3、死鎖測試
--增設 帳戶表_2
CREATE TABLE 帳戶表_2
(
帳號 CHAR(4),
餘額 INT
)
GO
INSERT 帳戶表_2
SELECT 'C',100
UNION ALL
SELECT 'D',200


--在第一個連接中執行以下語句
BEGIN TRAN
UPDATE 帳戶表 SET 餘額=3 WHERE 帳號='A'
WAITFOR DELAY '00:00:10'
UPDATE 帳戶表_2 SET 餘額=3 WHERE 帳號='C'
COMMIT TRAN

--在第二個連接中執行以下語句
BEGIN TRAN
UPDATE 帳戶表_2 SET 餘額=4 WHERE 帳號='C'
WAITFOR DELAY '00:00:10'
UPDATE 帳戶表 SET 餘額=4 WHERE 帳號='A'
COMMIT TRAN

--刪除測試表
DROP TABLE 帳戶表,帳戶表_2

--同時執行,系統會檢測出死鎖,第一個連接的事務可能正常執行,SQL Server 終止第二個連接的事務(不涉及超時)。
--如果沒有出現死鎖,則在其它事務釋放鎖之前,請求鎖的事務被阻塞。
--LOCK_TIMEOUT 設置允許應用程序設置語句等待阻塞資源的最長時間。

4、更新數據時候允許進行插入
5、插入數據時不允許更新/讀取

上面我們是以sql server2000爲藍本講解的,實際上其和oracle在這方面還是有些不同的。

一.事務設置及類型的區別

在SQL Server中有三種事務類型,分別是:隱式事務、顯式事務、自動提交事務,缺省爲自動提交。

自動提交,是指對於用戶發出的每條SQL語句,SQL Server都會自動開始一個事務,並且在執行後自動進行提交操作來完成這個事務,也可以說在這種事務模式下,一個SQL語句就是一個事務。

顯式事務,是指在自動提交模式下以Begin Transaction開始一個事務,以Commit或Rollback結束一個事務,以Commit結束事務是把事務中的修改永久化,即使這時發生斷電這樣的故障。例如下面是SQL Server中的一個顯式事務的例子。

Begin Tran

Update emp Set ename=’Smith’ Where empno=7369

Insert Into dept Values(60,’HR’,’GZh’)

Commit

隱式事務,是指在當前會話中用Set Implicit_Transactions On命令設置的事務類型,這時任何DML語句(Delete、Update、Insert)都會開始一個事務,而事務的結束也是用Commit或Rollback。

在Oracle中沒有SQL Server的這些事務類型,缺省情況下任何一個DML語句都會開始一個事務,直到用戶發出Commit或Rollback操作,這個事務纔會結束,這與SQL Server的隱式事務模式相似。

二.事務隔離級別

在SQL92標準中,事務隔離級別分爲四種,分別爲:Read Uncommitted、Read Committed、Read Repeatable、Serializable,其中Read Uncommitted與Read Committed爲語句級別的,而Read Repeatable與Serializable是針對事務級別的。

在Oracle和SQL Server中設置事務隔離級別的語句是相同的,都使用SQL92標準語法,即:

Set Transaction Isolation Level Read Committed

上面示例中的Read Committed可以被替換爲其他三種隔離級別中的任意一種。

1.SQL Server中的隔離級別及實現機制

在SQL Server中提供了所有這四種隔離級別。

下面我們討論在SQL Server中,這幾種隔離級別的含義及其實現方式。

Read Uncommitted:一個會話可以讀取其他事務未提交的更新結果,如果這個事務最後以回滾結束,這時的讀取結果就可能是錯誤的,所以多數的數據庫應用都不會使用這種隔離級別。

Read Committed:這是SQL Server的缺省隔離級別,設置爲這種隔離級別的事務只能讀取其他事務已經提交的更新結果,否則,發生等待,但是其他會話可以修改這個事務中被讀取的記錄,而不必等待事務結束,顯然,在這種隔離級別下,一個事務中的兩個相同的讀取操作,其結果可能不同。

Read Repeatable:在一個事務中,如果在兩次相同條件的讀取操作之間沒有添加記錄的操作,也沒有其他更新操作導致在這個查詢條件下記錄數增多,則兩次讀取結果相同。換句話說,就是在一個事務中第一次讀取的記錄保證不會在這個事務期間發生改變。SQL Server是通過在整個事務期間給讀取的記錄加鎖實現這種隔離級別的,這樣,在這個事務結束前,其他會話不能修改事務中讀取的記錄,而只能等待事務結束,但是SQL Server不會阻礙其他會話向表中添加記錄,也不阻礙其他會話修改其他記錄。

Serializable:在一個事務中,讀取操作的結果是在這個事務開始之前其他事務就已經提交的記錄,SQL Server通過在整個事務期間給表加鎖實現這種隔離級別。在這種隔離級別下,對這個表的所有DML操作都是不允許的,即要等待事務結束,這樣就保證了在一個事務中的兩次讀取操作的結果肯定是相同的。

2.Oracle中的隔離級別及實現機制

在Oracle中,沒有Read Uncommitted及Repeatable Read隔離級別,這樣在Oracle中不允許一個會話讀取其他事務未提交的數據修改結果,從而避免了由於事務回滾發生的讀取錯誤。Oracle中的Read Committed和Serializable級別,其含義與SQL Server類似,但是實現方式卻大不一樣。

在Oracle中,存在所謂的回滾段(Oracle9i之前版本)或撤銷段(Oracle9i版本),Oracle在修改數據記錄時,會把這些記錄被修改之前的結果存入回滾段或撤銷段中,就是因爲這種機制,Oracle對於事務隔離級別的實現與SQL Server截然不同。在Oracle中,讀取操作不會阻礙更新操作,更新操作也不會阻礙讀取操作,這樣在Oracle中的各種隔離級別下,讀取操作都不會等待更新事務結束,更新操作也不會因爲另一個事務中的讀取操作而發生等待,這也是Oracle事務處理的一個優勢所在。

Oracle缺省的設置是Read Committed隔離級別(也稱爲語句級別的隔離),在這種隔離級別下,如果一個事務正在對某個表進行DML操作,而這時另外一個會話對這個表的記錄進行讀取操作,則Oracle會去讀取回滾段或撤銷段中存放的更新之前的記錄,而不會象SQL Server一樣等待更新事務的結束。

在Serializable隔離級別(也稱爲事務級別的隔離),事務中的讀取操作只能讀取這個事務開始之前已經提交的數據結果。如果在讀取時,其他事務正在對記錄進行修改,則Oracle就會在回滾段或撤銷段中去尋找對應的原來未經更改的記錄(而且是在讀取操作所在的事務開始之前存放於回滾段或撤銷段的記錄),這時讀取操作也不會因爲相應記錄被更新而等待。

三.DDL語句對事務的影響

1.Oracle中DDL語句對事務的影響

在Oracle中,執行DDL語句(如Create Table、Create View等)時,會在執行之前自動發出一個Commit命令,並在隨後發出一個Commit或者Rollback命令,也就是說,DDL會象如下僞碼一樣執行:

Commit;

DDL_Statement;

If (Error) then

Rollback;

Else

Commit;

End if;

我們通過分析下面例子來看Oracle中,DDL語句對事務的影響:

Insert into some_table values(‘Before’);

Creaate table T(x int);

Insert into some_table values(‘After’);

Rollback;

由於在Oracle執行Create table語句之前進行了提交,而在Create table執行後也會自動發出Commit命令,所以只有插入After的行被回滾,而插入Before的行不會被回滾,Create table命令的結果也不會被回滾,即使Create table語句失敗,所進行的Before插入也會被提交。如果最後發出Commit命令,因爲插入Before及Create table的操作結果已經在之前提交,所以Commit命令影響的只有插入After的操作。

2.SQL Server中DDL語句對事務的影響

在SQL Server中,DDL語句對事務的影響與其他DML語句相同,也就是說,在DML語句發出之前或之後,都不會自動發出Commit命令。

在SQL Server 2000中,對於與上面Oracle同樣的例子,最後發出Rollback後,數據庫會回滾到插入Before之前的狀態,即插入Before和After的行都會被回滾,數據表T也不會被創建。

如果最後發出Commit操作,則會把三個操作的結果全部提交。

四.用戶斷開數據庫連接對事務的影響

另外,對應於Oracle的管理客戶端工具SQL*Plus,在SQL Server 2000中是osql,兩種管理工具都是命令行工具,使用方式及作用也類似,但是在SQL*Plus中,用戶退出連接時,會自動先發出Commit命令,然後再退出,而在osql中,如果用戶退出連接,會自動發出Rollback命令,這對於SQL Server的自動提交模式沒有什麼影響,但如果處於隱式事務模式,其影響是顯而易見的。對於兩種數據庫產品的其他客戶端管理工具也有類似的不同之處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章