Cách khôi phục ngoại lệ tương tự trong SQL Server


86

Tôi muốn tạo lại cùng một ngoại lệ trong SQL Server vừa xảy ra trong khối thử của tôi. Tôi có thể gửi cùng một thông báo nhưng tôi muốn ném cùng một lỗi.

BEGIN TRANSACTION
    BEGIN TRY
        INSERT INTO Tags.tblDomain (DomainName, SubDomainId, DomainCode, Description)
            VALUES(@DomainName, @SubDomainId, @DomainCode, @Description)
        COMMIT TRANSACTION
    END TRY
    
    BEGIN CATCH
        declare @severity int; 
        declare @state int;

        select @severity=error_severity(), @state=error_state();

        RAISERROR(@@Error,@ErrorSeverity,@state);
        ROLLBACK TRANSACTION
    END CATCH

RAISERROR(@@Error, @ErrorSeverity, @state);

Dòng này sẽ hiển thị lỗi, nhưng tôi muốn chức năng giống như vậy. Điều này gây ra lỗi với số lỗi 50000, nhưng tôi muốn số lỗi được đưa ra mà tôi đang vượt qua @@error,

Tôi muốn ghi lại lỗi này không có ở giao diện người dùng.

I E

catch (SqlException ex)
{
    if ex.number==2627
    MessageBox.show("Duplicate value cannot be inserted");
}

Tôi muốn chức năng này. mà không thể đạt được bằng cách sử dụng raiseerror. Tôi không muốn đưa ra thông báo lỗi tùy chỉnh ở phía sau.

RAISEERROR sẽ trả về lỗi được đề cập bên dưới khi tôi vượt qua ErrorNo để được ném vào bắt

Msg 2627, Level 14, State 1, Procedure spOTest_DomainInsert,

Dòng 14 Vi phạm ràng buộc KEY DUY NHẤT 'UK_DomainCode'. Không thể chèn khóa trùng lặp trong đối tượng 'Tags.tblDomain'. Các tuyên bố này đã bị chấm dứt.

BIÊN TẬP:

Hạn chế của việc không sử dụng khối try catch là gì nếu tôi muốn ngoại lệ được xử lý ở giao diện người dùng xem xét thủ tục được lưu trữ chứa nhiều truy vấn cần được thực thi?

Câu trả lời:


120

Đây là một mẫu mã sạch đầy đủ chức năng để khôi phục một loạt các câu lệnh nếu xảy ra lỗi và báo cáo thông báo lỗi.

begin try
    begin transaction;

    ...

    commit transaction;
end try
begin catch
    if @@trancount > 0 rollback transaction;
    throw;
end catch

Trước SQL 2012

begin try
    begin transaction;
    
    ...
    
    commit transaction;
end try
begin catch
    declare @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int;
    select @ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();
    if @@trancount > 0 rollback transaction;
    raiserror (@ErrorMessage, @ErrorSeverity, @ErrorState);
end catch

8
Tôi đang sử dụng nó ở giữa một thủ tục được lưu trữ và nhận thấy rằng nó sẽ tiếp tục thực thi sau raiserrorđó, điều này khác với cách c # thoát sau a throw. Vì vậy, tôi đã thêm một returnbên trong catchbởi vì tôi muốn phù hợp với hành vi đó.
Brian J

@BogdanBogdanov tôi cuộn lại chỉnh sửa của bạn bởi vì điểm của mã này là để được tối thiểu và không làm giảm uy tín từ các mã thực nhập vào vị trí của ...
Bến Gripka

Được rồi, không sao cả, @Ben Gripka. Tôi cố gắng làm cho nó dễ đọc hơn trên màn hình. Cảm ơn bạn đã chỉ ra lý do để thực hiện khôi phục.
Bogdan Bogdanov

1
@BrianJ: thông thường, việc thực thi có dừng lại hay không phụ thuộc vào mức độ nghiêm trọng của lỗi ban đầu. Nếu mức độ nghiêm trọng> = 11 thì quá trình thực hiện sẽ dừng lại. Nó thực sự kỳ lạ, bởi vì kẻ tấn công bên trong khối bắt với mức độ nghiêm trọng> = 11 không ngừng thực hiện nữa. Quan sát của bạn rất tốt và nó cho thấy máy chủ sql chết não như thế nào, ít nhất là 2008r2. Các phiên bản mới hơn có vẻ tốt hơn.
costa

1
@costa Xem RAISERROR()tài liệu của . Mức độ nghiêm trọng ≥11 chỉ chuyển đến một CATCHkhối nếu nó nằm trong một TRYkhối. Vì vậy, bạn phảiBEGIN TRY…END CATCH xung quanh mã nếu bạn muốn của bạn RAISERROR()ảnh hưởng đến kiểm soát luồng.
binki,

137

SQL 2012 giới thiệu câu lệnh ném:

http://msdn.microsoft.com/en-us/library/ee677615.aspx

Nếu câu lệnh THROW được chỉ định mà không có tham số, nó phải xuất hiện bên trong khối CATCH. Điều này làm cho ngoại lệ đã bắt được tăng lên.

BEGIN TRY
    BEGIN TRANSACTION
    ...
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW
END CATCH

2
Lưu ý, có vẻ như giải pháp này chỉ hoạt động từ máy chủ sql 2012 trở lên: msdn.microsoft.com/en-us/library/ee677615.aspx
Adi

3
@BogdanBogdanov Vì vậy mà bạn có thể đăng nhập lỗi, có thể xử lý số trường hợp, nhưng nếu bạn không có thì bạn muốn rethrow lỗi vì bất kỳ thử cao lên / catch sau đó có thể có một cơ hội để xử lý nó
Robert McKee

Vâng, @Robert McKee. Tôi tìm ra điều đó. Xin lỗi, đã quên xóa bình luận này.
Bogdan Bogdanov

3
Dấu chấm phẩy trên ROLLBACKdòng là quan trọng! Nếu không có nó, bạn có thể nhận được một SQLException: Cannot roll back THROW.
idontevenseethecode

5

Lật lại bên trong khối CATCH (mã trước SQL2012, sử dụng câu lệnh THROW cho SQL2012 trở lên):

DECLARE
    @ErrorMessage nvarchar(4000) = ERROR_MESSAGE(),
    @ErrorNumber int = ERROR_NUMBER(),
    @ErrorSeverity int = ERROR_SEVERITY(),
    @ErrorState int = ERROR_STATE(),
    @ErrorLine int = ERROR_LINE(),
    @ErrorProcedure nvarchar(200) = ISNULL(ERROR_PROCEDURE(), '-');
SELECT @ErrorMessage = N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' + 'Message: ' + @ErrorMessage;
RAISERROR (@ErrorMessage, @ErrorSeverity, 1, @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine)

4

Tôi nghĩ lựa chọn của bạn là:

  • Không bắt lỗi (để nó nổi lên)
  • Nâng cao một tùy chỉnh

Tại một số điểm, SQL có thể sẽ giới thiệu một lệnh reraise, hoặc khả năng chỉ bắt một số lỗi nhất định. Nhưng hiện tại, hãy sử dụng một giải pháp thay thế. Lấy làm tiếc.


7
trong sql 2012, bạn có thể tăng lại một ngoại lệ bằng cách sử dụng từ khóa
THROW

5
Đúng. Tất nhiên, điều đó không có sẵn khi câu hỏi này được hỏi.
Rob Farley

Điều quan trọng hơn là bắt và ném một lỗi mới hơn là không bắt nó và để nó 'nổi bong bóng' bởi vì bạn có thể sẽ yêu cầu một số hoạt động dọn dẹp, sửa chữa và đóng lại để xử lý ngoại lệ đúng cách. Một ví dụ rõ ràng là đóng và loại bỏ con trỏ. Các ví dụ khác có thể là thực hiện quy trình ghi nhật ký hoặc đặt lại một số dữ liệu.
Antony Booth

1

Bạn không thể: chỉ động cơ mới có thể tạo ra lỗi nhỏ hơn 50000. Tất cả những gì bạn có thể làm là ném một ngoại lệ giống như nó ...

Vui lòng xem câu trả lời của tôi ở đây

Người hỏi ở đây đã sử dụng các giao dịch phía khách hàng để làm những gì anh ta muốn mà tôi nghĩ là hơi ngớ ngẩn ...


0

Ok, đây là một giải pháp thay thế ... :-)

DECLARE @Error_Number INT
BEGIN TRANSACTION 
    BEGIN TRY
    INSERT INTO Test(Id, Name) VALUES (newID(),'Ashish') 
    /* Column 'Name' has unique constraint on it*/
    END TRY
    BEGIN CATCH

            SELECT ERROR_NUMBER()
            --RAISERROR (@ErrorMessage,@Severity,@State)
            ROLLBACK TRAN
    END CATCH

Nếu bạn lưu ý khối bắt, Nó không tăng lỗi mà trả về số lỗi thực tế (và cũng sẽ khôi phục giao dịch). Bây giờ trong mã .NET của bạn, thay vì bắt ngoại lệ, nếu bạn sử dụng ExecuteScalar (), bạn sẽ nhận được số lỗi thực tế mà bạn muốn và hiển thị số thích hợp.

int errorNumber=(int)command.ExecuteScalar();
if(errorNumber=<SomeNumber>)
{
    MessageBox.Show("Some message");
}

Hi vọng điêu nay co ich,

CHỈNH SỬA: - Chỉ cần lưu ý, Nếu bạn muốn có số lượng bản ghi bị ảnh hưởng và cố gắng sử dụng ExecuteNonQuery, giải pháp trên có thể không hiệu quả với bạn. Nếu không, tôi nghĩ nó sẽ phù hợp với những gì bạn cần. Cho tôi biết.


@Ashish Gupta: Thx để được giúp đỡ, nhưng tôi cần ngoại lệ được ném từ cơ sở dữ liệu để các lối vào, nếu không tôi có nhiều lựa chọn mở như ERROR_NUMBER in (), trả lại ERROR_NUMBER và 1 u gợi ý
Shantanu Gupta

0

Cách để dừng thực thi trong một thủ tục được lưu trữ sau khi xảy ra lỗi và đánh dấu lỗi trở lại chương trình đang gọi là làm theo từng câu lệnh có thể gây ra lỗi với mã này:

If @@ERROR > 0
Return

Bản thân tôi đã rất ngạc nhiên khi phát hiện ra rằng việc thực thi trong một thủ tục được lưu trữ có thể tiếp tục sau khi có lỗi - việc không nhận ra điều này có thể dẫn đến một số lỗi khó theo dõi.

Loại xử lý lỗi này song song (pre .Net) Visual Basic 6. Mong đợi lệnh Throw trong SQL Server 2012.


0

Giả sử rằng bạn vẫn chưa chuyển sang năm 2012, một cách để triển khai mã lỗi ban đầu là sử dụng phần tin nhắn văn bản của ngoại lệ bạn đang (lại) ném từ khối bắt. Hãy nhớ rằng nó có thể chứa một số cấu trúc, chẳng hạn như văn bản XML để mã người gọi của bạn phân tích cú pháp trong khối bắt của nó.


0

Bạn cũng có thể tạo một thủ tục được lưu trữ trong trình bao bọc cho các trường hợp đó khi bạn muốn câu lệnh SQL được thực thi trong giao dịch và cung cấp lỗi cho mã của bạn.

CREATE PROCEDURE usp_Execute_SQL_Within_Transaction
(
    @SQL nvarchar(max)
)
AS

SET NOCOUNT ON

BEGIN TRY
    BEGIN TRANSACTION
        EXEC(@SQL)
    COMMIT TRANSACTION
END TRY

BEGIN CATCH
    DECLARE @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int
    SELECT @ErrorMessage = N'Error Number: ' + CONVERT(nvarchar(5), ERROR_NUMBER()) + N'. ' + ERROR_MESSAGE() + ' Line ' + CONVERT(nvarchar(5), ERROR_LINE()), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE()
    ROLLBACK TRANSACTION
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState)
END CATCH

GO

-- Test it
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1/0; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'EXEC usp_Another_SP'

-2

Từ quan điểm thiết kế, việc ném các ngoại lệ với số lỗi ban đầu và thông báo tùy chỉnh có ích gì? Ở một mức độ nào đó, nó phá vỡ hợp đồng giao diện giữa các ứng dụng và cơ sở dữ liệu. Nếu bạn muốn bắt lỗi ban đầu và xử lý chúng trong mã cao hơn, đừng xử lý chúng trong cơ sở dữ liệu. Sau đó, khi bạn bắt gặp một ngoại lệ, bạn có thể thay đổi thông báo được hiển thị cho người dùng thành bất kỳ thứ gì bạn muốn. Tuy nhiên, tôi sẽ không làm điều đó, bởi vì nó làm cho mã cơ sở dữ liệu của bạn hmm 'không đúng'. Như những người khác đã nói, bạn nên xác định một tập hợp các mã lỗi của riêng mình (trên 50000) và thay thế chúng. Sau đó, bạn có thể giải quyết các vấn đề về tính toàn vẹn ('Không cho phép các giá trị trùng lặp') riêng biệt với các vấn đề kinh doanh tiềm ẩn - 'Mã zip không hợp lệ', 'Không tìm thấy hàng nào phù hợp với tiêu chí', v.v.


9
Điểm của việc ném các ngoại lệ với số lỗi ban đầu và thông báo tùy chỉnh là gì? Giả sử bạn muốn xử lý một hoặc hai lỗi cụ thể (dự kiến) trực tiếp trong khối bắt và để phần còn lại cho các lớp cao hơn. Vì vậy, bạn cần có thể ném lại các trường hợp ngoại lệ mà bạn đã không xử lý ... tốt nhất là không cần phải báo cáo và xử lý các lỗi theo một số cách khác, đặc biệt.
Jenda

1
Ngoài những gì @Jenda đã giải thích, tôi thích sử dụng try-catch để đảm bảo rằng việc thực thi mã không tiếp tục sau một ngoại lệ, giống như trong C # : try { code(); } catch (Exception exc) { log(exc); throw; } finally { cleanup(); }, throw;sẽ chỉ nêu ra ngoại lệ ban đầu, với ngữ cảnh ban đầu của nó.
R. Schreurs

Tôi bắt lỗi và ném lại thông báo lỗi tùy chỉnh trong SQL để thêm chi tiết mô tả lỗi đã xảy ra ở dòng nào hoặc các chi tiết khác (chẳng hạn như dữ liệu đang cố gắng chèn vào) để giúp tôi theo dõi lỗi sau này.
Russell Hankins
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.