【轉】事務

轉自:http://www.cnblogs.com/hoojo/archive/2011/07/19/2110325.html

 

Ø 事務

在數據庫中有時候需要把多個步驟的指令當作一個整體來運行,這個整體要麼全部成功,要麼全部失敗,這就需要用到事務。

    1、 事務的特點

        事務有若干條T-SQL指令組成,並且所有的指令作爲一個整體提交給數據庫系統,執行時,這組指令要麼全部執行完成,要麼全部取消。因此,事務是一個不可分割的邏輯單元。

 

        事務有4個屬性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)以及持久性(Durability),也稱作事務的ACID屬性。

        原子性:事務內的所有工作要麼全部完成,要麼全部不完成,不存在只有一部分完成的情況。

        一致性:事務內的操作都不能違反數據庫的約束或規則,事務完成時有內部數據結構都必須是正確的。(個人理解:事務運行後數據庫前後的狀態要是相等的,既5+2=7和5-2=3)

        隔離性:事務之間是相互隔離的,如果有兩個事務對同一個數據庫進行操作,比如讀取表數據。任何一個事務看到的所有內容要麼是其他事務完成之前的狀態,要麼是其他事務完成之後的狀態。一個事務不可能遇到另一個事務的中間狀態。

        持久性:事務完成之後,它對數據庫系統的影響是持久的,即使是系統錯誤,重新啓動系統後,該事務的結果依然存在。

 

    2、 事務的模式

        a、 顯示事務

        顯示事務就是用戶使用T-SQL明確的定義事務的開始(begin transaction)和提交(commit transaction)或回滾事務(rollback transaction)

        b、 自動提交事務

        自動提交事務是一種能夠自動執行並能自動回滾事務,這種方式是T-SQL的默認事務方式。例如在刪除一個表記錄的時候,如果這條記錄有主外鍵關係的時候,刪除就會受主外鍵約束的影響,那麼這個刪除就會取消。

        可以設置事務進入隱式方式:set implicit_transaction on;

        c、 隱式事務

        隱式事務是指當事務提交或回滾後,SQL Server自動開始事務。因此,隱式事務不需要使用begin transaction顯示開始,只需直接失業提交事務或回滾事務的T-SQL語句即可。

        使用時,需要設置set implicit_transaction on語句,將隱式事務模式打開,下一個語句會啓動一個新的事物,再下一個語句又將啓動一個新事務。

 

    3、 事務處理

        常用T-SQL事務語句:

        a、 begin transaction語句

        開始事務,而@@trancount全局變量用來記錄事務的數目值加1,可以用@@error全局變量記錄執行過程中的錯誤信息,如果沒有錯誤可以直接提交事務,有錯誤可以回滾。(BEGIN TRANSACTION 代表一個點,由連接引用的數據在該點是邏輯和物理上都一致的。如果遇上錯誤,在 BEGIN TRANSACTION 之後的所有數據改動都能進行回滾,以將數據返回到已知的一致狀態 。每個事務繼續執行直到它無誤地完成並且用 COMMIT TRANSACTION 對數據庫作永久的改動,或者遇上錯誤並且用 ROLLBACK TRANSACTION 語句擦除所有改動

        b、 commit transaction語句

        提交事務,表示一個隱式或顯示的事務的結束,對數據庫所做的修改正式生效。並將@@trancount的值減1;

        c、 rollback transaction語句

        回滾事務,執行rollback tran語句後,數據會回滾到begin tran的時候的狀態

 

    4、 事務的示例

--開始事務
begin transaction tran_bank;
declare @tran_error int;
    set @tran_error = 0;
    begin try
        update bank set totalMoney = totalMoney - 10000 where userName = 'jack';        
        set @tran_error = @tran_error + @@error;
        update bank set totalMoney = totalMoney + 10000 where userName = 'jason';
        set @tran_error = @tran_error + @@error;
    end try
    begin catch        
        print '出現異常,錯誤編號:' + convert(varchar, error_number()) + ', 錯誤消息:' + error_message(); 
        set @tran_error = @tran_error + 1;
    end catch
if (@tran_error > 0)
    begin
        --執行出錯,回滾事務
        rollback tran;
        print '轉賬失敗,取消交易';
    end
else
    begin
        --沒有異常,提交事務
        commit tran;
        print '轉賬成功';
    end
go

 Ø 異常

     在程序中,有時候完成一些Transact-SQL會出現錯誤、異常信息。如果我們想自己處理這些異常信息的話,需要手動捕捉這些信息。那麼我們可以利用try catch完成。

TRY…CATCH 構造包括兩部分:一個 TRY 塊和一個 CATCH 塊。如果在 TRY 塊中所包含的 Transact-SQL 語句中檢測到錯誤條件,控制將被傳遞到 CATCH 塊(可在此塊中處理該錯誤)。

     CATCH 塊處理該異常錯誤後,控制將被傳遞到 END CATCH 語句後面的第一個 Transact-SQL 語句。如果 END CATCH 語句是存儲過程或觸發器中的最後一條語句,控制將返回到調用該存儲過程或觸發器的代碼。將不執行 TRY 塊中生成錯誤的語句後面的 Transact-SQL 語句。

     如果 TRY 塊中沒有錯誤,控制將傳遞到關聯的 END CATCH 語句後緊跟的語句。如果 END CATCH 語句是存儲過程或觸發器中的最後一條語句,控制將傳遞到調用該存儲過程或觸發器的語句。

     TRY 塊以 BEGIN TRY 語句開頭,以 END TRY 語句結尾。在 BEGIN TRY 和 END TRY 語句之間可以指定一個或多個 Transact-SQL 語句。CATCH 塊必須緊跟 TRY 塊。CATCH 塊以 BEGIN CATCH 語句開頭,以 END CATCH 語句結尾。在 Transact-SQL 中,每個 TRY 塊僅與一個 CATCH 塊相關聯。

     # 錯誤函數

TRY...CATCH 使用錯誤函數來捕獲錯誤信息。
    ERROR_NUMBER() 返回錯誤號。
    ERROR_MESSAGE() 返回錯誤消息的完整文本。此文本包括爲任何可替換參數(如長度、對象名稱或時間)提供的值。
    ERROR_SEVERITY() 返回錯誤嚴重性。
    ERROR_STATE() 返回錯誤狀態號。
    ERROR_LINE() 返回導致錯誤的例程中的行號。
    ERROR_PROCEDURE() 返回出現錯誤的存儲過程或觸發器的名稱。

  示例

--錯誤消息存儲過程
if (object_id('proc_error_info') is not null)
    drop procedure proc_error_info
go
create proc proc_error_info
as
    select 
        error_number() '錯誤編號',
        error_message() '錯誤消息',
        error_severity() '嚴重性',
        error_state() '狀態好',
        error_line() '錯誤行號',
        error_procedure() '錯誤對象(存儲過程或觸發器)名稱';
go

 # 示例:用異常處理錯誤信息

--簡單try catch示例
begin try
    select 1 / 0;
end try
begin catch
    exec proc_error_info; --調用錯誤消息存儲過程
end catch
go

 # 示例:異常能處理的錯誤信息

--
--簡單try catch示例,無法處理錯誤
begin try
    select * * from student;
end try
begin catch
    exec proc_error_info;
end catch
go
--
--簡單try catch示例,不處理錯誤(不存在的表對象)
begin try
    select * from st;
end try
begin catch
    exec proc_error_info;
end catch
go
--
--異常處理,能處理存儲過程(觸發器)中(不存在表對象)的錯誤信息
if (object_id('proc_select') is not null)
    drop procedure proc_select
go
create proc proc_select
as
    select * from st;
go
begin try
    exec proc_select;
end try
begin catch    
    exec proc_error_info;
end catch
go

 異常不能處理編譯期的錯誤,如語法錯誤。以及重編譯造成部分名稱對象得不到正確解析的時候所出現的錯誤。

 

     # 示例:無法提交的事務

--創建臨時用表
if (object_id('temp_tab', 'u') is not null)
    drop table temp_tab
go
create table temp_tab(
    id int primary key identity(100000, 1),
    name varchar(200)
)
go

begin try
    begin tran;
    --沒有createTime字段
    alter table temp_tab drop column createTime;
    commit tran;
end try
begin catch
    exec proc_error_info;--顯示異常信息
    if (xact_state() = -1)
    begin
        print '會話具有活動事務,但出現了致使事務被歸類爲無法提交的事務的錯誤。'
            + '會話無法提交事務或回滾到保存點;它只能請求完全回滾事務。'
            + '會話在回滾事務之前無法執行任何寫操作。會話在回滾事務之前只能執行讀操作。'
            + '事務回滾之後,會話便可執行讀寫操作並可開始新的事務。';
    end
    else if (xact_state() = 0)
    begin
        print '會話沒有活動事務。';
    end
    else if (xact_state() = 1)
    begin
        print '會話具有活動事務。會話可以執行任何操作,包括寫入數據和提交事務。';
    end
end catch
go

 # 示例:處理異常日誌信息

--
---異常、錯誤信息表
if (object_id('errorLog', 'U') is not null)
    drop table errorLog
go
create table errorLog(
    errorLogID int primary key identity(100, 1),    --ErrorLog 行的主鍵。
    errorTime datetime default getDate(),            --發生錯誤的日期和時間。
    userName sysname default current_user,            --執行發生錯誤的批處理的用戶。
    errorNumber int,                                --發生的錯誤的錯誤號。
    errorSeverity int,                                --發生的錯誤的嚴重性。
    errorState int,                                    --發生的錯誤的狀態號。
    errorProcedure nvarchar(126),                    --發生錯誤的存儲過程或觸發器的名稱。
    errorLine int,                                    --發生錯誤的行號。
    errorMessage nvarchar(4000)
)
go
--
--存儲過程:添加異常日誌信息
if (object_id('proc_add_exception_log', 'p') is not null)
    drop proc proc_add_exception_log
go
create proc proc_add_exception_log(@logId int = 0 output)
as
begin
    set nocount on;
    set @logId = 0;
    begin try
        if (error_number() is null)
            return;
        
        if (xact_state() = -1)
        begin
            print '會話具有活動事務,但出現了致使事務被歸類爲無法提交的事務的錯誤。'
                + '會話無法提交事務或回滾到保存點;它只能請求完全回滾事務。'
                + '會話在回滾事務之前無法執行任何寫操作。會話在回滾事務之前只能執行讀操作。'
                + '事務回滾之後,會話便可執行讀寫操作並可開始新的事務。';
        end
        else if (xact_state() = 0)
        begin
            print '會話沒有活動事務。';
        end
        else if (xact_state() = 1)
        begin
            print '會話具有活動事務。會話可以執行任何操作,包括寫入數據和提交事務。';
        end
        
        --添加日誌信息
        insert into errorLog values(getDate(), 
            current_user, error_number(), 
            error_severity(), error_state(), 
            error_procedure(), 
            error_line(), error_message());
        --設置自增值
        select @logId = @@identity;
    end try
    begin catch
        print '添加異常日誌信息出現錯誤';
        exec proc_error_info;--顯示錯誤信息
        return -1;
    end catch
end
go
--
---處理異常信息示例
declare @id int;
begin try
    begin tran;
    --刪除帶有外鍵的記錄信息
    delete classes where id = 1;
    commit tran;
end try
begin catch
    exec proc_error_info;--顯示錯誤信息
    if (xact_state() <> 0)
    begin
        rollback tran;
    end
    exec proc_add_exception_log @id output
end catch
select * from errorLog where errorLogID = @id;
go

 

事務隔離級別




其實隔離級別就是提供鎖機制來控制讀者寫者問題對臨界資源的訪問:

更新丟失:2個寫者同時作業,其中一個失敗導致回滾,但是另一個寫者事務不知道。(read umcommitted)

髒讀:讀者讀到了寫者過程中的數據,這個被修改了,但是沒提交到數據庫,那麼寫者事務失敗的話,這個數據就沒意義。(read committed)

不可重複讀:讀者事務對相同數據兩次讀取的間隙,寫者事務更新了數據,導致讀者兩次操作結果不同。(repeatable read)

幻讀:讀者事務兩次讀取的結果數目不同,因爲兩次讀取的間隙,寫者事務發生操作了。(serializable)

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