Làm cách nào để hạn chế một thủ tục lưu trữ SQL được chạy bởi một người tại một thời điểm?


12

Tôi có một thủ tục được lưu trữ về cơ bản chọn các giá trị từ một bảng và chèn chúng vào một bảng khác, một kiểu lưu trữ. Tôi muốn tránh nhiều người làm điều đó cùng một lúc.

Trong khi thủ tục này đang chạy, tôi không muốn bất kỳ ai khác có thể bắt đầu nó, tuy nhiên tôi không muốn tuần tự hóa, người khác chạy thủ tục sau khi tôi hoàn thành nó.

Những gì tôi muốn là cho người khác cố gắng khởi động nó để nhận được một lỗi, trong khi tôi đang chạy thủ tục.

Tôi đã thử sử dụng sp_getapplock, tuy nhiên tôi không thể quản lý để ngăn chặn hoàn toàn người đó chạy quy trình.

Tôi cũng đã thử tìm thủ tục với sys.dm_exec numquests và chặn thủ tục, trong khi điều này không hoạt động, tôi nghĩ nó không tối ưu vì trên một số máy chủ tôi không có quyền chạy sys.dm_exec_sql giác (sql_handle).

Cách tốt nhất để tôi làm điều này là gì?


3
Bạn có thể lùi lại một bước và cung cấp thêm một số thông tin về quy trình đang làm gì không và tại sao bạn muốn tránh nhiều người chạy nó cùng một lúc? Có thể có một kỹ thuật mã hóa loại bỏ yêu cầu này hoặc một số loại hàng đợi bạn có thể thực hiện để xử lý mọi việc.
AMtwo

Câu trả lời:


15

Để thêm vào câu trả lời của @ Tibor-Karaszi, đặt thời gian chờ khóa không thực sự gây ra lỗi (Tôi đã gửi PR cho các tài liệu). sp_getapplock chỉ trả về -1, vì vậy bạn phải kiểm tra giá trị trả về. Vì vậy, như thế này:

create or alter procedure there_can_be_only_one 
as
begin
begin transaction

  declare @rv int
  exec @rv = sp_getapplock 'only_one','exclusive','Transaction',0
  if @rv < 0
   begin
      throw 50001, 'There is already an instance of this procedure running.', 10
   end

  --do stuff
  waitfor delay '00:00:20'


commit transaction
end

8

Sử dụng sp_getapplock vào đầu Proc và đặt thời gian chờ khóa thành giá trị rất thấp. Bằng cách này bạn sẽ gặp lỗi khi bạn bị chặn.


7

Một tùy chọn khác là xây dựng một bảng để kiểm soát truy cập vào thủ tục. ví dụ dưới đây cho thấy một bảng có thể cũng như một quy trình có thể sử dụng nó.

CREATE TABLE dbo.ProcedureLock
    (
    ProcedureLockID INT NOT NULL IDENTITY(1,1)
    , ProcedureName SYSNAME NOT NULL
    , IsLocked BIT NOT NULL CONSTRAINT DF_ProcedureLock_IsLocked DEFAULT (0)
    , UserSPID INT NULL
    , DateLockTaken DATETIME2(7) NULL
    , DateLockExpires DATETIME2(7) NULL
    , CONSTRAINT PK_ProcedureLock PRIMARY KEY CLUSTERED (ProcedureLockID)
    )

CREATE UNIQUE NONCLUSTERED INDEX IDXUQ_ProcedureLock_ProcedureName
    ON dbo.ProcedureLock (ProcedureName)

INSERT INTO dbo.ProcedureLock
    (ProcedureName, IsLocked)
VALUES ('dbo.DoSomeWork', 0)

GO

CREATE PROCEDURE dbo.DoSomeWork
AS
BEGIN

    /** Take Lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 1
        , UserSPID = @@SPID
        , DateLockTaken = SYSDATETIME()
        , DateLockExpires = DATEADD(MINUTE, 10, SYSDATETIME())
    WHERE ProcedureName = 'dbo.DoSomeWork'
        AND (IsLocked = 0
            OR (IsLocked = 1 AND DateLockExpires < SYSDATETIME())
            )

    IF COALESCE(@@ROWCOUNT, 0) = 0
    BEGIN
        ;THROW 50000, 'This procedure can only be run one at a time, please wait', 1;
    END

    /** DO WHATEVER NEEDS TO BE DONE */

    /** Release the lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 0
        , UserSPID = NULL
        , DateLockTaken = NULL
        , DateLockExpires = NULL
    WHERE ProcedureName = 'dbo.DoSomeWork'

END

1
Điều này rất giống với (hoặc có lẽ giống như) những gì tôi nghĩ ngay sau khi đọc câu hỏi, nhưng tôi có một vấn đề với ý tưởng đó là tôi không hoàn toàn chắc chắn làm thế nào để giải quyết và không thể thấy nó được giải quyết trong câu trả lời của bạn hoặc. Mối quan tâm của tôi là, điều gì sẽ xảy ra nếu phần nào đó xảy ra trong phần "làm bất cứ điều gì cần phải làm"? Làm thế nào bạn sẽ thiết lập lại IsLockedtrạng thái về 0 trong trường hợp đó? Tôi cũng tò mò về việc bạn sử dụng COALESCEở đây. Có @@ROWCOUNTthể null sau khi tuyên bố như thế UPDATEnào? Cuối cùng, chỉ là một cú đánh nhỏ, tại sao lại đặt dấu chấm phẩy trước THROWcâu lệnh trong trường hợp cụ thể đó?
Andriy M

Hết hạn khóa là một cách xử lý đó. Nó sẽ cần được đặt thành một khung thời gian hợp lý, tôi đã đặt nó thành 10 phút trong ví dụ của mình. Bạn có thể gói gọn logic công việc của mình trong khối thử / bắt và mở khóa khi bắt nếu bạn muốn. Tôi sử dụng COALESCE theo thói quen, nhưng không @@ ROWCOUNT không thể là NULL. dấu chấm phẩy hàng đầu đến từ việc làm việc với các dự án cơ sở dữ liệu của Visual Studio, nó phàn nàn nếu nó không ở đó. không có hại gì cả
Jonathan Fite

-1

Tôi nghĩ rằng bạn đang cố gắng giải quyết vấn đề một cách không chính xác. Những gì bạn muốn là bảo vệ tối đa tính nhất quán của cơ sở dữ liệu. Nếu hai người chạy một thủ tục được lưu trữ cùng một lúc, tính nhất quán của cơ sở dữ liệu có thể bị vi phạm.

Để bảo vệ chống lại các loại mâu thuẫn cơ sở dữ liệu khác nhau, tiêu chuẩn SQL có bốn mức cách ly giao dịch:

  • ĐỌC KHÔNG HOÀN THÀNH khi các giao dịch về cơ bản mất giá trị, các giao dịch khác nhìn thấy dữ liệu bẩn. Đừng dùng cái này!
  • ĐỌC CAM KẾT nơi các giao dịch chỉ nhìn thấy dữ liệu đã cam kết, nhưng có thể có sự không nhất quán khi hai giao dịch có thể bước qua các ngón chân của nhau
  • ĐỌC LẠI ĐỌC nơi mà một loại không nhất quán, đọc không lặp lại, được giải quyết
  • SERIALIZABLE đảm bảo rằng tồn tại một số thứ tự ảo trong đó thực hiện các giao dịch sẽ dẫn đến kết quả mà việc thực hiện của chúng dẫn đến

Tuy nhiên, tiêu chuẩn SQL có cách tiếp cận dựa trên khóa cho các sự không nhất quán của cơ sở dữ liệu này và vì lý do hiệu năng, nhiều cơ sở dữ liệu sử dụng cách tiếp cận dựa trên ảnh chụp nhanh, về cơ bản có các mức này:

  • ĐỌC CAM KẾT giống như trong khóa cơ sở dữ liệu
  • SNAPSHOT ISOLATION trong đó cơ sở dữ liệu nhìn thấy ảnh chụp nhanh của tất cả dữ liệu và nếu nó cố cập nhật một hàng đã được cập nhật bởi một số giao dịch khác, nó sẽ bị hủy, nhưng có một số dị thường nổi tiếng có thể xảy ra
  • SERIALIZABLE giống như trong việc khóa cơ sở dữ liệu dựa trên cơ sở dữ liệu, nhưng lần này được thực hiện theo một cách khác, không phải bằng cách khóa mà bằng cách đảm bảo không có vi phạm tuần tự hóa và nếu phát hiện vi phạm đó, hãy hủy giao dịch

Việc hủy giao dịch trong các cơ sở dữ liệu dựa trên cách ly ảnh chụp này nghe có vẻ đáng lo ngại, nhưng sau đó, mọi cơ sở dữ liệu sẽ hủy giao dịch do bế tắc, do đó, bất kỳ ứng dụng hợp lý nào cũng cần có thể thử lại giao dịch.

Những gì bạn muốn là mức cô lập SERIALIZABLE : nó đảm bảo rằng nếu các giao dịch được thực hiện độc lập hết lần này đến lần khác trong trạng thái tốt, thì bất kỳ việc thực hiện song song nào của giao dịch cũng dẫn đến trạng thái tốt. May mắn thay, Michael Cahill đã có trong luận án tiến sĩ của mình để tìm ra mức độ cô lập SERIALIZABLE có thể được hỗ trợ bởi các cơ sở dữ liệu bị cô lập nhanh chóng với ít nỗ lực.

Nếu sử dụng mức cô lập SERIALIZABLE trong cơ sở dữ liệu bị cô lập nhanh, nếu hai người cố gắng chạy quy trình được lưu trữ đồng thời và họ sẽ giẫm lên các ngón chân của nhau, một trong các giao dịch sẽ bị hủy.

Bây giờ, SQL Server có thực sự hỗ trợ mức cô lập SERIALIZABLE (thay vì giả mạo cách ly ảnh chụp nhanh phía sau từ khóa SERIALIZABLE ) không? Thành thật mà nói, tôi không biết: cơ sở dữ liệu duy nhất tôi biết hỗ trợ nó là PostgreQuery.

Mặc dù tôi không thể đưa ra lời khuyên cụ thể cho SQL Server, tuy nhiên tôi vẫn đăng câu trả lời này, vì người dùng PostgreQuery và người dùng cơ sở dữ liệu khác có thể xem xét chuyển sang PostgreQuery có thể hưởng lợi từ câu trả lời của tôi. Ngoài ra, người dùng cơ sở dữ liệu không phải là PostgreQuery không thể chuyển sang PostgreQuery có thể gây áp lực cho nhà cung cấp cơ sở dữ liệu yêu thích của họ để cung cấp mức cách ly SERIALIZABLE chính hãng .


Tôi coi đó là downvote có nghĩa là ai đó đã điều tra xem SQL Server có mức cô lập SERIALIZABLE hay không và phát hiện ra rằng nó không.
juhist

-2

Tôi nhận ra vấn đề 'thực tế' có thể phức tạp hơn.

Trong trường hợp không phải là: nếu bạn thực hiện lưu trữ với trình kích hoạt chèn và / hoặc cập nhật, bạn có thể tránh được sự cố bạn đang cố gắng giải quyết.

Hy vọng rằng sẽ giúp,
-Chris C.


1
Bạn có ý nghĩa gì bởi "ngay lập tức"? Ngay sau đó là gì? Sau khi chèn? Vì vậy, ngay sau khi một hàng mới đến, nó ngay lập tức được gửi đến lưu trữ? Hay bạn có nghĩa là sau khi cập nhật? Vì vậy, bất kỳ thay đổi dữ liệu kích hoạt lưu trữ? Có lẽ bạn nên cụ thể hơn về kịch bản mà bạn có trong đầu gợi ý điều này.
Andriy M

Việc lưu trữ có thể quá tốn kém và / hoặc quá hiếm khi đáng để thực hiện trên mỗi lần chèn, đặc biệt nếu bảng nguồn thường xuyên được chèn và / hoặc an toàn giao dịch giữa nó và kho lưu trữ sẽ yêu cầu khóa đắt tiền.
gạch dưới

@underscore_d Có, nó có thể quá tốn kém hoặc không phải lúc nào cũng được yêu cầu. Đây là lý do tại sao tôi bắt đầu câu trả lời của mình với tuyên bố rằng the 'real' problem may be more complex. Trong trường hợp không phải vậy, kích hoạt là một giải pháp tốt. Thêm vào đó, có lẽ sẽ dễ dàng hơn để kiểm tra và bảo trì vì đây là một tính năng của cơ sở dữ liệu thay vì một giải pháp tùy chỉnh.
J. Chris Compton

@AndriyM Tôi đã xóa từ này ngay lập tức, thay thế bằng một tham chiếu để chèn / cập nhật kích hoạt. Xin lỗi vì sự nhầm lẫn.
J. Chris Compton

1
Tôi đã đọc lại câu hỏi và tôi nghĩ rằng tôi có thể thấy nguồn gốc của sự nhầm lẫn của mình. Những gì bạn đang đề xuất ở đây gần giống với kiểm toán hơn là lưu trữ. Theo tôi hiểu, lưu trữ dữ liệu ngụ ý di chuyển dữ liệu (ví dụ từ bảng này sang bảng khác). Tuy nhiên, mặc dù OP đã tóm tắt chức năng của quy trình của họ là "một kiểu lưu trữ", họ không bao giờ nói rằng dữ liệu sẽ bị xóa khỏi nguồn, chỉ có điều nó sẽ được chọn từ đó và chèn vào mục tiêu. Vì vậy, tôi đoán bạn cho rằng OP cần sao chép , thay vì di chuyển , dữ liệu của họ, trong trường hợp sử dụng kích hoạt có thể có ý nghĩa.
Andriy M
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.