SQL 存儲過程入門(事務)(四)

SQL 存儲過程入門(事務)(四)

 

本篇咱們來說一下事務處理技術。html

爲何要使用事務呢,事務有什麼用呢,舉個例子。sql

假設咱們如今有個業務,當作成功某件事情的時候要向2張表中插入數據,A表,B表,咱們插入的順序是先插入A,再插入B表,若是都順利插入成功了,固然沒有問題,若是任意一張表插入失敗了,而另外一張表插入成功了,插入成功的表就是垃圾數據了。咱們要判斷,任意一張表插入失敗都回滾,就是都不插入,這就是事務的基本使用。數據庫

一,sql事務定義c#

所謂事務是用戶定義的一個數據庫操做序列,是一個不可分割的工做單位。他包含的全部數據庫操做命令做爲一個總體一塊兒向系提交或撤消 ,這些操做要麼全作,要麼全不作,併發

 例如在關係數據庫中,一個事務能夠是一條sql語句,或者是一組sql語句或者是整個程序。ide

 

二,sql事務語句函數

開始事務:BEGIN TRANSACTION
提交事務:COMMIT TRANSACTION
回滾事務:ROLLBACK TRANSACTIONpost

事務一般是以begin transaction開始的,以commit或rollback 結束,commit表示提交,既提交事務的全部操做。具體的說就是將事務中全部對數據庫的更新寫到磁盤優化

上的物理數據中去,事務正常結束。url

Rollback表示回滾,在事務運行的過程當中發生了某種故障,事務不能繼續執行,系統將事務中對數據庫的全部已完成的操做所有取消,回滾到事務開始時的狀態。這裏的操做

指對數據庫的更新操做。

 

三,事務的特性(ACID Propertites)

原子性(Atomicity):事務是數據庫的邏輯工做單位,事務中包括的諸操做要麼都作,要麼都不作。

一致性(Consistency):事物完成時,數據必須是一致的,也就是說,和事物開始以前,數據存儲中的數據處於一致狀態。保證數據的無損

隔離性(Isolation):一個事務的執行不能被其餘事務干擾。併發執行的各個事務之間不能互相干擾。

持續性(Durability):指一個事務一旦提交,它對數據庫中數據的改變時永久性的。

 

四,存儲過程使用事務


這裏作好準備工做,創建一張表,插入一條數據。

複製代碼
--新建表
create table userinfo
(ID  int identity(1,1) ,
 UserName varchar(20)  primary key,
 UserPwd varchar(20)   ,
 RegisterTime datetime 
)
--初始化插入一條記錄
insert into userinfo(username,userpwd,RegisterTime) 
values('admin','admin',getdate())

select * from userinfo

-------------------

ID userName UserPwd RegisterTime

1 admin admin 2013-04-13 10:30:36.387
複製代碼

 

 從表結構看出,UserName是主鍵,是惟一值,如今要插入兩條數據

複製代碼
 Create Procedure  MyProcedure
    AS
       Begin
           Set    NOCOUNT    ON; 
           Set XACT_ABORT on; --這句話很是重要
           
           Begin  Tran   --開始事務
           
           insert into userinfo(username,userpwd,RegisterTime) values('admin','admin',getdate())
           insert into userinfo(username,userpwd,RegisterTime) values('jack','jack',getdate())

           Commit Tran       --提交事務
       End
複製代碼

執行

複製代碼
exec    MyProcedure

/*
消息 2627,級別 14,狀態 1,過程 MyProcedure,第 9 行
違反了 PRIMARY KEY 約束 'PK__userinfo__C9F284577F60ED59'。不能在對象 'dbo.userinfo' 中插入重複鍵。

*/
--查看數據庫

------------------------------------------------
12    admin    admin    2013-04-13 10:41:22.457
複製代碼
上面說了 Set XACT_ABORT on; 這句話很是重要 ,爲何呢?咱們來設置爲off的時候來看效果
複製代碼
 Create Procedure  MyProcedure
    AS
       Begin
           Set    NOCOUNT    ON; 
           Set XACT_ABORT off; --這句話很是重要
           
           Begin  Tran   --開始事務
           
           insert into userinfo(username,userpwd,RegisterTime) values('admin','admin',getdate())
           insert into userinfo(username,userpwd,RegisterTime) values('jack','jack',getdate())

           Commit Tran       --提交事務
       End
       
複製代碼

執行並查看結果

複製代碼
  exec    MyProcedure

/*
消息 2627,級別 14,狀態 1,過程 MyProcedure,第 9 行
違反了 PRIMARY KEY 約束 'PK__userinfo__C9F284577F60ED59'。不能在對象 'dbo.userinfo' 中插入重複鍵。
語句已終止。

*/

--查看結果
select * from userinfo
------------------------------------------- 12 admin admin 2013-04-13 10:41:22.457 15 jack jack 2013-04-13 10:44:05.203
複製代碼

這裏咱們將XACT_ABORT 設置爲off,事務中執行已經出現錯誤了,可是仍是將 「jack」這條記錄插入進去了。這就違反了事務的一致性原則了。因此咱們要將XACT_ABORT 設置爲ON的緣由。

看看下面說明:

1 、使用存儲過程執行事物,須要開啓XACT_ABORT參數(默認值爲Off),將該參數設置爲On,表示當執行事務時,若是出錯,會將transcation設置爲uncommittable狀態,那麼在語句塊批處理結束後將回滾全部操做;若是該參數設置爲Off,表示當執行事務時,若是出錯,出錯的語句將不會執行,其餘正確的操做繼續執行。

二、當SET NOCOUNT 爲 ON 時,不返回計數(計數表示受 Transact-SQL 語句影響的行數,例如在Sql server查詢分析器中執行一個delete操做後,下方窗口會提示(3)Rows Affected)。當   SET NOCOUNT 爲 OFF 時,返回計數,咱們應該在存儲過程的頭部加上SET NOCOUNT ON 這樣的話,在退出存儲過程的時候加上 SET NOCOUNT OFF這樣的話,以達到優化存儲過程的目的。

 

五,存儲過程當中事務和try…catch聯合使用

若是咱們在存儲過程事務中出現了錯誤,咱們不想顯示錯誤,咱們想動態處理這些錯誤信息,好比出錯了,咱們回滾,咱們設置某個屬性的值,這裏就會用到try ,catch了
仍是從例子出發

複製代碼
Create Procedure  MyProcedure
    AS
       Begin
           Set    NOCOUNT    ON; 
           Set XACT_ABORT ON; --這句話很是重要
           begin try
               Begin  Tran   --開始事務
               
               insert into userinfo(username,userpwd,RegisterTime) values('admin','admin',getdate())
               insert into userinfo(username,userpwd,RegisterTime) values('jack','jack',getdate())

               Commit Tran       --提交事務
            end try
            begin catch
                --在此可使用xact_state()來判斷是否有不可提交的事務,不可提交的事務

                --表示在事務內部發生錯誤了。Xact_state()有三種值:-1.事務不可提交;

                 --1.事務可提交;0.表示沒有事務,此時commit或者rollback會報錯。

                 if xact_state()=-1
                     rollback tran;
            end catch
      Set XACT_ABORT OFF;
End
複製代碼

當咱們執行的時候不會再出現剛纔的那種錯誤了,

複製代碼
  exec    MyProcedure

--------------
命令已成功完成。   --沒有出現那種錯誤



    select * from userinfo      

--------------------------------------------------
22    admin    admin    2013-04-13 10:55:50.653
複製代碼

能夠看到,事務回滾了,沒有插入數據了。

若是咱們想看到錯誤信息呢,再來看個例子
複製代碼
 Create Procedure  MyProcedure
    AS
       Begin
           Set    NOCOUNT    ON; 
           Set XACT_ABORT ON; --這句話很是重要
           begin try
               Begin  Tran   --開始事務
               
               insert into userinfo(username,userpwd,RegisterTime) values('admin','admin',getdate())
               insert into userinfo(username,userpwd,RegisterTime) values('jack','jack',getdate())

               Commit Tran       --提交事務
            end try
            begin catch
                --在此可使用xact_state()來判斷是否有不可提交的事務,不可提交的事務

                --表示在事務內部發生錯誤了。Xact_state()有三種值:-1.事務不可提交;

                 --1.事務可提交;0.表示沒有事務,此時commit或者rollback會報錯。

                 if xact_state()=-1
                 begin
                       rollback tran;
                       SELECT ERROR_NUMBER()  AS  ErrorNumber,
                       ERROR_MESSAGE()  AS  ErrorMessage;
                     end
            end catch   
        
       End
複製代碼

執行

    exec    MyProcedure

--------------------------------
ErrorNumber         ErrorMessage
2627             違反了 PRIMARY KEY 約束 'PK__userinfo__C9F284577F60ED59'。不能在對象 'dbo.userinfo' 中插入重複鍵。

說明:一、捕獲錯誤的函數有不少,以下:

           ERROR_NUMBER() 返回錯誤號。

    ERROR_SEVERITY() 返回嚴重性。

    ERROR_STATE() 返回錯誤狀態號。

    ERROR_PROCEDURE() 返回出現錯誤的存儲過程或觸發器的名稱。

    ERROR_LINE() 返回致使錯誤的例程中的行號。

    ERROR_MESSAGE() 返回錯誤消息的完整文本。該文本可包括任何可替換參數所提供的值,如長度、對象名或時間。



在存儲過程當中使用事務時,若是存在try…catch語句塊,那麼當捕獲到錯誤時,須要在catch語句塊中手動進行Rollback操做,不然系統會給客戶端傳遞一條錯誤信息。若是在存儲過程開始處將set xact_abort on,那麼當有錯誤發生時,系統會將當前事務置爲不可提交狀態,即會將xact_state()置爲-1,此時只能夠對事務進行Rollback操做,不可進行提交(commit)操做,那麼咱們在catch語句塊中就能夠根據xact_state()的值來判斷是否有事務處於不可提交狀態,若是有則能夠進行rollback操做了。

若是在存儲過程開始處將set xact_abort off,那麼當有錯誤發生時,系統不會講xact_state()置爲-1,那麼咱們在catch塊中就不能夠根據該函數值來判斷是否須要進行rollback了,可是咱們能夠根據@@Trancount全局變量來判斷,若是在catch塊中判斷出@@Trancount數值大於0,表明還有未提交的事務,既然進入catch語句塊了,那麼還存在未提交的事務,該事務應該是須要rollback的,可是這種方法在某些狀況下可能判斷的不許確。

 

推薦的方法仍是將set xact_abort on,而後在catch中判斷xact_state()的值來判斷是否須要Rollback操做。

 

下面再來看個例子,在實踐中不斷熟悉。

這個例子就是,若是插入重複的數據給出提示信息並返回

複製代碼
 -- 判斷要建立的存儲過程名是否存在
if Exists(Select name From sysobjects Where name = 'P_InsertUser' And type = 'P')
-- 刪除存儲過程
Drop Procedure dbo.P_InsertUser
Go
複製代碼
USE [StoreTest]
GO

create Procedure  [dbo].[P_InsertUser]
@UserName varchar(100),
@UserPwd varchar(100)
AS
Begin
Set NOCOUNT ON; 
Set XACT_ABORT ON; --這句話很是重要

Begin try
    if(isnull(@UserName,'')='')
   begin print 'UserName is empty';
     return;
end declare @iCount int; set @iCount = 0; select @iCount = Count(1) from userinfo with(nolock) where username=@UserName; if( @iCount > 0 ) begin print 'the current name already exist'; return end Begin Tran --開始事務,事務中不能有return語句 --insert insert into userinfo( username ,userpwd ,RegisterTime ) values( @UserName, @UserPwd, getdate() ) Commit Tran --提交事務 end try begin catch --在此可使用xact_state()來判斷是否有不可提交的事務,不可提交的事務 --表示在事務內部發生錯誤了。Xact_state()有三種值:-1.事務不可提交; --1.事務可提交;0.表示沒有事務,此時commit或者rollback會報錯。 if xact_state()=-1 begin rollback tran; --事務回滾 SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_MESSAGE() AS ErrorMessage; end end catch Set XACT_ABORT off; End --調用存儲過程 exec [P_InsertUser] '','admin' select * from userinfo GO
複製代碼

 

 
複製代碼

 

事務的東西不少,這裏但願能起到拋磚引玉的效果。

這裏附近一下c#使用事務的語法,概念是同樣是,只不過是用c#實現的。
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqltransaction(v=vs.110).aspx
複製代碼
 using (SqlConnection conn = new SqlConnection(Connstring)
       {
           conn .Open();

           // Start a local transaction.
           SqlTransaction sqlTran = conn .BeginTransaction();

           // Enlist a command in the current transaction.
           SqlCommand command = conn .CreateCommand();
           //begin transaction
           command.Transaction = sqlTran;   

           try
           {
               // Execute two separate commands.
               command.CommandText ="xxxxx";
               command.ExecuteNonQuery();

               command.CommandText ="yyyyyy";
               command.ExecuteNonQuery();

               // Commit the transaction.
               sqlTran.Commit();
           }
           catch (Exception ex)
           {
               // Handle the exception if the transaction fails to commit.
               lblMsg.Text = ex.Message;


               try
               {
                   // Attempt to roll back the transaction.
                   sqlTran.Rollback();
               }
               catch (Exception exRollback)
               {
                   // Throws an InvalidOperationException if the connection 
                   // is closed or the transaction has already been rolled 
                   // back on the server.
                   lblMsg.Text = exRollback.Message;

               }
           }
       }
複製代碼

 

未完待續......
相關文章
相關標籤/搜索