ให้รหัสที่แสดงในคำถามเท่านั้นและสมมติว่าไม่มีโปรแกรมย่อยสามรายการใดที่มีการจัดการธุรกรรมอย่างชัดเจนดังนั้นใช่ข้อผิดพลาดในโปรแกรมย่อยสามรายการใด ๆ จะถูกดักจับและ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 createSavebillinginvoice
spSavesomename