Trong trường hợp nào, một giao dịch có thể được cam kết từ bên trong khối CATCH khi XACT_ABORT được đặt thành BẬT?


13

Tôi đã đọc MSDN về TRY...CATCHXACT_STATE.

Nó có ví dụ sau sử dụng XACT_STATEtrong CATCHkhối của một TRY…CATCHcấu trúc để xác định xem có nên cam kết hay quay lại giao dịch hay không:

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Test XACT_STATE for 0, 1, or -1.
    -- If 1, the transaction is committable.
    -- If -1, the transaction is uncommittable and should 
    --     be rolled back.
    -- XACT_STATE = 0 means there is no transaction and
    --     a commit or rollback operation would generate an error.

    -- Test whether the transaction is uncommittable.
    IF (XACT_STATE()) = -1
    BEGIN
        PRINT 'The transaction is in an uncommittable state.' +
              ' Rolling back transaction.'
        ROLLBACK TRANSACTION;
    END;

    -- Test whether the transaction is active and valid.
    IF (XACT_STATE()) = 1
    BEGIN
        PRINT 'The transaction is committable.' + 
              ' Committing transaction.'
        COMMIT TRANSACTION;   
    END;
END CATCH;
GO

Điều tôi không hiểu là tại sao tôi phải quan tâm và kiểm tra những gì XACT_STATEtrả lại?

Xin lưu ý rằng cờ XACT_ABORTđược đặt ONtrong ví dụ.

Nếu có một lỗi đủ nghiêm trọng bên trong TRYkhối, điều khiển sẽ chuyển vào CATCH. Vì vậy, nếu tôi ở trong CATCH, tôi biết rằng giao dịch đã có vấn đề và thực sự điều hợp lý duy nhất cần làm trong trường hợp này là đẩy nó trở lại, phải không?

Nhưng, ví dụ này từ MSDN ngụ ý rằng có thể có trường hợp khi quyền kiểm soát được chuyển vào CATCHvà vẫn có ý nghĩa để thực hiện giao dịch. Ai đó có thể cung cấp một số ví dụ thực tế khi nó có thể xảy ra, khi nó có ý nghĩa?

Tôi không thấy trong trường hợp nào quyền kiểm soát có thể được chuyển vào bên trong CATCHvới một giao dịch có thể được cam kết khi XACT_ABORTđược đặt thànhON .

Bài viết về MSDN SET XACT_ABORTcó một ví dụ khi một số câu lệnh bên trong một giao dịch thực hiện thành công và một số lỗi khi XACT_ABORTđược đặt thành OFF, tôi hiểu điều đó. Nhưng, SET XACT_ABORT ONlàm thế nào nó có thể xảy ra mà XACT_STATE()trả về 1 trong CATCHkhối?

Ban đầu, tôi đã viết mã này như thế này:

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Some severe problem with the transaction
    PRINT 'Rolling back transaction.';
    ROLLBACK TRANSACTION;
END CATCH;
GO

Có tính đến câu trả lời của Max Vernon, tôi sẽ viết mã như thế này. Ông đã chỉ ra rằng việc kiểm tra xem có giao dịch đang hoạt động hay không trước khi thử ROLLBACK. Tuy nhiên, với SET XACT_ABORT ONcác CATCHkhối có thể có hoặc cam chịu giao dịch hoặc không có giao dịch nào cả. Vì vậy, trong mọi trường hợp không có gì để COMMIT. Tôi có lầm không?

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Some severe problem with the transaction
    IF (XACT_STATE()) <> 0
    BEGIN
        -- There is still an active transaction that should be rolled back
        PRINT 'Rolling back transaction.';
        ROLLBACK TRANSACTION;
    END;

END CATCH;
GO

Câu trả lời:


8

Hóa ra giao dịch không thể được cam kết từ bên trong CATCHkhối nếu XACT_ABORTđược đặt thành ON.

Ví dụ từ MSDN có phần sai lệch, bởi vì kiểm tra ngụ ý XACT_STATEcó thể trả về 1 trong một số trường hợp và có thể COMMITgiao dịch có thể xảy ra .

IF (XACT_STATE()) = 1
BEGIN
    PRINT 'The transaction is committable.' + 
          ' Committing transaction.'
    COMMIT TRANSACTION;   
END;

Điều đó không đúng, XACT_STATEsẽ không bao giờ trả lại 1 CATCHkhối bên trong nếu XACT_ABORTđược đặt thành ON.

Có vẻ như mã mẫu MSDN chủ yếu nhằm minh họa việc sử dụng XACT_STATE()hàm bất kể XACT_ABORTcài đặt. Mã mẫu có vẻ đủ chung để làm việc với cả hai XACT_ABORTđược đặt thành ONOFF. Nó chỉ là với XACT_ABORT = ONviệc kiểm tra IF (XACT_STATE()) = 1trở nên không cần thiết.


Có một bộ bài viết chi tiết rất tốt về Lỗi và Xử lý giao dịch trong SQL Server của Erland Sommarskog. Trong Phần 2 - Phân loại lỗi , anh ta trình bày một bảng toàn diện, tập hợp tất cả các lớp lỗi và cách chúng được xử lý bởi SQL Server cũng như cách TRY ... CATCHXACT_ABORTthay đổi hành vi.

+-----------------------------+---------------------------++------------------------------+
|                             |     Without TRY-CATCH     ||        With TRY-CATCH        |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
|              SET XACT_ABORT |  OFF  |  ON   | OFF | ON  ||    ON or OFF     | OFF | ON  |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Class Name                  |    Aborts     |   Rolls   ||    Catchable     |   Dooms   |
|                             |               |   Back    ||                  |transaction|
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Fatal errors                |  Connection   |    Yes    ||       No         |    n/a    |
| Batch-aborting              |     Batch     |    Yes    ||       Yes        |    Yes    |
| Batch-only aborting         |     Batch     | No  | Yes ||       Yes        | No  | Yes |
| Statement-terminating       | Stmnt | Batch | No  | Yes ||       Yes        | No  | Yes |
| Terminates nothing at all   |    Nothing    |    No     ||       Yes        | No  | Yes |
| Compilation: syntax errors  |  (Statement)  |    No     ||       Yes        | No  | Yes |
| Compilation: binding errors | Scope | Batch | No  | Yes || Outer scope only | No  | Yes |
| Compilation: optimisation   |     Batch     |    Yes    || Outer scope only |    Yes    |
| Attention signal            |     Batch     | No  | Yes ||       No         |    n/a    |
| Informational/warning msgs  |    Nothing    |    No     ||       No         |    n/a    |
| Uncatchable errors          |    Varying    |  Varying  ||       No         |    n/a    |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+

Cột cuối cùng trong bảng trả lời câu hỏi. Với TRY-CATCHvà với XACT_ABORT ONgiao dịch là cam chịu trong tất cả các trường hợp có thể.

Một lưu ý ngoài phạm vi câu hỏi. Như Erland nói, tính nhất quán này là một trong những lý do để đặt XACT_ABORTthành ON:

Tôi đã đưa ra khuyến nghị rằng các thủ tục được lưu trữ của bạn nên bao gồm lệnh SET XACT_ABORT, NOCOUNT ON. Nếu bạn nhìn vào bảng trên, bạn sẽ thấy rằng XACT_ABORTtrong thực tế, có một mức độ nhất quán cao hơn. Ví dụ, giao dịch luôn luôn phải chịu số phận. Trong phần tiếp theo, tôi sẽ giới thiệu nhiều ví dụ mà tôi đặt XACT_ABORTcho OFF, vì vậy mà bạn có thể nhận được một sự hiểu biết về lý do tại sao bạn nên tránh thiết lập mặc định này.


7

Tôi sẽ tiếp cận điều này khác nhau. XACT_ABORT_ONlà một cái búa tạ, bạn có thể sử dụng một cách tiếp cận tinh tế hơn, xem Xử lý ngoại lệ và giao dịch lồng nhau :

create procedure [usp_my_procedure_name]
as
begin
    set nocount on;
    declare @trancount int;
    set @trancount = @@trancount;
    begin try
        if @trancount = 0
            begin transaction
        else
            save transaction usp_my_procedure_name;

        -- Do the actual work here

lbexit:
        if @trancount = 0   
            commit;
    end try
    begin catch
        declare @error int, @message varchar(4000), @xstate int;
        select @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE();
        if @xstate = -1
            rollback;
        if @xstate = 1 and @trancount = 0
            rollback
        if @xstate = 1 and @trancount > 0
            rollback transaction usp_my_procedure_name;

        raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ;
    end catch   
end
go

Cách tiếp cận này sẽ khôi phục, khi có thể, chỉ công việc được thực hiện bên trong khối TRY và khôi phục trạng thái về trạng thái trước khi vào khối TRY. Bằng cách này, bạn có thể thực hiện xử lý phức tạp, như lặp lại một con trỏ, sẽ không mất tất cả công việc trong trường hợp có lỗi. Hạn chế duy nhất là, bằng cách sử dụng các điểm lưu trữ giao dịch, bạn bị hạn chế sử dụng bất kỳ thứ gì không tương thích với các điểm lưu trữ, như giao dịch phân tán.


Tôi đánh giá cao câu trả lời của bạn, nhưng câu hỏi không thực sự là chúng ta nên đặt XACT_ABORTthành ONhay OFF.
Vladimir Baranov

7

Tóm tắt về TL; DR / Điều hành: Về phần này của Câu hỏi:

Tôi không thấy trong trường hợp nào quyền kiểm soát có thể được chuyển vào bên trong CATCHvới một giao dịch có thể được cam kết khi XACT_ABORTđược đặt thànhON .

Bây giờ tôi đã thực hiện khá nhiều thử nghiệm về điều này và tôi không thể tìm thấy bất kỳ trường hợp nào XACT_STATE()trả về 1bên trong một CATCHkhối khi @@TRANCOUNT > 0 thuộc tính phiên XACT_ABORTON. Và trên thực tế, theo trang MSDN hiện tại cho SET XACT_ABORT :

Khi SET XACT_ABORT BẬT, nếu câu lệnh Transact-SQL xuất hiện lỗi thời gian chạy, toàn bộ giao dịch bị chấm dứt và được khôi phục.

Tuyên bố đó dường như phù hợp với suy đoán của bạn và phát hiện của tôi.

Bài viết về MSDN SET XACT_ABORTcó một ví dụ khi một số câu lệnh bên trong giao dịch thực hiện thành công và một số lỗi khi XACT_ABORTđược đặt thànhOFF

Đúng, nhưng các câu lệnh trong ví dụ đó không nằm trong một TRYkhối. Những tuyên bố tương tự trong một TRYkhối vẫn sẽ ngăn chặn thực hiện đối với bất kỳ báo cáo sau khi một trong đó gây ra lỗi, nhưng giả sử rằng XACT_ABORTOFF, khi kiểm soát được thông qua vào CATCHkhối các giao dịch vẫn còn chất có giá trị ở chỗ tất cả các thay đổi trước đã xảy ra mà không có lỗi và có thể được cam kết, nếu đó là mong muốn, hoặc chúng có thể được khôi phục. Mặt khác, nếu XACT_ABORTONsau đó bất kỳ thay đổi trước sẽ được tự động cuộn lại, và sau đó bạn có sự lựa chọn một trong hai: a) vấn đề mộtROLLBACKmà chủ yếu là chỉ là một sự chấp nhận của tình hình kể từ khi giao dịch đã được cuộn lại trừ đi đặt @@TRANCOUNTđể 0, hoặc b) nhận được một lỗi. Không có nhiều sự lựa chọn, phải không?

Một chi tiết quan trọng có thể có cho câu đố này không rõ ràng trong tài liệu đó SET XACT_ABORTlà thuộc tính phiên này và mã ví dụ đó, đã xuất hiện từ SQL Server 2000 (tài liệu gần như giống hệt nhau giữa các phiên bản), trước TRY...CATCHcấu trúc giới thiệu trong SQL server 2005. nhìn vào tài liệu một lần nữa và nhìn vào ví dụ ( mà không cần sự TRY...CATCH), sử dụng XACT_ABORT ONnguyên nhân một ngay lập tức roll-back của giao dịch: không có trạng thái giao dịch của "uncommittable" (xin lưu ý rằng không có đề cập đến ở tất cả trạng thái Giao dịch "không thể chấp nhận" trong SET XACT_ABORTtài liệu đó ).

Tôi nghĩ thật hợp lý khi kết luận rằng:

  1. việc giới thiệu TRY...CATCHcấu trúc trong SQL Server 2005 đã tạo ra nhu cầu về trạng thái Giao dịch mới (nghĩa là "không thể truy cập") và XACT_STATE()chức năng để có được thông tin đó.
  2. kiểm tra XACT_STATE()trong một CATCHkhối thực sự chỉ có ý nghĩa nếu cả hai điều sau là đúng:
    1. XACT_ABORTOFF(người khác XACT_STATE()nên luôn luôn quay lại -1@@TRANCOUNTsẽ là tất cả những gì bạn cần)
    2. Bạn có logic trong CATCHkhối hoặc ở đâu đó trong chuỗi nếu các cuộc gọi được lồng nhau, điều đó tạo ra sự thay đổi (a COMMIThoặc thậm chí bất kỳ câu lệnh DML, DDL, v.v.) thay vì thực hiện a ROLLBACK. (đây là trường hợp sử dụng rất không điển hình) ** vui lòng xem ghi chú ở phía dưới, trong phần CẬP NHẬT 3, liên quan đến khuyến nghị không chính thức của Microsoft để luôn kiểm tra XACT_STATE()thay vì @@TRANCOUNTvà tại sao thử nghiệm cho thấy lý do của họ không được đưa ra.
  3. Phần lớn việc giới thiệu TRY...CATCHcấu trúc trong SQL Server 2005 đã làm lỗi thuộc tính XACT_ABORT ONphiên vì nó cung cấp mức độ kiểm soát lớn hơn đối với Giao dịch (ít nhất bạn có tùy chọn COMMIT, miễn là XACT_STATE()không trả lại -1).
    Một cách khác để xem xét điều này là, trước SQL Server 2005 , XACT_ABORT ONđã cung cấp một cách dễ dàng và đáng tin cậy để dừng xử lý khi xảy ra lỗi, so với việc kiểm tra @@ERRORsau mỗi câu lệnh.
  4. Mã ví dụ tài liệu cho XACT_STATE()là sai, hoặc tốt nhất là sai lệch, trong đó nó hiển thị kiểm tra XACT_STATE() = 1khi nào XACT_ABORTON.

Phần dài ;-)

Có, mã ví dụ trên MSDN hơi khó hiểu (xem thêm: @@ TRANCOUNT (Phục hồi) so với XACT_STATE ) ;-). Và, tôi cảm thấy nó sai lệch bởi vì nó hiển thị một cái gì đó vô nghĩa (vì lý do mà bạn đang hỏi về: bạn thậm chí có thể có một giao dịch "có thể giao dịch" trong CATCHkhối khi XACT_ABORTON), hoặc thậm chí nếu có thể, nó vẫn tập trung vào một khả năng kỹ thuật mà ít ai muốn hoặc cần, và bỏ qua lý do người ta có thể cần nó hơn.

Nếu có một lỗi đủ nghiêm trọng bên trong khối TRY, điều khiển sẽ chuyển vào CATCH. Vì vậy, nếu tôi ở trong CATCH, tôi biết rằng giao dịch đã có vấn đề và thực sự điều hợp lý duy nhất cần làm trong trường hợp này là đẩy lùi nó, phải không?

Tôi nghĩ sẽ hữu ích nếu chúng tôi đảm bảo rằng chúng tôi ở trên cùng một trang liên quan đến ý nghĩa của các từ và khái niệm nhất định:

  • "Lỗi đủ nghiêm trọng": Chỉ cần rõ ràng, TRY ... CATCH sẽ bẫy hầu hết các lỗi. Danh sách những thứ sẽ không bị bắt được liệt kê trên trang MSDN được liên kết đó, trong phần "Lỗi không bị ảnh hưởng bởi phần Xây dựng CATCH TRY".

  • "nếu tôi ở trong CATCH, tôi biết rằng giao dịch đã có vấn đề" (em phas được thêm vào): Nếu bằng "giao dịch", bạn có nghĩa là đơn vị công việc logic được xác định bởi bạn bằng cách nhóm các báo cáo vào một giao dịch rõ ràng, sau đó rất có thể là có Tôi nghĩ rằng hầu hết mọi người trong DB chúng ta sẽ có xu hướng đồng ý rằng quay ngược lại là "điều hợp lý duy nhất để làm" vì chúng ta có thể có một cái nhìn tương tự về cách thức và lý do tại sao chúng ta sử dụng các giao dịch rõ ràng và hình dung những bước nào sẽ tạo nên một đơn vị nguyên tử của công việc.

    Nhưng, nếu bạn có nghĩa là các đơn vị công việc thực tế đang được nhóm vào giao dịch rõ ràng, thì không, bạn không biết rằng chính giao dịch đã có vấn đề. Bạn chỉ biết rằng một câu lệnh thực thi trong giao dịch được xác định rõ ràng đã gây ra lỗi. Nhưng nó có thể không phải là một tuyên bố DML hoặc DDL. Và ngay cả khi đó là một tuyên bố DML, bản thân Giao dịch vẫn có thể được ủy quyền.

Với hai điểm đã nêu ở trên, có lẽ chúng ta nên rút ra sự khác biệt giữa các giao dịch mà bạn "không thể" cam kết và những điểm mà bạn "không muốn" cam kết.

Khi XACT_STATE()trả về a 1, điều đó có nghĩa là Giao dịch là "có thể thực hiện được", rằng bạn có lựa chọn giữa COMMIThoặc ROLLBACK. Bạn có thể không muốn cam kết điều đó, nhưng nếu vì một số lý do khó thực hiện ngay cả với một ví dụ - vì lý do bạn muốn, ít nhất bạn có thể vì một số phần của Giao dịch đã hoàn thành thành công.

Nhưng khi XACT_STATE()trả về a -1, thì bạn thực sự cần ROLLBACKbởi vì một phần của Giao dịch đã rơi vào trạng thái xấu. Bây giờ, tôi đồng ý rằng nếu kiểm soát đã được chuyển đến khối CATCH, thì nó chỉ đủ để kiểm tra @@TRANCOUNT, bởi vì ngay cả khi bạn có thể thực hiện Giao dịch, tại sao bạn lại muốn?

Nhưng nếu bạn chú ý ở đầu ví dụ, cài đặt XACT_ABORT ONthay đổi mọi thứ một chút. Bạn có thể có một lỗi thông thường, sau khi làm BEGIN TRANmà sẽ vượt qua kiểm soát khối catch khi XACT_ABORTOFFvà XACT_STATE () sẽ trở lại 1. NHƯNG, nếu XACT_ABORT là ON, thì Giao dịch bị "hủy bỏ" (nghĩa là không hợp lệ) cho bất kỳ lỗi 'ol nào và sau đó XACT_STATE()sẽ trả về -1. Trong trường hợp này, có vẻ như vô ích để kiểm tra XACT_STATE()trong CATCHkhối vì nó luôn luôn có vẻ trở lại một -1khi XACT_ABORTON.

Vậy thì XACT_STATE()để làm gì? Một số manh mối là:

  • Trang MSDN cho TRY...CATCH, trong phần "Giao dịch không thể chấp nhận và XACT_STATE", cho biết:

    Một lỗi thông thường kết thúc một giao dịch bên ngoài khối TRY khiến giao dịch đi vào trạng thái không thể sửa chữa khi lỗi xảy ra bên trong khối TRY.

  • Trang MSDN cho SET XACT_ABORT , trong phần "Ghi chú", cho biết:

    Khi SET XACT_ABORT TẮT, trong một số trường hợp, chỉ có câu lệnh Transact-SQL gây ra lỗi được khôi phục và giao dịch tiếp tục xử lý.

    và:

    XACT_ABORT phải được đặt BẬT cho các câu lệnh sửa đổi dữ liệu trong một giao dịch ngầm hoặc rõ ràng đối với hầu hết các nhà cung cấp OLE DB, bao gồm cả SQL Server.

  • Trang MSDN cho BEGIN TRANSACTION , trong phần "Ghi chú", cho biết:

    Giao dịch cục bộ được bắt đầu bởi câu lệnh BEGIN TRANSACTION được chuyển thành giao dịch phân tán nếu các hành động sau được thực hiện trước khi tuyên bố được cam kết hoặc khôi phục:

    • Một câu lệnh INSERT, DELETE hoặc UPDATE tham chiếu bảng từ xa trên máy chủ được liên kết được thực thi. Câu lệnh INSERT, UPDATE hoặc DELETE không thành công nếu nhà cung cấp OLE DB được sử dụng để truy cập máy chủ được liên kết không hỗ trợ giao diện ITransactionJoin.

Việc sử dụng được áp dụng nhiều nhất dường như nằm trong ngữ cảnh của các câu lệnh DML của Máy chủ được liên kết. Và tôi tin rằng tôi đã gặp phải điều này từ nhiều năm trước. Tôi không nhớ tất cả các chi tiết, nhưng nó có liên quan đến máy chủ từ xa không có sẵn và vì một lý do nào đó, lỗi đó không bị bắt trong khối TRY và không bao giờ được gửi đến CATCH và vì vậy nó đã xảy ra một CAM KẾT khi nó không nên có. Tất nhiên, đó có thể là một vấn đề của việc không XACT_ABORTđặt ra ONthay vì không kiểm tra XACT_STATE(), hoặc có thể cả hai. Và tôi nhớ lại việc đọc một cái gì đó nói rằng nếu bạn sử dụng Máy chủ được liên kết và / hoặc Giao dịch phân tán thì bạn cần sử dụng XACT_ABORT ONvà / hoặc XACT_STATE(), nhưng dường như tôi không thể tìm thấy tài liệu đó ngay bây giờ. Nếu tôi tìm thấy nó, tôi sẽ cập nhật điều này với liên kết.

Tuy nhiên, tôi đã thử một vài thứ và không thể tìm thấy một kịch bản có XACT_ABORT ONvà vượt qua sự kiểm soát đối với CATCHkhối có XACT_STATE()báo cáo 1.

Hãy thử các ví dụ sau để thấy ảnh hưởng của XACT_ABORTgiá trị XACT_STATE():

SET XACT_ABORT OFF;

BEGIN TRY
    BEGIN TRAN;

    SELECT 1/0 AS [DivideByZero]; -- error, yo!

    COMMIT TRAN;
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]

    IF (@@TRANCOUNT > 0)
    BEGIN
        ROLLBACK;
    END;
END CATCH;

GO ------------------------------------------------

SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRAN;

    SELECT 1/0 AS [DivideByZero]; -- error, yo!

    COMMIT TRAN;
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]

    IF (@@TRANCOUNT > 0)
    BEGIN
        ROLLBACK;
    END;
END CATCH;

GO ------------------------------------------------

SET XACT_ABORT ON;

BEGIN TRY
    SELECT 1/0 AS [DivideByZero]; -- error, yo!
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]
END CATCH;

CẬP NHẬT

Mặc dù không phải là một phần của Câu hỏi ban đầu, dựa trên những nhận xét về Câu trả lời này:

Tôi đã đọc qua các bài viết của Erland về Xử lý lỗi và giao dịch trong đó anh ta nói rằng đó XACT_ABORTOFFmặc định vì lý do di sản và thông thường chúng ta nên đặt nó thành ON.
...
"... nếu bạn làm theo khuyến nghị và chạy với SET XACT_ABORT ON, giao dịch sẽ luôn bị hủy bỏ."

Trước khi sử dụng XACT_ABORT ONở mọi nơi, tôi sẽ đặt câu hỏi: chính xác những gì đang đạt được ở đây? Tôi không thấy cần phải làm và thường ủng hộ rằng bạn chỉ nên sử dụng nó khi cần thiết. Bạn có muốn ROLLBACKxử lý đủ dễ dàng hay không bằng cách sử dụng mẫu được hiển thị trong câu trả lời của @ Remus hoặc mẫu mà tôi đã sử dụng trong nhiều năm về cơ bản là giống nhau nhưng không có Điểm lưu, như được hiển thị trong câu trả lời này (mà xử lý các cuộc gọi lồng nhau):

Chúng tôi có bắt buộc phải xử lý Giao dịch bằng Mã C # cũng như trong thủ tục được lưu trữ không


CẬP NHẬT 2

Tôi đã thử nghiệm thêm một chút, lần này bằng cách tạo một Ứng dụng .NET Console nhỏ, tạo Giao dịch trong lớp ứng dụng, trước khi thực hiện bất kỳ SqlCommandđối tượng nào (tức là thông qua using (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...), cũng như sử dụng lỗi hủy bỏ hàng loạt thay vì chỉ một câu lệnh -borting lỗi, và thấy rằng:

  1. Giao dịch "không phổ biến" là một giao dịch đã được khôi phục, phần lớn đã được khôi phục (các thay đổi đã được hoàn tác), nhưng @@TRANCOUNTvẫn còn> 0.
  2. Khi bạn có một Giao dịch "không có ích", bạn không thể phát hành COMMITvì nó sẽ tạo ra và lỗi nói rằng Giao dịch là "không thể chấp nhận được". Bạn cũng không thể bỏ qua nó / không làm gì vì một lỗi sẽ được tạo khi lô kết thúc nói rằng lô đã hoàn thành với một giao dịch kéo dài, không thể chấp nhận được và nó sẽ được khôi phục (vì vậy, ừm, nếu nó sẽ tự động quay lại, Tại sao phải ném lỗi?). Vì vậy, bạn phải đưa ra một rõ ràng ROLLBACK, có thể không phải trong CATCHkhối ngay lập tức , nhưng trước khi lô kết thúc.
  3. Trong một TRY...CATCHcấu trúc, khi XACT_ABORTOFF, lỗi đó sẽ chấm dứt giao dịch tự động có họ xảy ra bên ngoài của một TRYkhối, chẳng hạn như các lỗi hàng loạt hủy, sẽ lùi lại công việc nhưng không chấm dứt Tranasction, để lại nó như là "uncommitable". Phát hành a ROLLBACKlà một hình thức cần thiết hơn để đóng Giao dịch, nhưng công việc đã được khôi phục.
  4. Khi XACT_ABORTON, hầu hết các lỗi làm hàng loạt hủy, và do đó cư xử như mô tả trong điểm đạn trực tiếp trên (# 3).
  5. XACT_STATE(), ít nhất là trong một CATCHkhối, sẽ hiển thị -1lỗi hủy bỏ hàng loạt nếu có Giao dịch hoạt động tại thời điểm xảy ra lỗi.
  6. XACT_STATE()đôi khi trả lại 1ngay cả khi không có Giao dịch hoạt động. Nếu @@SPID(trong số những người khác) có trong SELECTdanh sách cùng với XACT_STATE(), thì XACT_STATE()sẽ trả về 1 khi không có Giao dịch hoạt động. Hành vi này bắt đầu trong SQL Server 2012 và tồn tại vào năm 2014, nhưng tôi chưa thử nghiệm vào năm 2016.

Với những điểm trên trong tâm trí:

  • Với các điểm số 4 và số 5, vì hầu hết các lỗi (hoặc tất cả?) Sẽ khiến Giao dịch trở nên "không thể thực hiện được", nên việc kiểm tra XACT_STATE()trong CATCHkhối XACT_ABORTlà khi nào ONgiá trị được trả về sẽ luôn luôn là vô nghĩa -1.
  • Kiểm tra XACT_STATE()trong CATCHkhối khi XACT_ABORTOFFý nghĩa hơn bởi vì giá trị trả về ít nhất sẽ có một số biến thể vì nó sẽ trả về 1các lỗi hủy bỏ câu lệnh. Tuy nhiên, nếu bạn viết mã như hầu hết chúng ta, thì sự khác biệt này là vô nghĩa vì ROLLBACKdù sao bạn cũng sẽ gọi đơn giản vì thực tế là đã xảy ra lỗi.
  • Nếu bạn tìm thấy một tình huống mà không trát phát hành một COMMITtrong các CATCHkhối, sau đó kiểm tra giá trị của XACT_STATE(), và hãy chắc chắn SET XACT_ABORT OFF;.
  • XACT_ABORT ONdường như cung cấp ít hoặc không có lợi ích trên các TRY...CATCHcông trình.
  • Tôi không thể tìm thấy kịch bản nào trong đó việc kiểm tra XACT_STATE()cung cấp một lợi ích có ý nghĩa hơn là kiểm tra đơn giản @@TRANCOUNT.
  • Tôi cũng có thể tìm thấy không có kịch bản mà XACT_STATE()lợi nhuận 1trong một CATCHkhối khi XACT_ABORTON. Tôi nghĩ đó là một lỗi tài liệu.
  • Có, bạn có thể quay lại Giao dịch mà bạn không bắt đầu rõ ràng. Và trong bối cảnh sử dụng XACT_ABORT ON, đó là một điểm cần thiết vì một lỗi xảy ra trong một TRYkhối sẽ tự động khôi phục các thay đổi.
  • Cấu TRY...CATCHtrúc có lợi ích XACT_ABORT ONtrong việc không tự động hủy toàn bộ Giao dịch và do đó cho phép Giao dịch (miễn là XACT_STATE()trả lại 1) được cam kết (ngay cả khi đây là trường hợp cạnh).

Ví dụ XACT_STATE()về -1khi trở về XACT_ABORTOFF:

SET XACT_ABORT OFF;

BEGIN TRY
    BEGIN TRAN;

    SELECT CONVERT(INT, 'g') AS [ConversionError];

    COMMIT TRAN;
END TRY
BEGIN CATCH
    DECLARE @State INT;
    SET @State = XACT_STATE();
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            @State AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage];

    IF (@@TRANCOUNT > 0)
    BEGIN
        SELECT 'Rollin back...' AS [Transaction];
        ROLLBACK;
    END;
END CATCH;

CẬP NHẬT 3

Liên quan đến mục số 6 trong phần CẬP NHẬT 2 (nghĩa là giá trị không chính xác có thể được trả về XACT_STATE()khi không có Giao dịch hoạt động):

  • Hành vi kỳ quặc / sai lầm bắt đầu trong SQL Server 2012 (cho đến nay đã được thử nghiệm so với 2012 SP2 và 2014 SP1)
  • Trong các phiên bản SQL Server 2005, 2008 và 2008 R2, XACT_STATE()đã không báo cáo các giá trị dự kiến ​​khi được sử dụng trong Triggers hoặc INSERT...EXECkịch bản: xact_state () không thể được sử dụng một cách đáng tin cậy để xác định liệu giao dịch có bị hủy hay không . Tuy nhiên, trong những phiên bản 3 (tôi chỉ thử nghiệm trên 2008 R2), XACT_STATE()không không sai báo cáo 1khi sử dụng trong một SELECTvới @@SPID.
  • Có một lỗi Connect được đệ trình chống lại hành vi được đề cập ở đây nhưng được đóng lại là "Theo thiết kế": XACT_STATE () có thể trả về trạng thái giao dịch không chính xác trong SQL 2012 . Tuy nhiên, thử nghiệm đã được thực hiện khi chọn từ DMV và kết luận rằng làm như vậy tự nhiên sẽ có một giao dịch được tạo ra hệ thống, ít nhất là đối với một số DMV. Nó cũng được nêu trong phản hồi cuối cùng của MS rằng:

    Lưu ý rằng một câu lệnh IF và cũng là CHỌN không có TỪ, không bắt đầu giao dịch.
    ví dụ: chạy CHỌN XACT_STATE () nếu bạn không có giao dịch hiện tại trước đó sẽ trả về 0.

    Những tuyên bố không chính xác được đưa ra ví dụ sau:

    SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @@SPID AS [SPID];
    GO
    DECLARE @SPID INT;
    SET @SPID = @@SPID;
    SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @SPID AS [SPID];
    GO
  • Do đó, lỗi Connect mới:
    XACT_STATE () trả về 1 khi được sử dụng trong SELECT với một số biến hệ thống nhưng không có mệnh đề TỪ

XIN LƯU Ý rằng trong "XACT_STATE () có thể trả về trạng thái giao dịch không chính xác trong SQL 2012" Kết nối mục được liên kết trực tiếp ở trên, Microsoft (tốt, đại diện của) tuyên bố:

@@ trancount trả về số lượng câu lệnh BEGIN TRAN. Do đó, nó không phải là một chỉ số đáng tin cậy về việc có một giao dịch hoạt động hay không. XACT_STATE () cũng trả về 1 nếu có giao dịch tự động hoạt động và do đó là một chỉ số đáng tin cậy hơn về việc có giao dịch đang hoạt động hay không.

Tuy nhiên, tôi không thể tìm thấy lý do để không tin tưởng @@TRANCOUNT. Thử nghiệm sau đây cho thấy @@TRANCOUNTthực sự trở lại 1trong giao dịch tự động cam kết:

--- begin setup
GO
CREATE PROCEDURE #TransactionInfo AS
SET NOCOUNT ON;
SELECT @@TRANCOUNT AS [TranCount],
       XACT_STATE() AS [XactState];
GO
--- end setup

DECLARE @Test TABLE (TranCount INT, XactState INT);

SELECT * FROM @Test; -- no rows

EXEC #TransactionInfo; -- 0 for both fields

INSERT INTO @Test (TranCount, XactState)
    EXEC #TransactionInfo;

SELECT * FROM @Test; -- 1 row; 1 for both fields

Tôi cũng đã thử nghiệm trên một bảng thực với Trình kích hoạt và @@TRANCOUNTtrong Trình kích hoạt đã báo cáo chính xác 1mặc dù không có Giao dịch rõ ràng nào được bắt đầu.


4

Lập trình phòng thủ đòi hỏi bạn phải viết mã xử lý càng nhiều trạng thái đã biết càng tốt, do đó giảm khả năng xảy ra lỗi.

Kiểm tra XACT_STATE () để xác định xem có thể thực hiện khôi phục lại hay không chỉ đơn giản là một cách thực hành tốt. Cố gắng một cách mù quáng có nghĩa là bạn có thể vô tình gây ra lỗi bên trong TRY ... CATCH.

Một cách mà một rollback có thể thất bại trong TRY ... CATCH sẽ là nếu bạn không bắt đầu một giao dịch rõ ràng. Sao chép và dán các khối mã có thể dễ dàng gây ra điều này.


Cảm ơn bạn đã trả lời. Tôi chỉ không thể nghĩ ra một trường hợp khi đơn giản ROLLBACKsẽ không hoạt động bên trong CATCHvà bạn đã đưa ra một ví dụ tốt. Tôi đoán, nó cũng có thể nhanh chóng trở nên lộn xộn nếu các giao dịch lồng nhau và các thủ tục lưu trữ lồng nhau với chính TRY ... CATCH ... ROLLBACKchúng được tham gia.
Vladimir Baranov

Tuy nhiên, tôi sẽ đánh giá cao nó, nếu bạn có thể mở rộng câu trả lời của mình về phần thứ hai - IF (XACT_STATE()) = 1 COMMIT TRANSACTION; Làm thế nào chúng ta có thể kết thúc bên trong CATCHkhối với giao dịch có thể ủy thác? Tôi sẽ không dám để một số rác (có thể) từ bên trong CATCH. Lý do của tôi là: nếu chúng ta ở bên trong CATCHmột cái gì đó đã sai, tôi không thể tin vào trạng thái của cơ sở dữ liệu, vì vậy tôi tốt hơn ROLLBACKbất cứ điều gì chúng ta có.
Vladimir Baranov
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.