SQL Server 事務、異常和游標
建議先閱讀存儲過程: SQL Server 存儲過程
? 事務
在數(shù)據(jù)庫中有時候需要把多個步驟的指令當作一個整體來運行,這個整體要么全部成功,要么全部失敗,這就需要用到事務。
??? 1、 事務的特點
??????? 事務有若干條T-SQL指令組成,并且所有的指令昨晚一個整體提交給數(shù)據(jù)庫系統(tǒng),執(zhí)行時,這組指令要么全部執(zhí)行完成,要么全部取消。因此,事務是一個不可分割的邏輯單元。
?
??????? 事務有4個屬性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)以及持久性(Durability),也稱作事務的ACID屬性。
??????? ? 原子性 :事務內的所有工作要么全部完成,要么全部不完成,不存在只有一部分完成的情況。
??????? ? 一致性 :事務內的然后操作都不能違反數(shù)據(jù)庫的然后約束或規(guī)則,事務完成時有內部數(shù)據(jù)結構都必須是正確的。
??????? ? 隔離性 :事務直接是相互隔離的,如果有兩個事務對同一個數(shù)據(jù)庫進行操作,比如讀取表數(shù)據(jù)。任何一個事務看到的所有內容要么是其他事務完成之前的狀態(tài),要么是其他事務完成之后的狀態(tài)。一個事務不可能遇到另一個事務的中間狀態(tài)。
??????? ? 持久性 :事務完成之后,它對數(shù)據(jù)庫系統(tǒng)的影響是持久的,即使是系統(tǒng)錯誤,重新啟動系統(tǒng)后,該事務的結果依然存在。
?
??? 2、 事務的模式
??????? a、 顯示事務
??????? 顯示事務就是用戶使用T-SQL明確的定義事務的開始(begin transaction)和提交(commit transaction)或回滾事務(rollback transaction)
??????? b、 自動提交事務
??????? 自動提交事務是一種能夠自動執(zhí)行并能自動回滾事務,這種方式是T-SQL的默認事務方式。例如在刪除一個表記錄的時候,如果這條記錄有主外鍵關系的時候,刪除就會受主外鍵約束的影響,那么這個刪除就會取消。
??????? 可以設置事務進入隱式方式:set implicit_transaction on;
??????? c、 隱式事務
??????? 隱式事務是指當事務提交或回滾后,SQL Server自動開始事務。因此,隱式事務不需要使用begin transaction顯示開始,只需直接失業(yè)提交事務或回滾事務的T-SQL語句即可。
??????? 使用時,需要設置set implicit_transaction on語句,將隱式事務模式打開,下一個語句會啟動一個新的事物,再下一個語句又將啟動一個新事務。
?
??? 3、 事務處理
??????? 常用T-SQL事務語句:
??????? a、 begin transaction語句
??????? 開始事務,而@@trancount全局變量用來記錄事務的數(shù)目值加1,可以用@@error全局變量記錄執(zhí)行過程中的錯誤信息,如果沒有錯誤可以直接提交事務,有錯誤可以回滾。
??????? b、 commit transaction語句
??????? 回滾事務,表示一個隱式或顯示的事務的結束,對數(shù)據(jù)庫所做的修改正式生效。并將@@trancount的值減1;
??????? c、 rollback transaction語句
??????? 回滾事務,執(zhí)行rollback tran語句后,數(shù)據(jù)會回滾到begin tran的時候的狀態(tài)
?
??? 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
'出現(xiàn)異常,錯誤編號:'
+
convert
(
varchar
, error_number()) +
', 錯誤消息:'
+ error_message();
set
@tran_error = @tran_error + 1;
end
catch
if
(@tran_error > 0)
begin
--執(zhí)行出錯,回滾事務
rollback
tran
;
print
'轉賬失敗,取消交易'
;
end
else
begin
--沒有異常,提交事務
commit
tran
;
print
'轉賬成功'
;
end
go
? 異常
???? 在程序中,有時候完成一些Transact-SQL會出現(xiàn)錯誤、異常信息。如果我們想自己處理這些異常信息的話,需要手動捕捉這些信息。那么我們可以利用try catch完成。
TRY…CATCH 構造包括兩部分:一個 TRY 塊和一個 CATCH 塊。如果在 TRY 塊中所包含的 Transact-SQL 語句中檢測到錯誤條件,控制將被傳遞到 CATCH 塊(可在此塊中處理該錯誤)。
???? CATCH 塊處理該異常錯誤后,控制將被傳遞到 END CATCH 語句后面的第一個 Transact-SQL 語句。如果 END CATCH 語句是存儲過程或觸發(fā)器中的最后一條語句,控制將返回到調用該存儲過程或觸發(fā)器的代碼。將不執(zhí)行 TRY 塊中生成錯誤的語句后面的 Transact-SQL 語句。
???? 如果 TRY 塊中沒有錯誤,控制將傳遞到關聯(lián)的 END CATCH 語句后緊跟的語句。如果 END CATCH 語句是存儲過程或觸發(fā)器中的最后一條語句,控制將傳遞到調用該存儲過程或觸發(fā)器的語句。
???? TRY 塊以 BEGIN TRY 語句開頭,以 END TRY 語句結尾。在 BEGIN TRY 和 END TRY 語句之間可以指定一個或多個 Transact-SQL 語句。CATCH 塊必須緊跟 TRY 塊。CATCH 塊以 BEGIN CATCH 語句開頭,以 END CATCH 語句結尾。在 Transact-SQL 中,每個 TRY 塊僅與一個 CATCH 塊相關聯(lián)。
???? # 錯誤函數(shù)
TRY...CATCH 使用錯誤函數(shù)來捕獲錯誤信息。
ERROR_NUMBER() 返回錯誤號。
ERROR_MESSAGE() 返回錯誤消息的完整文本。此文本包括為任何可替換參數(shù)(如長度、對象名稱或時間)提供的值。
ERROR_SEVERITY() 返回錯誤嚴重性。
ERROR_STATE() 返回錯誤狀態(tài)號。
ERROR_LINE() 返回導致錯誤的例程中的行號。
ERROR_PROCEDURE() 返回出現(xiàn)錯誤的存儲過程或觸發(fā)器的名稱。
???? 示例
--錯誤消息存儲過程
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()
'狀態(tài)好'
,
error_line()
'錯誤行號'
,
error_procedure()
'錯誤對象(存儲過程或觸發(fā)器)名稱'
;
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
--
--異常處理,能處理存儲過程(觸發(fā)器)中(不存在表對象)的錯誤信息
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
???? 異常不能處理編譯期的錯誤,如語法錯誤。以及重編譯造成部分名稱對象得不到正確解析的時候所出現(xiàn)的錯誤。
?
???? # 示例:無法提交的事務
--創(chuàng)建臨時用表
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
'會話具有活動事務,但出現(xiàn)了致使事務被歸類為無法提交的事務的錯誤。'
+
'會話無法提交事務或回滾到保存點;它只能請求完全回滾事務。'
+
'會話在回滾事務之前無法執(zhí)行任何寫操作。會話在回滾事務之前只能執(zhí)行讀操作。'
+
'事務回滾之后,會話便可執(zhí)行讀寫操作并可開始新的事務。'
;
end
else
if
(xact_state() = 0)
begin
print
'會話沒有活動事務。'
;
end
else
if
(xact_state() = 1)
begin
print
'會話具有活動事務。會話可以執(zhí)行任何操作,包括寫入數(shù)據(jù)和提交事務。'
;
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(), --發(fā)生錯誤的日期和時間。
userName sysname
default
current_user
, --執(zhí)行發(fā)生錯誤的批處理的用戶。
errorNumber
int
, --發(fā)生的錯誤的錯誤號。
errorSeverity
int
, --發(fā)生的錯誤的嚴重性。
errorState
int
, --發(fā)生的錯誤的狀態(tài)號。
errorProcedure nvarchar(126), --發(fā)生錯誤的存儲過程或觸發(fā)器的名稱。
errorLine
int
, --發(fā)生錯誤的行號。
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
'會話具有活動事務,但出現(xiàn)了致使事務被歸類為無法提交的事務的錯誤。'
+
'會話無法提交事務或回滾到保存點;它只能請求完全回滾事務。'
+
'會話在回滾事務之前無法執(zhí)行任何寫操作。會話在回滾事務之前只能執(zhí)行讀操作。'
+
'事務回滾之后,會話便可執(zhí)行讀寫操作并可開始新的事務。'
;
end
else
if
(xact_state() = 0)
begin
print
'會話沒有活動事務。'
;
end
else
if
(xact_state() = 1)
begin
print
'會話具有活動事務。會話可以執(zhí)行任何操作,包括寫入數(shù)據(jù)和提交事務。'
;
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
'添加異常日志信息出現(xiàn)錯誤'
;
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
?
? 游標
???? 游標可以對一個select的結果集進行處理,或是不需要全部處理,就會返回一個對記錄集進行處理之后的結果。
???? 1、游標實際上是一種能從多條數(shù)據(jù)記錄的結果集中每次提取一條記錄的機制。游標可以完成:
????????? # 允許定位到結果集中的特定行
????????? # 從結果集的當前位置檢索一行或多行數(shù)據(jù)
????????? # 支持對結果集中當前位置的進行修改
???? 由于游標是將記錄集進行一條條的操作,所以這樣給服務器增加負擔,一般在操作復雜的結果集的情況下,才使用游標。SQL Server 2005有三種游標:T-SQL游標、API游標、客戶端游標。
?
???? 2、游標的基本操作
????????? 游標的基本操作有定義游標、打開游標、循環(huán)讀取游標、關閉游標、刪除游標。
???? A、 定義游標
declare
cursor_name --游標名稱
cursor
[
local
|
global
] --全局、局部
[forward
only
|
scroll
] --游標滾動方式
[read_only | scroll_locks | optimistic] --讀取方式
for
select_statements --查詢語句
[
for
update
|
of
column_name ...] --修改字段
???? 參數(shù):
???? forward only | scroll:前一個參數(shù),游標只能向后移動;后一個參數(shù),游標可以隨意移動
???? read_only:只讀游標
???? scroll_locks:游標鎖定,游標在讀取時,數(shù)據(jù)庫會將該記錄鎖定,以便游標完成對記錄的操作
???? optimistic:該參數(shù)不會鎖定游標;此時,如果記錄被讀入游標后,對游標進行更新或刪除不會超過
?
???? B、 打開游標
????????? open cursor_name;
????????? 游標打開后,可以使用全局變量@@cursor_rows顯示讀取記錄條數(shù)
?
???? C、 檢索游標
????????? fetch cursor_name;
????????? 檢索方式如下:
???????????? fetch first; 讀取第一行
???????????? fetch next; 讀取下一行
???????????? fetch prior; 讀取上一行
???????????? fetch last; 讀取最后一行
???????????? fetch absolute n; 讀取某一行
??????????????? 如果n為正整數(shù),則讀取第n條記錄
??????????????? 如果n為負數(shù),則倒數(shù)提取第n條記錄
??????????????? 如果n為,則不讀取任何記錄
???????????? fetch pelative n
??????????????? 如果n為正整數(shù),則讀取上次讀取記錄之后第n條記錄
??????????????? 如果n為負數(shù),則讀取上次讀取記錄之前第n條記錄
??????????????? 如果n為,則讀取上次讀取的記錄
?
???? D、 關閉游標
????????? close cursor_name;
?
???? E、 刪除游標
????????? deallocate cursor_name;
?
???? 3、游標操作示例
--創(chuàng)建一個游標
declare
cursor_stu
cursor
scroll
for
select
id, name, age
from
student;
--打開游標
open
cursor_stu;
--存儲讀取的值
declare
@id
int
,
@name nvarchar(20),
@age
varchar
(20);
--讀取第一條記錄
fetch
first
from
cursor_stu
into
@id, @name, @age;
--循環(huán)讀取游標記錄
print
'讀取的數(shù)據(jù)如下:'
;
--全局變量
while
(@@fetch_status = 0)
begin
print
'編號:'
+
convert
(
char
(5), @id) +
', 名稱:'
+ @name +
', 類型:'
+ @age;
--繼續(xù)讀取下一條記錄
fetch
next
from
cursor_stu
into
@id, @name, @age;
end
--關閉游標
close
area_cursor;
--刪除游標
--
deallocate
area_cursor;
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

