التعامل مع الأخطاء باستخدام TRY... القبض
يجب على تطبيقات قواعد البيانات الإنتاجية التعامل مع المواقف غير المتوقعة برشاقة. القسمة على الصفر، انتهاكات القيود، مهلات الاتصال، والبيانات غير الصالحة كلها يمكن أن تسبب أخطاء. تؤدي الأخطاء غير المطبقة إلى رسائل خطأ غير واضحة، أو معاملات غير مكتملة، أو تعطل التطبيقات. يضمن التعامل الصحيح مع الأخطاء فشل كود T-SQL بشكل متوقع ويوفر تغذية راجعة ذات معنى.
تتفاعل عمليات قواعد البيانات مع مستخدمين متعددين، وأنظمة خارجية، ومدخلات بيانات غير متوقعة في نفس الوقت. على عكس كود التطبيق الذي قد يتعافى من عملية فاشلة بإعادة المحاولة، يمكن أن تترك أخطاء قاعدة البيانات البيانات في حالة غير متسقة، حيث يتم إدخال بعض الصفوف وأخرى لا، أو مع تثبيت الأقفال إلى أجل غير مسمى. يحول التعامل مع الأخطاء هذه الأوضاع الفوضوية للفشل إلى استجابات محكومة ومتوقعة.
معالجة الأخطاء المصممة بشكل جيد تحسن كودك بعدة طرق:
حماية سلامة البيانات: عندما تفشل العملية في منتصف العملية، يضمن التعامل الصحيح مع الأخطاء أن جميع التغييرات إما تلتزم معا أو لا تستمر أي منها. بدون ذلك، قد تترك العملية متعددة الخطوات قاعدة بياناتك مع سجلات متفائلة، أو أرقام غير متطابقة، أو علاقات متقطعة.
كفاءة التصحيح: التقاط تفاصيل الأخطاء مثل رقم السطر، اسم الإجراء، ورسالة الخطأ المحددة يجعل عملية استكشاف الأخطاء أسرع. بدلا من البحث في السجلات عن أعطال غامضة، يمكنك تحديد مكان وسبب حدوث الخطأ بالضبط.
تجربة المستخدم: يمكن للتطبيقات عرض رسائل ذات معنى مثل "معرف المنتج غير موجود" بدلا من أخطاء قواعد البيانات الغامضة. هذا يساعد المستخدمين على فهم ما حدث وكيفية إصلاحه.
الرؤية التشغيلية: تسجيل الأخطاء إلى جدول مخصص ينشئ مسار تدقيق يساعد في تحديد الأنماط، مثل تكرار انتهاكات القيود التي تشير إلى خطأ أو أخطاء مهلة النهاية التي تشير إلى مشاكل في الأداء.
تدهور رشيق: عندما تفشل إحدى العمليات، يسمح التعامل مع الأخطاء لبقية الكود بالاستمرار أو اتخاذ إجراء بديل، بدلا من تعطل الدفعة أو الإجراء المخزن.
تنفيذ معالجة أخطاء T-SQL
يوفر T-SQL معالجة أخطاء منظمة عبر TRY...CATCH الكتل، مشابها لمعالجة الاستثناءات في لغات البرمجة الأخرى. عندما يحدث خطأ في الكتلة TRY ، ينتقل التنفيذ إلى الكتلة CATCH حيث يمكنك التعامل مع الخطأ بشكل مناسب:
BEGIN TRY
-- TRY block contains code that might cause an error
-- If an error occurs here, execution jumps to the CATCH block
SELECT 1/0; -- This causes a division by zero error
END TRY
BEGIN CATCH
-- CATCH block handles the error
-- This code runs only if an error occurred in the TRY block
PRINT 'An error occurred';
END CATCH;
بدون معالجة الأخطاء، ينتهي نفس الكود برسالة خطأ:
SELECT 1/0; -- Msg 8134: Divide by zero error encountered
ملاحظة
TRY...CATCH لا يمكن التقاط كل الأخطاء. أخطاء التجميع (أخطاء في الصياغة، الكائنات المفقودة) والأخطاء ذات شدة 20 أو أعلى التي تغلق الاتصال لا يمكن اكتشافها خلال نفس الجلسة.
استرجاع معلومات الخطأ
داخل الكتلة CATCH ، يوفر SQL Server الوظائف التالية لاسترجاع تفاصيل الخطأ الذي حدث:
| Function | وصف |
|---|---|
ERROR_NUMBER() |
يعيد رقم الخطأ |
ERROR_MESSAGE() |
يرجع نص رسالة الخطأ الكامل |
ERROR_SEVERITY() |
يعيد شدة الخطأ (0-25) |
ERROR_STATE() |
يعيد رقم حالة الخطأ |
ERROR_LINE() |
يرجع رقم السطر حيث حدث الخطأ |
ERROR_PROCEDURE() |
يعيد اسم الإجراء أو المشغل المخزن |
يوضح المثال التالي كيفية التقاط تفاصيل الأخطاء وتوثيقها في جدول لتحليلها لاحقا:
BEGIN TRY
-- Attempt an operation that might fail
INSERT INTO SalesLT.Customer (CustomerID, FirstName, LastName)
VALUES (1, 'Test', 'Customer'); -- Duplicate key causes error
END TRY
BEGIN CATCH
-- Log error details to a table using the ERROR_* functions
INSERT INTO ErrorLog (
ErrorTime,
ErrorNumber,
ErrorSeverity,
ErrorState,
ErrorProcedure,
ErrorLine,
ErrorMessage
)
VALUES (
GETDATE(),
ERROR_NUMBER(), -- The error number (e.g., 2627 for duplicate key)
ERROR_SEVERITY(), -- Severity level (0-25)
ERROR_STATE(), -- Error state for debugging
ISNULL(ERROR_PROCEDURE(), 'Ad hoc query'), -- NULL if not in a procedure
ERROR_LINE(), -- Line number where error occurred
ERROR_MESSAGE() -- Full error message text
);
-- Re-raise the error to the calling application
THROW;
END CATCH;
نصيحة
دائما قم بتسجيل الأخطاء قبل إعادة إظهارها. بمجرد استخدام THROW أو RAISERROR، تعود NULL دوال الخطأ إذا تم استدعاؤها مرة أخرى.
التعامل مع المعاملات باستخدام TRY... القبض
عندما تحدث أخطاء داخل المعاملات، يجب عليك التراجع صراحة عن العمل غير الملتزم. تخبرك الدالة @@TRANCOUNT ما إذا كانت المعاملة نشطة:
BEGIN TRY
-- Start a transaction to group multiple operations
BEGIN TRANSACTION;
-- First operation: update product prices
UPDATE SalesLT.Product
SET ListPrice = ListPrice * 1.05
WHERE ProductCategoryID = 5;
-- Second operation: update order totals
-- If this fails, we want to undo the first update too
UPDATE SalesLT.SalesOrderHeader
SET TotalDue = TotalDue * 1.05
WHERE CustomerID = 12345;
-- Both operations succeeded, make changes permanent
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Check if a transaction is still active before rolling back
-- Some errors auto-rollback, so @@TRANCOUNT might be 0
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION; -- Undo all changes from this transaction
-- Re-raise the error so the caller knows something failed
THROW;
END CATCH;
الشيك @@TRANCOUNT مهم لأن:
- قد يحدث خطأ قبل ذلك
BEGIN TRANSACTION - بعض الأخطاء تؤدي تلقائيا إلى التراجع عن المعاملة قبل الوصول إليها
CATCH - محاولة التراجع دون إجراء معاملة نشطة تسبب خطأ آخر
مهم
تأكد @@TRANCOUNT دائما قبل الاتصال ROLLBACK TRANSACTION في أي CATCH حي. هذا يمنع الخطأ "طلب المعاملة التراجع ليس له BEGIN TRANSACTION مقابل."
رفع الأخطاء المخصصة مع THROW
تثير العبارة THROW استثناء مع رقم خطأ مخصص ورسالة رسالة. استخدمه للإشارة إلى حالات الخطأ الخاصة بالتطبيق:
CREATE PROCEDURE ProcessOrder
@OrderID INT,
@Quantity INT
AS
BEGIN
BEGIN TRY
-- Validate input and raise custom errors for invalid data
IF @Quantity <= 0
THROW 50001, 'Quantity must be greater than zero.', 1;
IF NOT EXISTS (SELECT 1 FROM Orders WHERE OrderID = @OrderID)
THROW 50002, 'Order not found.', 1;
-- Process the order
UPDATE Orders
SET Quantity = @Quantity
WHERE OrderID = @OrderID;
END TRY
BEGIN CATCH
-- Log the error before reraising
EXEC LogError;
-- THROW without parameters reraises the current error
THROW;
END CATCH;
END;
يجب أن تكون أرقام الأخطاء المخصصة للأخطاء التي يحددها المستخدم 50000 أو أكثر. معامل الحالة (1 في الأمثلة) هو قيمة يحددها المستخدم بين 1 و255 يمكن أن تساعد في تحديد مكان ظهور الخطأ.
الاستخدام RAISERROR للرسائل المنسقة
RAISERROR يوفر خيارات تنسيق أكثر من THROW، بما في ذلك استبدال معلمات نمط PrintF. إدراج قيم وقت التشغيل في رسائل الخطأ يجعل التصحيح أسهل لأنك تستطيع رؤية البيانات التي تسببت في الفشل بدقة دون الحاجة إلى البحث في السجلات أو تكرار المشكلة:
DECLARE @ProductName NVARCHAR(100) = 'Widget Pro';
DECLARE @CurrentStock INT = 5;
DECLARE @RequestedQty INT = 10;
IF @CurrentStock < @RequestedQty
BEGIN
RAISERROR(
'Insufficient stock for product "%s". Available: %d, Requested: %d',
16, -- Severity
1, -- State
@ProductName,
@CurrentStock,
@RequestedQty
);
END;
ملاحظة
THROW هو النهج الموصى به للكود الجديد لأنه أبسط ودائما يتضمن تتبع المكدس. استخدمها RAISERROR عندما تحتاج إلى رسائل منسقة أو توافق مع أنماط معالجة الأخطاء الحالية.
تنفيذ معالجة الأخطاء المتداخلة
الإجراءات المخزنة التي تستدعي إجراءات أخرى تحتاج إلى معالجة أخطاء منسقة. يجب أن يتعامل كل مستوى مع تنظيفه الخاص وينشر الأخطاء بشكل مناسب:
CREATE PROCEDURE OuterProcedure
AS
BEGIN
BEGIN TRY
-- Outer procedure owns the transaction
BEGIN TRANSACTION;
-- First operation in the outer procedure
UPDATE SomeTable SET Column1 = 'Value';
-- Call nested procedure - if it fails, error propagates here
EXEC InnerProcedure;
-- All operations succeeded, commit the transaction
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Outer procedure handles rollback for all nested calls
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
-- Propagate error to the application
THROW;
END CATCH;
END;
GO
CREATE PROCEDURE InnerProcedure
AS
BEGIN
BEGIN TRY
-- Inner procedure does its work within the outer's transaction
UPDATE AnotherTable SET Column2 = 'Value';
END TRY
BEGIN CATCH
-- Don't rollback here - let the outer procedure handle it
-- This keeps transaction management in one place
THROW; -- Re-raise error to outer procedure
END CATCH;
END;
الاستخدام XACT_ABORT للرجوع التلقائي
يمكنك ضبط XACT_ABORT ON جعل SQL Server يعيد المعاملة تلقائيا عند حدوث أي خطأ، حتى بدون TRY...CATCH هذا:
SET XACT_ABORT ON;
BEGIN TRANSACTION;
UPDATE Table1 SET Col1 = 'A';
UPDATE Table2 SET Col2 = 'B'; -- If this fails, entire transaction rolls back
UPDATE Table3 SET Col3 = 'C';
COMMIT TRANSACTION;
الدمج XACT_ABORT مع TRY...CATCH يمنحك فوائد كلا النهجين: XACT_ABORT يضمن استرجاع فوري لأي خطأ، كما TRY...CATCH يسمح لك بتسجيل تفاصيل الأخطاء وإجراء تنظيف مخصص قبل انتشار الخطأ:
-- XACT_ABORT ON ensures automatic rollback on any error
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- Execute multiple procedures as a single unit of work
EXEC Procedure1; -- If any of these fail...
EXEC Procedure2; -- ...XACT_ABORT automatically rolls back...
EXEC Procedure3; -- ...and jumps to the CATCH block
-- All succeeded, commit the changes
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- With XACT_ABORT ON, the transaction is usually already rolled back
-- This check handles edge cases where it might still be active
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
-- Log the error details before re-raising
EXEC LogError;
-- Let the caller know an error occurred
THROW;
END CATCH;
نصيحة
الاستخدام SET XACT_ABORT ON هو ممارسة مثلى للإجراءات المخزنة، خاصة تلك التي تمتد عبر عدة عمليات. يضمن سلوكا متسقا بغض النظر عن الخطأ المحدد الذي يحدث.
لمزيد من المعلومات حول التعامل مع الأخطاء، راجع TRY... التقاطها (Transact-SQL)وارمي (Transact-SQL).