Cách khôi phục khi 3 thủ tục được lưu trữ được bắt đầu từ một thủ tục được lưu trữ


23

Tôi có một thủ tục được lưu trữ chỉ thực hiện 3 thủ tục được lưu trữ bên trong chúng. Tôi chỉ sử dụng 1 tham số để lưu trữ nếu SP chính thành công.

Nếu quy trình được lưu trữ đầu tiên hoạt động tốt trong quy trình được lưu trữ chính, nhưng quy trình được lưu trữ thứ 2 không thành công, thì nó sẽ tự động khôi phục tất cả các SP trong SP chính hay tôi phải thực hiện một số lệnh?

Đây là thủ tục của tôi:

CREATE PROCEDURE [dbo].[spSavesomename] 
    -- Add the parameters for the stored procedure here

    @successful bit = null output
AS
BEGIN
begin transaction createSavebillinginvoice
    begin Try
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

   BEGIN 

   EXEC [dbo].[spNewBilling1]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling2]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling3]

   END 

   set @successful  = 1

   end Try

    begin Catch
        rollback transaction createSavesomename
        insert into dbo.tblErrorMessage(spName, errorMessage, systemDate) 
             values ('spSavesomename', ERROR_MESSAGE(), getdate())

        return
    end Catch
commit transaction createSavesomename
return
END

GO

Nếu spNewBilling3ném lỗi, nhưng bạn không muốn quay lại spNewBilling2hoặc spNewBilling1sau đó chỉ cần xóa [begin|rollback|commit] transaction createSavebillinginvoicekhỏi spSavesomename.
Mike

Câu trả lời:


56

Chỉ đưa ra mã được hiển thị trong câu hỏi và giả sử rằng không ai trong số ba procs có bất kỳ xử lý giao dịch rõ ràng nào, thì có, một lỗi trong bất kỳ ba procs nào sẽ bị bắt và ROLLBACKtrong CATCHkhối sẽ quay trở lại tất cả của công việc.

NHƯNG đây là một số điều cần lưu ý về các giao dịch (ít nhất là trong SQL Server):

  • Chỉ có một giao dịch thực sự (giao dịch đầu tiên), bất kể bạn gọi bao nhiêu lầnBEGIN TRAN

    • Bạn có thể đặt tên cho một giao dịch (như bạn đã làm ở đây) và tên sẽ xuất hiện trong các bản ghi, nhưng đặt tên chỉ có ý nghĩa đối với bên ngoài hầu hết các giao dịch đầu tiên / (vì một lần nữa, một trong những đầu tiên là các giao dịch).
    • Mỗi lần bạn gọi BEGIN TRAN, dù được đặt tên hay không, bộ đếm giao dịch được tăng thêm 1.
    • Bạn có thể thấy cấp độ hiện tại bằng cách làm SELECT @@TRANCOUNT;
    • Bất kỳ COMMITlệnh nào @@TRANCOUNTđược ban hành khi ở mức 2 trở lên không làm gì hơn là giảm, mỗi lần một lệnh, bộ đếm giao dịch.
    • Không có gì là bao giờ cam kết cho đến khi một COMMITđược ban hành khi @@TRANCOUNTđang ở1
    • Chỉ trong trường hợp thông tin trên không cho biết rõ ràng: bất kể cấp độ giao dịch, không có lồng nhau thực sự của giao dịch.
  • Lưu điểm cho phép tạo ra một tập hợp con của công việc trong các giao dịch có thể được hoàn tác.

    • Điểm lưu được tạo / đánh dấu thông qua SAVE TRAN {save_point_name}lệnh
    • Lưu điểm đánh dấu sự khởi đầu của tập hợp con công việc có thể được hoàn tác mà không cần khôi phục toàn bộ giao dịch.
    • Tên điểm lưu không cần phải là duy nhất, nhưng sử dụng cùng một tên nhiều lần vẫn tạo ra các điểm lưu riêng biệt.
    • Lưu điểm có thể được lồng nhau.
    • Lưu điểm không thể được cam kết.
    • Lưu điểm có thể được hoàn tác thông qua ROLLBACK {save_point_name}. (thêm về điều này dưới đây)
    • Quay ngược lại một điểm lưu sẽ hoàn tác bất kỳ công việc nào đã xảy ra sau lệnh gọi gần đây nhấtSAVE TRAN {save_point_name} , bao gồm mọi điểm lưu được tạo sau khi điểm được quay lại được tạo ra (do đó "lồng").
    • Quay trở lại một điểm lưu không ảnh hưởng đến số lượng / cấp độ giao dịch
    • Bất kỳ công việc nào được thực hiện trước khi ban đầu SAVE TRANkhông thể được hoàn tác ngoại trừ bằng cách phát hành ROLLBACKtoàn bộ giao dịch.
    • Nói rõ hơn: phát hành COMMITthời điểm @@TRANCOUNTở mức 2 trở lên, không ảnh hưởng đến điểm lưu (vì một lần nữa, mức giao dịch trên 1 không tồn tại bên ngoài quầy đó).
  • Bạn không thể cam kết các giao dịch được đặt tên cụ thể. "Tên" giao dịch, nếu được cung cấp cùng với COMMIT, bị bỏ qua và chỉ tồn tại để dễ đọc.

  • Một ROLLBACKphát hành không có tên sẽ luôn phục hồi TẤT CẢ các giao dịch.

  • Một tên được ROLLBACKban hành phải tương ứng với một trong hai:

    • Giao dịch đầu tiên, giả sử nó được đặt tên:
      Giả sử không có tên nào SAVE TRANđược gọi với cùng tên giao dịch, điều này sẽ phục hồi TẤT CẢ các giao dịch.
    • "Điểm lưu" (được mô tả ở trên):
      Hành vi này sẽ "hoàn tác" tất cả thay đổi được thực hiện kể từ lần gần đây nhất SAVE TRAN {save_point_name} được gọi.
    • Nếu giao dịch đầu tiên là a) được đặt tên và b) đã có SAVE TRANcác lệnh được ban hành với tên của nó, thì mỗi ROLLBACK của tên giao dịch đó sẽ hoàn tác từng điểm lưu cho đến khi không còn lại tên đó. Sau đó, ROLLBACK được cấp tên đó sẽ phục hồi TẤT CẢ các giao dịch.
    • Ví dụ: giả sử các lệnh sau đã được chạy theo thứ tự hiển thị:

      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

      Bây giờ, nếu bạn phát hành (mỗi trường hợp sau đây độc lập với nhau):

      • ROLLBACK TRAN Bmột lần: Nó sẽ hoàn tác "Truy vấn DML 4". @@TRANCOUNTvẫn là 2.
      • ROLLBACK TRAN Bhai lần: Nó sẽ hoàn tác "Truy vấn DML 4" và sau đó lỗi vì không có điểm lưu tương ứng cho "B". @@TRANCOUNTvẫn là 2.
      • ROLLBACK TRAN Amột lần: Nó sẽ hoàn tác "Truy vấn DML 4" và "Truy vấn DML 3". @@TRANCOUNTvẫn là 2.
      • ROLLBACK TRAN Ahai lần: Nó sẽ hoàn tác "Truy vấn DML 4", "Truy vấn DML 3" và "Truy vấn DML 2". @@TRANCOUNTvẫn là 2.
      • ROLLBACK TRAN Aba lần: Nó sẽ hoàn tác "Truy vấn DML 4", "Truy vấn DML 3" và "Truy vấn DML 2". Sau đó, nó sẽ phục hồi toàn bộ giao dịch (tất cả những gì còn lại là "Truy vấn DML 1"). @@TRANCOUNTbây giờ là 0
      • COMMITmột lần: @@TRANCOUNTđi xuống 1.
      • COMMITmột lần và sau đó ROLLBACK TRAN Bmột lần: @@TRANCOUNTđi xuống 1. Sau đó, nó sẽ hoàn tác "Truy vấn DML 4" (chứng minh rằng CAM KẾT không làm gì cả). @@TRANCOUNTvẫn là 1.
  • Tên giao dịch và lưu tên điểm:

    • có thể có tối đa 32 ký tự
    • được coi là có Collation nhị phân (không phân biệt chữ hoa chữ thường như tài liệu hiện tại), bất kể Collation ở cấp độ Instance hay Database.
    • Để biết chi tiết, vui lòng xem phần Tên giao dịch của bài đăng sau: What in a Name?: Inside the Wacky World of T-SQL Định danh
  • Một thủ tục được lưu trữ, bản thân nó không phải là một giao dịch ngầm. Mỗi truy vấn nếu không có giao dịch rõ ràng đã được bắt đầu, là một giao dịch ngầm. Đây là lý do tại sao các giao dịch rõ ràng xung quanh các truy vấn đơn lẻ là không cần thiết trừ khi có thể có một lý do lập trình để thực hiện ROLLBACK, nếu không, bất kỳ lỗi nào trong truy vấn là tự động quay lại truy vấn đó.

  • Khi gọi một thủ tục được lưu trữ, nó phải thoát với giá trị @@TRANCOUNTgiống như khi nó được gọi. Có nghĩa là, bạn không thể:

    • Bắt đầu một BEGIN TRANtrong Proc mà không cam kết, hy vọng sẽ cam kết trong quá trình gọi / cha mẹ.
    • Bạn không thể phát hành ROLLBACKnếu một giao dịch rõ ràng đã được bắt đầu trước khi Proc được gọi vì nó sẽ trở về @@TRANCOUNT0.

    Nếu bạn thoát một thủ tục được lưu trữ với số lượng giao dịch cao hơn hoặc thấp hơn so với khi nó bắt đầu, bạn sẽ gặp một lỗi tương tự như:

    Msg 266, Cấp 16, Trạng thái 2, Quy trình YourProcName, Dòng 0
    Số lượng giao dịch sau khi EXECUTE chỉ ra số lượng không khớp của các câu lệnh BEGIN và CAM KẾT. Số trước = X, số hiện tại = Y.

  • Các biến bảng, giống như các biến thông thường, không bị ràng buộc bởi các giao dịch.


Về việc xử lý giao dịch trong các procs có thể được gọi độc lập (và do đó cần xử lý giao dịch) hoặc gọi từ các procs khác (do đó không cần xử lý giao dịch): điều này có thể được thực hiện theo một vài cách khác nhau.

Cách mà tôi đã xử lý nó trong nhiều năm nay dường như hoạt động tốt là chỉ BEGIN/ COMMIT/ ROLLBACKở lớp ngoài cùng. Các cuộc gọi phụ chỉ cần bỏ qua các lệnh giao dịch. Tôi đã phác thảo bên dưới những gì tôi đưa vào mỗi Proc (tốt, mỗi cái cần xử lý giao dịch).

  • Ở đầu mỗi Proc, DECLARE @InNestedTransaction BIT;
  • Thay vì đơn giản BEGIN TRAN, hãy làm:

    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;
  • Thay vì đơn giản COMMIT, hãy làm:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       COMMIT;
    END;
  • Thay vì đơn giản ROLLBACK, hãy làm:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       ROLLBACK;
    END;

Phương pháp này sẽ hoạt động như nhau bất kể giao dịch được bắt đầu trong SQL Server hay nếu nó được bắt đầu ở lớp ứng dụng.

Đối với mẫu đầy đủ của Xử lý giao dịch này trong TRY...CATCHcấu trúc, vui lòng xem câu trả lời của tôi cho câu hỏi DBA.SE sau: Chúng tôi có bắt buộc phải xử lý Giao dịch trong Mã C # cũng như trong quy trình được lưu trữ không .


Đi ra ngoài "những điều cơ bản", có một số sắc thái giao dịch bổ sung cần lưu ý:

  • Theo mặc định, hầu hết thời gian, Giao dịch không được tự động khôi phục / hủy khi xảy ra lỗi. Đây thường không phải là vấn đề miễn là bạn có cách xử lý lỗi thích hợp và ROLLBACKtự gọi cho mình. Tuy nhiên, đôi khi mọi thứ trở nên phức tạp, chẳng hạn như trong trường hợp xảy ra lỗi hủy bỏ hàng loạt hoặc khi sử dụng OPENQUERY(hoặc Máy chủ được liên kết nói chung) và xảy ra lỗi trên hệ thống từ xa. Mặc dù hầu hết các lỗi có thể bị mắc bẫy bằng cách sử dụng TRY...CATCH, có hai lỗi không thể bị mắc kẹt theo cách đó (mặc dù không thể nhớ lỗi nào tại thời điểm này - nghiên cứu). Trong những trường hợp này, bạn phải sử dụng SET XACT_ABORT ONđể khôi phục đúng Giao dịch.

    SET XACT_ABORT ON khiến SQL Server lập tức quay lại bất kỳ Giao dịch nào (nếu một giao dịch đang hoạt động) hủy bỏ lô nếu lỗi xảy ra. Cài đặt này tồn tại trước SQL Server 2005, đã giới thiệu TRY...CATCHcấu trúc. Đối với hầu hết các phần, TRY...CATCHxử lý hầu hết các tình huống và do đó chủ yếu là lỗi thời XACT_ABORT ON. Tuy nhiên, khi sử dụng OPENQUERY(và có thể là một kịch bản khác mà tôi không thể nhớ vào lúc này), thì bạn vẫn sẽ cần sử dụng SET XACT_ABORT ON;.

  • Bên trong một Kích hoạt, XACT_ABORTđược đặt ngầm định ON. Điều này gây ra bất kỳ lỗi nào trong Trình kích hoạt để hủy toàn bộ câu lệnh DML đã kích hoạt Trình kích hoạt.

  • Bạn phải luôn có cách xử lý lỗi thích hợp, đặc biệt là khi sử dụng Giao dịch. Cấu TRY...CATCHtrúc, được giới thiệu trong SQL Server 2005, cung cấp một phương thức xử lý gần như tất cả các tình huống, một cải tiến đáng hoan nghênh so với thử nghiệm @@ERRORsau mỗi câu lệnh, điều này không giúp ích nhiều cho các lỗi hủy bỏ hàng loạt.

    TRY...CATCHgiới thiệu một "trạng thái" mới, tuy nhiên. Khi không sử dụng TRY...CATCHcấu trúc, nếu bạn có Giao dịch đang hoạt động và xảy ra lỗi, thì có một số đường dẫn có thể được thực hiện:

    • XACT_ABORT OFFvà lỗi hủy bỏ câu lệnh: Giao dịch vẫn hoạt động và quá trình xử lý tiếp tục với câu lệnh tiếp theo , nếu có.
    • XACT_ABORT OFFvà hầu hết các lỗi hủy bỏ hàng loạt: Giao dịch vẫn hoạt động và quá trình xử lý tiếp tục với đợt tiếp theo , nếu có.
    • XACT_ABORT OFFvà một số lỗi hủy bỏ hàng loạt nhất định: Giao dịch được khôi phục và xử lý tiếp tục với đợt tiếp theo , nếu có.
    • XACT_ABORT ONbất kỳ lỗi nào : Giao dịch được khôi phục và xử lý tiếp tục với đợt tiếp theo , nếu có.


    TUY NHIÊN, khi sử dụng TRY...CATCH, các lỗi hủy bỏ hàng loạt không hủy bỏ lô mà thay vào đó chuyển điều khiển sang CATCHkhối. Khi XACT_ABORTOFF, các giao dịch vẫn sẽ là hoạt động đại đa số thời điểm đó, và bạn sẽ cần phải COMMIT, hoặc có khả năng nhất, ROLLBACK. Nhưng khi gặp phải một số lỗi hàng loạt hủy (chẳng hạn như với OPENQUERY), hoặc khi XACT_ABORTON, các giao dịch sẽ được trong một trạng thái mới, "uncommitable". Ở trạng thái này, bạn không thể COMMIT, bạn cũng không thể thực hiện bất kỳ hoạt động DML nào. Tất cả những gì bạn có thể làm là ROLLBACKSELECTtuyên bố. Tuy nhiên, trong trạng thái "không thể khắc phục" này, Giao dịch đã được khôi phục khi xảy ra lỗi và việc ban hành ROLLBACKchỉ là một hình thức, nhưng phải thực hiện.

    Một hàm, XACT_STATE , có thể được sử dụng để xác định xem Giao dịch có hoạt động, không có tác dụng hay không tồn tại. Nên kiểm tra (ít nhất là một số, ít nhất) để kiểm tra chức năng này trong CATCHkhối để xác định xem kết quả có -1(nghĩa là không có ích) thay vì kiểm tra nếu @@TRANCOUNT > 0. Nhưng với XACT_ABORT ON, đó phải là trạng thái duy nhất có thể tồn tại, vì vậy có vẻ như việc kiểm tra @@TRANCOUNT > 0XACT_STATE() <> 0tương đương. Mặt khác, khi XACT_ABORTOFFvà có một giao dịch tích cực, sau đó nó có thể có tình trạng một trong hai 1hoặc -1trong CATCHkhối, cho phép khả năng phát hành COMMITthay vì ROLLBACK(mặc dù, tôi không thể nghĩ ra một trường hợp khi một người nào đó muốnCOMMITnếu Giao dịch được cam kết). Có thể tìm thấy thêm thông tin và nghiên cứu về việc sử dụng XACT_STATE()trong một CATCHkhối với XACT_ABORT ONcâu trả lời của tôi cho câu hỏi DBA.SE sau: Trong trường hợp nào giao dịch có thể được thực hiện từ bên trong khối CATCH khi XACT_ABORT được đặt thành BẬT? . Xin lưu ý rằng có một lỗi nhỏ XACT_STATE()khiến nó trả về sai 1trong một số trường hợp nhất định: XACT_STATE () trả về 1 khi được sử dụng trong CHỌN với một số biến hệ thống nhưng không có mệnh đề TỪ


Ghi chú về mã gốc:

  • Bạn có thể xóa tên được đặt cho giao dịch vì nó không giúp được gì.
  • Bạn không cần BEGINENDxung quanh mỗi EXECcuộc gọi

2
Đó là một câu trả lời thực sự tốt, tốt.
McNets

1
Wow, đó là một câu trả lời toàn diện! Cảm ơn bạn! BTW dos trang sau đây giải quyết các lỗi mà bạn ám chỉ không bị mắc kẹt bởi Thử ... Bắt? (Dưới tiêu đề "Lỗi Không bị ảnh hưởng bởi một TRY ... CATCH Thi công"? Technet.microsoft.com/en-us/library/ms175976(v=sql.110).aspx
jrdevdba

1
@jrdevdba Cảm ơn :-). Và chào mừng bạn. Về các lỗi không bị mắc kẹt, tôi khá có nghĩa là hai: Compile errors, such as syntax errors, that prevent a batch from runningErrors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.. Nhưng chúng không xảy ra rất thường xuyên và khi bạn gặp tình huống như vậy, hãy sửa nó (nếu đó là một lỗi trong mã) hoặc đặt nó trong một quy trình phụ ( EXEChoặc sp_executesql) để TRY...CATCHcó thể bẫy nó.
Solomon Rutzky

2

Có, nếu do bất kỳ mã khôi phục lỗi nào trong câu lệnh bắt thủ tục được lưu trữ chính của bạn sẽ thực thi, nó sẽ phục hồi tất cả các hoạt động được thực hiện bởi bất kỳ câu lệnh trực tiếp nào hoặc thông qua bất kỳ thủ tục lưu trữ lồng nhau nào của bạn trong đó.

Ngay cả khi bạn chưa áp dụng bất kỳ giao dịch rõ ràng nào trong các thủ tục được lưu trữ lồng nhau của mình, các quy trình được lưu trữ này sẽ sử dụng giao dịch ngầm và sẽ cam kết hoàn thành NHƯNG bạn đã cam kết thông qua giao dịch rõ ràng hoặc ẩn trong các thủ tục lưu trữ được lưu trữ lồng nhau. rollback tất cả các hành động của các thủ tục lưu trữ lồng nhau này nếu thủ tục lưu trữ chính bị lỗi và giao dịch được hỗ trợ.

Mỗi khi giao dịch được cam kết hoặc được khôi phục dựa trên hành động được thực hiện vào cuối giao dịch ngoài cùng. Nếu giao dịch bên ngoài được cam kết, các giao dịch lồng bên trong cũng được cam kết. Nếu giao dịch bên ngoài được khôi phục, thì tất cả các giao dịch bên trong cũng được khôi phục, bất kể các giao dịch bên trong có được cam kết riêng lẻ hay không.

Để tham khảo http://technet.microsoft.com/en-us/l Library / ms189336 (v = sql.105) .aspx

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.