ให้รหัสที่แสดงในคำถามเท่านั้นและสมมติว่าไม่มีโปรแกรมย่อยสามรายการใดที่มีการจัดการธุรกรรมอย่างชัดเจนดังนั้นใช่ข้อผิดพลาดในโปรแกรมย่อยสามรายการใด ๆ จะถูกดักจับและROLLBACKในCATCHบล็อกจะย้อนกลับทั้งหมด ของการทำงาน
แต่ต่อไปนี้เป็นสิ่งที่ควรทราบเกี่ยวกับธุรกรรม (อย่างน้อยใน SQL Server):
มีธุรกรรมจริงเพียงรายการเดียวเท่านั้น(ธุรกรรมแรก) ไม่ว่าคุณจะโทรกี่ครั้งก็ตามBEGIN TRAN
- คุณสามารถตั้งชื่อการทำธุรกรรม (ตามที่คุณได้ทำที่นี่) และชื่อที่จะปรากฏในบันทึกการ แต่ตั้งชื่อเพียง แต่มีความหมายสำหรับครั้งแรก / ด้านนอกสุดของการทำธุรกรรม (เพราะอีกครั้งหนึ่งคือการทำธุรกรรม)
- ทุกครั้งที่คุณโทร
BEGIN TRANไม่ว่าจะมีชื่อหรือไม่เคาน์เตอร์ธุรกรรมจะเพิ่มขึ้น 1
- คุณสามารถดูระดับปัจจุบันโดยการทำ
SELECT @@TRANCOUNT;
COMMITคำสั่งใด ๆ ที่ออกเมื่อ@@TRANCOUNTอยู่ที่ 2 หรือสูงกว่าจะไม่ทำอะไรมากไปกว่าการลดทอนครั้งละหนึ่งรายการ
- ไม่มีอะไรเกิดขึ้นจนกว่าจะมีการ
COMMITออกเมื่อ@@TRANCOUNTอยู่ที่1
- ในกรณีที่ข้อมูลข้างต้นไม่ได้ระบุอย่างชัดเจน: ไม่ว่าระดับธุรกรรมจะไม่มีการซ้อนธุรกรรมที่แท้จริง
บันทึกจุดที่อนุญาตให้มีการสร้างส่วนหนึ่งของการทำงานภายในรายการที่สามารถยกเลิกได้
- บันทึกคะแนนถูกสร้าง / ทำเครื่องหมายผ่าน
SAVE TRAN {save_point_name}คำสั่ง
- บันทึกคะแนนทำเครื่องหมายจุดเริ่มต้นของชุดย่อยของงานที่สามารถยกเลิกได้โดยไม่ต้องย้อนกลับไปทำธุรกรรมทั้งหมด
- ชื่อจุดบันทึกไม่จำเป็นต้องไม่ซ้ำกัน แต่การใช้ชื่อเดียวกันมากกว่าหนึ่งครั้งยังคงสร้างจุดบันทึกที่แตกต่างกัน
- บันทึกคะแนนสามารถซ้อนกัน
- ไม่สามารถบันทึกคะแนนได้
ROLLBACK {save_point_name}บันทึกจุดที่สามารถยกเลิกได้ผ่านทาง (เพิ่มเติมในด้านล่างนี้)
- การย้อนกลับจุดบันทึกจะยกเลิกการทำงานที่เกิดขึ้นหลังจากการโทรครั้งล่าสุด
SAVE TRAN {save_point_name}รวมถึงจุดบันทึกที่สร้างขึ้นหลังจากที่มีการย้อนกลับถูกสร้างขึ้น (ดังนั้น "การซ้อน")
- การย้อนกลับจุดบันทึกไม่มีผลต่อการนับธุรกรรม / ระดับ
- งานใด ๆ ที่ทำก่อนที่จะเริ่มต้น
SAVE TRANไม่สามารถยกเลิกได้ยกเว้นโดยการROLLBACKทำธุรกรรมเต็มรูปแบบทั้งหมด
- เพียงเพื่อให้ชัดเจน: การออก a
COMMITเมื่อ@@TRANCOUNTอยู่ที่ 2 หรือสูงกว่าจะไม่มีผลต่อคะแนนการบันทึก (เพราะอีกครั้งระดับการทำธุรกรรมที่สูงกว่า 1 ไม่มีอยู่นอกตัวนับนั้น)
คุณไม่สามารถกระทำธุรกรรมที่มีชื่อเฉพาะ ธุรกรรม "ชื่อ" หากระบุพร้อมกับCOMMITจะถูกละเว้นและมีอยู่เพื่อให้สามารถอ่านได้เท่านั้น
การROLLBACKออกโดยไม่มีชื่อจะย้อนกลับธุรกรรมทั้งหมด
ชื่อที่ROLLBACKออกจะต้องตรงกับ:
- การทำธุรกรรมครั้งแรกสมมติว่าชื่อ:
สมมติว่าไม่มีSAVE TRANการเรียกด้วยชื่อการทำธุรกรรมเดียวกันนี้จะย้อนกลับการทำธุรกรรมทั้งหมด
- A "จุดบันทึก" (อธิบายไว้ข้างต้น):
พฤติกรรมนี้จะ "เลิกทำ" การเปลี่ยนแปลงทั้งหมดที่เกิดขึ้นเนื่องจากมีการ SAVE TRAN {save_point_name}เรียกใช้ล่าสุด
- หากการทำธุรกรรมครั้งแรกคือ a) ชื่อและ b) มี
SAVE TRANคำสั่งที่ออกพร้อมกับชื่อแล้ว ROLLBACK แต่ละชื่อของธุรกรรมนั้นจะยกเลิกการบันทึกแต่ละจุดจนกว่าจะไม่มีชื่อเหลืออยู่ หลังจากนั้น ROLLBACK ที่ออกในชื่อนั้นจะย้อนกลับธุรกรรมทั้งหมด
ตัวอย่างเช่นสมมติว่าคำสั่งต่อไปนี้ถูกเรียกใช้ตามลำดับที่แสดง:
BEGIN TRAN A -- @@TRANCOUNT is now 1
-- DML Query 1
SAVE TRAN A
-- DML Query 2
SAVE TRAN A
-- DML Query 3
BEGIN TRAN B -- @@TRANCOUNT is now 2
SAVE TRAN B
-- DML Query 4
ตอนนี้ถ้าคุณออก (แต่ละสถานการณ์ต่อไปนี้เป็นอิสระจากกัน):
ROLLBACK TRAN Bหนึ่งครั้ง: จะยกเลิก "DML Query 4" @@TRANCOUNTยังคงเป็น 2
ROLLBACK TRAN Bสองครั้ง: จะเลิกทำ "DML Query 4" แล้วเกิดข้อผิดพลาดเนื่องจากไม่มีจุดบันทึกที่สอดคล้องกันสำหรับ "B" @@TRANCOUNTยังคงเป็น 2
ROLLBACK TRAN Aหนึ่งครั้ง: จะยกเลิก "DML Query 4" และ "DML Query 3" @@TRANCOUNTยังคงเป็น 2
ROLLBACK TRAN Aสองครั้ง: จะเลิกทำ "DML Query 4", "DML Query 3" และ "DML Query 2" @@TRANCOUNTยังคงเป็น 2
ROLLBACK TRAN Aสามครั้ง: จะยกเลิก "DML Query 4", "DML Query 3" และ "DML Query 2" จากนั้นจะย้อนกลับธุรกรรมทั้งหมด (ทั้งหมดที่เหลือคือ "DML Query 1") @@TRANCOUNTตอนนี้เป็น 0
COMMITหนึ่งครั้ง: @@TRANCOUNTลงไปที่ 1
COMMITหนึ่งครั้งแล้วROLLBACK TRAN Bครั้งหนึ่ง: @@TRANCOUNTลงไปที่ 1 จากนั้นจะยกเลิก "DML Query 4" (พิสูจน์ว่า COMMIT ไม่ได้ทำอะไรเลย) @@TRANCOUNTยังคงเป็น 1
ชื่อธุรกรรมและชื่อจุดบันทึก:
- สามารถมีอักขระได้สูงสุด 32 ตัว
- ได้รับการปฏิบัติเสมือนว่ามีการจัดเรียงไบนารี (ไม่คำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ตามเอกสารปัจจุบัน) โดยไม่คำนึงถึงการจัดเรียงระดับอินสแตนซ์หรือระดับฐานข้อมูล
- สำหรับรายละเอียดโปรดดูที่ส่วนชื่อธุรกรรมของโพสต์ต่อไปนี้: มีอะไรในชื่อ: ภายใน Wacky World of T-SQL Identifiers
ขั้นตอนการจัดเก็บไม่ใช่ธุรกรรมโดยนัย แบบสอบถามแต่ละรายการหากไม่มีการเริ่มต้นธุรกรรมที่ชัดเจนเป็นธุรกรรมโดยนัย นี่คือเหตุผลที่การทำธุรกรรมที่ชัดเจนเกี่ยวกับแบบสอบถามเดียวไม่จำเป็นเว้นแต่จะมีเหตุผลเชิงโปรแกรมในการทำROLLBACKมิฉะนั้นข้อผิดพลาดใด ๆ ในแบบสอบถามคือการย้อนกลับอัตโนมัติของแบบสอบถามนั้น
เมื่อเรียกโพรซีเดอร์ที่เก็บไว้จะต้องออกโดยมีค่า@@TRANCOUNTเท่ากับเมื่อถูกเรียก หมายความว่าคุณไม่สามารถ:
- เริ่มต้น
BEGIN TRANใน proc โดยไม่ต้องทำมันโดยคาดว่าจะยอมรับในกระบวนการการเรียก / ผู้ปกครอง
- คุณไม่สามารถออกคำสั่ง
ROLLBACKหากธุรกรรมที่ชัดเจนเริ่มต้นขึ้นก่อนที่จะมีการเรียก proc เนื่องจากมันจะกลับมา@@TRANCOUNTเป็น 0
หากคุณออกจากกระบวนงานที่เก็บไว้โดยมีจำนวนธุรกรรมที่สูงกว่าหรือต่ำกว่าเมื่อมันจ้องคุณจะได้รับข้อผิดพลาดคล้ายกับ:
ข่าวสารเกี่ยวกับ 266, ระดับ 16, สถานะ 2, กระบวนงาน YourProcName, บรรทัด 0
ธุรกรรมนับหลังจากดำเนินการระบุจำนวนไม่ตรงกันของคำสั่ง BEGIN และ COMMIT จำนวนก่อนหน้า = X, จำนวนปัจจุบัน = Y
ตัวแปรตารางเช่นเดียวกับตัวแปรปกติไม่ถูกผูกมัดโดยการทำธุรกรรม
เกี่ยวกับการจัดการธุรกรรมใน procs ที่สามารถเรียกได้ว่าเป็นอิสระ (และจำเป็นต้องมีการจัดการธุรกรรม) หรือการโทรจาก procs อื่น ๆ (จึงไม่ต้องการการจัดการธุรกรรม): สามารถทำได้ในสองวิธีที่แตกต่างกัน
วิธีที่ฉันจัดการกับมันมาหลายปีแล้วตอนนี้ที่ดูเหมือนว่าจะทำงานได้ดีก็คือBEGIN/ COMMIT/ ROLLBACKที่ชั้นนอกสุดเท่านั้น การเรียก sub-proc เพียงข้ามคำสั่งการทำธุรกรรม ฉันได้อธิบายไว้ด้านล่างสิ่งที่ฉันใส่ลงในแต่ละ proc (ดีแต่ละคนที่ต้องการจัดการธุรกรรม)
- ที่ด้านบนของแต่ละ proc
DECLARE @InNestedTransaction BIT;
ใช้งานง่ายBEGIN TRANทำ:
IF (@@TRANCOUNT = 0)
BEGIN
SET @InNestedTransaction = 0;
BEGIN TRAN; -- only start a transaction if not already in one
END;
ELSE
BEGIN
SET @InNestedTransaction = 1;
END;
ใช้งานง่ายCOMMITทำ:
IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
BEGIN
COMMIT;
END;
ใช้งานง่ายROLLBACKทำ:
IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
BEGIN
ROLLBACK;
END;
วิธีการนี้ควรใช้วิธีเดียวกันโดยไม่คำนึงว่าธุรกรรมเริ่มต้นขึ้นภายใน SQL Server หรือเริ่มต้นที่ชั้นแอป
สำหรับเทมเพลตเต็มรูปแบบของการจัดการการทำธุรกรรมนี้ภายในTRY...CATCHโครงสร้างโปรดดูคำตอบของฉันสำหรับคำถาม DBA.SE ต่อไปนี้: เราจำเป็นต้องจัดการธุรกรรมในรหัส C # รวมถึงขั้นตอนการจัดเก็บหรือไม่
นอกเหนือจาก "พื้นฐาน" แล้วยังมีความแตกต่างของการทำธุรกรรมเพิ่มเติมที่ต้องระวัง:
ตามค่าเริ่มต้นธุรกรรมส่วนใหญ่จะไม่ย้อนกลับ / ยกเลิกโดยอัตโนมัติเมื่อเกิดข้อผิดพลาด ซึ่งมักจะไม่ใช่ปัญหาตราบใดที่คุณมีการจัดการข้อผิดพลาดที่เหมาะสมและเรียกROLLBACKตัวเอง อย่างไรก็ตามบางครั้งสิ่งต่าง ๆ มีความซับซ้อนเช่นในกรณีที่เกิดข้อผิดพลาดในการยกเลิกแบทช์หรือเมื่อใช้OPENQUERY(หรือเซิร์ฟเวอร์ที่เชื่อมโยงโดยทั่วไป) และเกิดข้อผิดพลาดในระบบระยะไกล ในขณะที่ข้อผิดพลาดส่วนใหญ่สามารถถูกดักจับได้โดยใช้TRY...CATCHมีสองข้อที่ไม่สามารถดักจับด้วยวิธีนั้นได้ (จำไม่ได้ว่ามีข้อผิดพลาดใดเกิดขึ้นในขณะนี้ - กำลังค้นคว้า) ในกรณีเหล่านี้คุณต้องใช้SET XACT_ABORT ONในการย้อนกลับธุรกรรมอย่างถูกต้อง
ชุด XACT_ABORT ONทำให้ SQL Server ย้อนกลับธุรกรรมใด ๆทันที (หากมีการใช้งานอยู่) และยกเลิกชุดงานหากมีข้อผิดพลาดเกิดขึ้น การตั้งค่านี้มีอยู่ก่อน SQL Server 2005 ซึ่งแนะนำการTRY...CATCHสร้าง ส่วนใหญ่ที่TRY...CATCHจะจัดการกับสถานการณ์ส่วนใหญ่และอื่น ๆ ส่วนใหญ่ obsoletes XACT_ABORT ONความจำเป็นในการ แต่เมื่อใช้OPENQUERY(และอาจจะเป็นหนึ่งสถานการณ์อื่น ๆ ที่ผมจำไม่ได้ในขณะนี้) SET XACT_ABORT ON;แล้วคุณยังจะต้องใช้
ภายในของไกถูกตั้งค่าโดยปริยายXACT_ABORT ONนี้ทำให้เกิดการใด ๆ ที่ผิดพลาดภายในทริกเกอร์ที่จะยกเลิกคำสั่ง DML ทั้งที่ยิง Trigger
คุณควรจัดการข้อผิดพลาดที่เหมาะสมโดยเฉพาะอย่างยิ่งเมื่อใช้ธุรกรรม TRY...CATCHสร้างแนะนำใน SQL Server 2005 ให้ความหมายของการจัดการเกือบทุกสถานการณ์ต้อนรับการปรับปรุงมากกว่าการทดสอบ@@ERRORหลังจากแต่ละคำสั่งซึ่งไม่ได้ช่วยมากมีข้อผิดพลาดชุดยกเลิก
TRY...CATCHแนะนำ "รัฐ" ใหม่อย่างไรก็ตาม เมื่อไม่ได้ใช้TRY...CATCHโครงสร้างหากคุณมีธุรกรรมที่ใช้งานอยู่และเกิดข้อผิดพลาดมีหลายเส้นทางที่สามารถทำได้:
XACT_ABORT OFFและข้อผิดพลาดการยกเลิกคำสั่ง: การทำธุรกรรมยังคงใช้งานอยู่และการประมวลผลดำเนินการต่อด้วยคำสั่งถัดไปถ้ามี
XACT_ABORT OFFและข้อผิดพลาดการยกเลิกแบทช์ส่วนใหญ่: ธุรกรรมยังคงใช้งานอยู่และการประมวลผลจะดำเนินต่อไปด้วยแบทช์ถัดไปหากมี
XACT_ABORT OFFและข้อผิดพลาดการยกเลิกแบทช์บางอย่าง: ธุรกรรมถูกย้อนกลับและการประมวลผลจะดำเนินการกับแบทช์ถัดไปถ้ามี
XACT_ABORT ONและข้อผิดพลาดใด ๆ : ธุรกรรมถูกย้อนกลับและการประมวลผลดำเนินการต่อด้วยชุดงานถัดไปหากมี
HOWEVER เมื่อใช้TRY...CATCHงานข้อผิดพลาดในการยกเลิกชุดงานจะไม่ยกเลิกชุด แต่เป็นการโอนการควบคุมไปยังCATCHบล็อกแทน เมื่อXACT_ABORTเป็นOFFธุรกรรมจะยังคงใช้งานส่วนใหญ่ของเวลาและคุณจะต้องหรือส่วนใหญ่COMMIT ROLLBACKแต่เมื่อพบข้อผิดพลาดการยกเลิกแบทช์บางอย่าง (เช่นกับOPENQUERY) หรือเมื่อXACT_ABORTใดONธุรกรรมจะอยู่ในสถานะใหม่ "ไม่น่าเชื่อถือ" ในสถานะนี้คุณไม่สามารถและไม่COMMITสามารถทำการดำเนินการ DML ใด ๆ สิ่งที่คุณสามารถทำได้คือROLLBACKและSELECTคำสั่ง อย่างไรก็ตามในสถานะ "uncomittable" นี้ธุรกรรมถูกย้อนกลับเมื่อเกิดข้อผิดพลาดและการออกROLLBACKเป็นเพียงรูปแบบ แต่ต้องทำ
ฟังก์ชั่นXACT_STATEสามารถนำมาใช้เพื่อตรวจสอบว่าการทำธุรกรรมมีการใช้งานผิดปกติหรือไม่มีอยู่ ก็จะแนะนำ (โดยบางส่วนอย่างน้อย) เพื่อตรวจสอบฟังก์ชั่นนี้ในCATCHบล็อกเพื่อตรวจสอบว่าผลที่ได้คือ-1(เช่น uncommitable) @@TRANCOUNT > 0แทนการทดสอบถ้า แต่ด้วยXACT_ABORT ONที่ควรจะเป็นรัฐที่เป็นไปได้เท่านั้นที่จะอยู่ในดังนั้นจึงดูเหมือนว่าการทดสอบ@@TRANCOUNT > 0และXACT_STATE() <> 0เทียบเท่า ในทางกลับกันเมื่อXACT_ABORTเป็นOFFและมีธุรกรรมที่ใช้งานอยู่แล้วมันเป็นไปได้ที่จะมีสถานะเป็นอย่างใดอย่างหนึ่ง1หรือ-1อยู่ในCATCHบล็อกซึ่งอนุญาตให้มีความเป็นไปได้ในการออกCOMMITแทนROLLBACK(แม้ว่าฉันจะไม่นึกถึงกรณี อยากจะCOMMITหากธุรกรรมสามารถกระทำได้) ข้อมูลเพิ่มเติมและการวิจัยเกี่ยวกับการใช้XACT_STATE()ภายในCATCHบล็อกด้วยXACT_ABORT ONสามารถพบได้ในคำตอบของฉันสำหรับคำถาม DBA.SE ต่อไปนี้: ในกรณีใดธุรกรรมสามารถกระทำจากภายในบล็อก CATCH เมื่อตั้ง XACT_ABORT เป็น ON . โปรดทราบว่ามีข้อผิดพลาดเล็กน้อยXACT_STATE()ที่ทำให้มันกลับมาผิดพลาด1ในบางสถานการณ์: XACT_STATE () ส่งคืน 1 เมื่อใช้ใน SELECT พร้อมกับตัวแปรระบบบางส่วน แต่ไม่มี FROM clause
หมายเหตุเกี่ยวกับรหัสเดิม:
- คุณสามารถลบชื่อที่กำหนดให้กับการทำธุรกรรมเพราะมันไม่ได้ช่วยอะไรเลย
- คุณไม่ต้องการ
BEGINและENDรอบการEXECโทรแต่ละครั้ง
spNewBilling3โยนข้อผิดพลาด แต่คุณไม่ต้องการที่จะย้อนกลับspNewBilling2หรือspNewBilling1แล้วก็ลบออกจาก[begin|rollback|commit] transaction createSavebillinginvoicespSavesomename