Trong Sql Server, có cách nào để kiểm tra xem một nhóm hàng đã chọn có bị khóa hay không?


21

Chúng tôi đang cố gắng cập nhật / xóa một số lượng lớn các bản ghi trong bảng hàng tỷ tỷ. Vì đây là một bảng phổ biến, có rất nhiều hoạt động trong các phần khác nhau của bảng này. Bất kỳ hoạt động cập nhật / xóa lớn nào đang bị chặn trong thời gian dài (vì nó đang chờ để có khóa trên tất cả các hàng hoặc khóa trang hoặc khóa bảng) dẫn đến hết thời gian hoặc mất nhiều ngày để hoàn thành nhiệm vụ.

Vì vậy, chúng tôi đang thay đổi cách tiếp cận để xóa hàng loạt hàng nhỏ tại một thời điểm. Nhưng chúng tôi muốn kiểm tra xem hàng đã chọn (giả sử 100 hoặc 1000 hoặc 2000 hàng) hiện có bị khóa bởi một quy trình khác hay không.

  • Nếu không, sau đó tiến hành xóa / cập nhật.
  • Nếu chúng bị khóa, sau đó chuyển sang nhóm hồ sơ tiếp theo.
  • Cuối cùng, hãy quay lại sự khởi đầu và cố gắng cập nhật / xóa những cái còn sót lại.

Đây có phải là có thể làm được?

Cảm ơn, ToC


2
Bạn đã xem READPAST như một phần của câu lệnh xóa hoặc NOWAIT (để thất bại cả nhóm) chưa? Một trong số này có thể làm việc cho bạn. msdn.microsoft.com/en-us/l Library / ms187373.aspx
Sean nói Xóa Sara Chipps

@SeanGallardy Tôi chưa xem xét ý tưởng đó, nhưng bây giờ tôi sẽ làm. Nhưng có cách nào dễ dàng hơn để kiểm tra xem một hàng cụ thể có bị khóa hay không? Cảm ơn.
ToC

3
Bạn cũng có thể xem xét LOCK_TIMEOUT ( msdn.microsoft.com/en-us/l Library / ms189470.aspx ). Ví dụ, đây là cách mà sp_whoisactive của Adam Machanic đảm bảo rằng quy trình không chờ quá lâu nếu nó bị chặn khi cố gắng thu thập kế hoạch thực hiện. Bạn có thể đặt thời gian chờ ngắn hoặc thậm chí sử dụng giá trị 0 ("0 có nghĩa là không chờ đợi và trả lại tin nhắn ngay khi gặp khóa.") Bạn có thể kết hợp điều này với TRY / CATCH để bắt lỗi 1222 ( "Đã hết thời gian yêu cầu khóa") và chuyển sang đợt tiếp theo.
Geoff Patterson

@gpatterson Cách tiếp cận thú vị. Tôi cũng sẽ thử điều này.
ToC

2
Để trả lời, không có cách nào dễ dàng hơn để xem các hàng có bị khóa hay không trừ khi có một cái gì đó được thực hiện cụ thể trong ứng dụng. Về cơ bản, trước tiên bạn có thể thực hiện lựa chọn với HOLDLOCK và XLOCK với bộ lock_timeout (đó là những gì NOWAIT trong nhận xét ban đầu của tôi là về, đặt thời gian chờ là 0). Nếu bạn không nhận được nó, thì bạn biết cái gì đó đã bị khóa. Không có gì dễ dàng có sẵn để nói "Hàng X trong Bảng Y có sử dụng Index Z bị khóa bởi thứ gì đó không". Chúng ta có thể xem bảng có khóa hay bất kỳ trang / hàng / khóa / vv nào có khóa không, nhưng việc dịch nó thành các hàng cụ thể trong truy vấn sẽ không dễ dàng.
Sean nói Hủy bỏ Sara Chipps

Câu trả lời:


10

Nếu tôi hiểu chính xác yêu cầu, mục tiêu là xóa các lô hàng, đồng thời, các hoạt động DML đang diễn ra trên các hàng trong suốt bảng. Mục tiêu là xóa một lô; tuy nhiên, nếu bất kỳ hàng bên dưới nào trong phạm vi được xác định bởi lô đã nói bị khóa, thì chúng ta phải bỏ qua lô đó và chuyển sang lô tiếp theo. Sau đó chúng tôi phải quay lại bất kỳ lô nào chưa bị xóa trước đó và thử lại logic xóa ban đầu của chúng tôi. Chúng ta phải lặp lại chu trình này cho đến khi tất cả các lô hàng yêu cầu bị xóa.

Như đã đề cập, thật hợp lý khi sử dụng gợi ý READPAST và mức cô lập READ CAMITTED (mặc định), để bỏ qua các phạm vi có thể chứa các hàng bị chặn. Tôi sẽ tiến thêm một bước nữa và khuyên bạn nên sử dụng mức cô lập SERIALIZABLE và xóa các thao tác xóa.

SQL Server sử dụng các khóa Phạm vi khóa để bảo vệ một loạt các hàng được bao gồm trong bộ bản ghi đang được đọc bởi câu lệnh Transact-SQL trong khi sử dụng mức cô lập giao dịch tuần tự hóa ... tìm thêm tại đây: https://technet.microsoft.com /en-US/l Library / ms191272 (v = Vista.105) .aspx

Với các thao tác xóa nibling, mục tiêu của chúng tôi là cô lập một phạm vi các hàng và đảm bảo rằng sẽ không có thay đổi nào xảy ra với các hàng đó trong khi chúng tôi đang xóa chúng, nghĩa là chúng tôi không muốn đọc hoặc chèn ảo. Mức cô lập tuần tự hóa có nghĩa là để giải quyết vấn đề này.

Trước khi tôi trình bày giải pháp của mình, tôi muốn nói thêm rằng tôi không khuyên bạn nên chuyển mức cô lập mặc định của cơ sở dữ liệu sang SERIALIZABLE cũng như tôi khuyên bạn nên giải pháp của mình là tốt nhất. Tôi chỉ muốn trình bày nó và xem chúng ta có thể đi đâu từ đây.

Một vài lưu ý giữ nhà:

  1. Phiên bản SQL Server mà tôi đang sử dụng là Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
  2. Cơ sở dữ liệu thử nghiệm của tôi đang sử dụng mô hình phục hồi FULL

Để bắt đầu thử nghiệm, tôi sẽ thiết lập cơ sở dữ liệu thử nghiệm, bảng mẫu và tôi sẽ lấp đầy bảng với 2.000.000 hàng.


USE [master];
GO

SET NOCOUNT ON;

IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
    ALTER DATABASE [test] SET SINGLE_USER
        WITH ROLLBACK IMMEDIATE;
    DROP DATABASE [test];
END
GO

-- Create the test database
CREATE DATABASE [test];
GO

-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;

-- Create a FULL database backup
-- in order to ensure we are in fact using 
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO

USE [test];
GO

-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
    DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
      c1 BIGINT IDENTITY (1,1) NOT NULL
    , c2 INT NOT NULL
) ON [PRIMARY];
GO

-- Insert 2,000,000 rows 
INSERT INTO dbo.tbl
    SELECT TOP 2000
        number
    FROM
        master..spt_values
    ORDER BY 
        number
GO 1000

Tại thời điểm này, chúng ta sẽ cần một hoặc nhiều chỉ mục mà theo đó các cơ chế khóa của mức cô lập SERIALIZABLE có thể hoạt động.


-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
    ON dbo.tbl (c1);
GO

-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2 
    ON dbo.tbl (c2);
GO

Bây giờ, chúng ta hãy kiểm tra xem 2.000.000 hàng của chúng tôi đã được tạo chưa


SELECT
    COUNT(*)
FROM
    tbl;

nhập mô tả hình ảnh ở đây

Vì vậy, chúng tôi có cơ sở dữ liệu, bảng, chỉ mục và hàng của chúng tôi. Vì vậy, chúng ta hãy thiết lập thử nghiệm để xóa các xóa. Đầu tiên, chúng ta phải quyết định cách tốt nhất để tạo ra một cơ chế xóa nibble điển hình.


DECLARE
      @BatchSize        INT    = 100
    , @LowestValue      BIGINT = 20000
    , @HighestValue     BIGINT = 20010
    , @DeletedRowsCount BIGINT = 0
    , @RowCount         BIGINT = 1;

SET NOCOUNT ON;
GO

WHILE  @DeletedRowsCount <  ( @HighestValue - @LowestValue ) 
BEGIN

    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    BEGIN TRANSACTION

        DELETE 
        FROM
            dbo.tbl 
        WHERE
            c1 IN ( 
                    SELECT TOP (@BatchSize)
                        c1
                    FROM
                        dbo.tbl 
                    WHERE 
                        c1 BETWEEN @LowestValue AND @HighestValue
                    ORDER BY 
                        c1
                  );

        SET @RowCount = ROWCOUNT_BIG();

    COMMIT TRANSACTION;

    SET @DeletedRowsCount += @RowCount;
    WAITFOR DELAY '000:00:00.025';
    CHECKPOINT;

END;

Như bạn có thể thấy, tôi đã đặt giao dịch rõ ràng bên trong vòng lặp while. Nếu bạn muốn hạn chế các bản ghi nhật ký, thì hãy đặt nó bên ngoài vòng lặp. Hơn nữa, vì chúng tôi đang ở trong mô hình phục hồi ĐẦY ĐỦ, bạn có thể muốn tạo bản sao lưu nhật ký giao dịch thường xuyên hơn trong khi chạy các hoạt động xóa nibble của bạn, để đảm bảo rằng nhật ký giao dịch của bạn có thể bị ngăn chặn phát triển quá mức.

Vì vậy, tôi có một vài mục tiêu với thiết lập này. Đầu tiên, tôi muốn khóa phạm vi khóa của tôi; Vì vậy, tôi cố gắng giữ các lô càng nhỏ càng tốt. Tôi cũng không muốn tác động tiêu cực đến sự tương tranh trên bảng "khổng lồ" của mình; Vì vậy, tôi muốn lấy ổ khóa của mình và để chúng nhanh nhất có thể. Vì vậy, tôi khuyên bạn nên làm cho kích thước lô của bạn nhỏ.

Bây giờ, tôi muốn cung cấp một ví dụ rất ngắn về thói quen xóa này trong hành động. Chúng tôi phải mở một cửa sổ mới trong SSMS và xóa một hàng khỏi bảng của chúng tôi. Tôi sẽ thực hiện việc này trong một giao dịch ngầm bằng cách sử dụng mức cô lập READ CAMITTED mặc định.


DELETE FROM
    dbo.tbl
WHERE
    c1 = 20005;

Hàng này đã thực sự bị xóa?


SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20010;

Vâng, nó đã bị xóa.

Bằng chứng về hàng đã xóa

Bây giờ, để xem các khóa của chúng tôi, chúng ta hãy mở một cửa sổ mới trong SSMS và thêm một hoặc hai đoạn mã. Tôi đang sử dụng sp_whoisactive của Adam Mechanic, có thể tìm thấy ở đây: sp_whoisactive


SELECT
    DB_NAME(resource_database_id) AS DatabaseName
  , resource_type
  , request_mode
FROM
    sys.dm_tran_locks
WHERE
    DB_NAME(resource_database_id) = 'test'
    AND resource_type = 'KEY'
ORDER BY
    request_mode;

-- Our insert
sp_lock 55;

-- Our deletions
sp_lock 52;

-- Our active sessions
sp_whoisactive;

Bây giờ, chúng tôi đã sẵn sàng để bắt đầu. Trong cửa sổ SSMS mới, chúng ta hãy bắt đầu một giao dịch rõ ràng sẽ cố gắng chèn lại một hàng mà chúng ta đã xóa. Đồng thời, chúng tôi sẽ thực hiện thao tác xóa nhanh.

Mã chèn:


BEGIN TRANSACTION

    SET IDENTITY_INSERT dbo.tbl ON;

    INSERT  INTO dbo.tbl
            ( c1 , c2 )
    VALUES
            ( 20005 , 1 );

    SET IDENTITY_INSERT dbo.tbl OFF;

--COMMIT TRANSACTION;

Hãy để chúng tôi khởi động cả hai hoạt động bắt đầu với chèn và tiếp theo là xóa. Chúng ta có thể thấy các khóa phạm vi khóa và khóa độc quyền.

Khóa phạm vi và khóa điện tử

Việc chèn tạo ra các khóa này:

Chèn khóa

Xóa / chọn nibble đang giữ các khóa này:

nhập mô tả hình ảnh ở đây

Chèn của chúng tôi đang chặn xóa của chúng tôi như mong đợi:

Chèn khối Xóa

Bây giờ, hãy để chúng tôi cam kết giao dịch chèn và xem những gì đang xảy ra.

Cam kết xóa

Và như mong đợi, tất cả các giao dịch hoàn tất. Bây giờ, chúng ta phải kiểm tra xem liệu phần chèn có phải là một bóng ma hay không hay liệu thao tác xóa cũng xóa nó đi.


SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20015;

Trong thực tế, chèn đã bị xóa; vì vậy, không cho phép chèn ảo.

Không có Phantom Chèn

Vì vậy, để kết luận, tôi nghĩ rằng mục đích thực sự của bài tập này không phải là cố gắng theo dõi từng hàng, trang hoặc khóa cấp bảng và cố gắng xác định xem một phần tử của lô có bị khóa hay không và do đó sẽ yêu cầu thao tác xóa của chúng tôi chờ đợi. Đó có thể là ý định của người hỏi; tuy nhiên, nhiệm vụ đó là bất thường và về cơ bản là không thực tế nếu không muốn nói là không thể. Mục tiêu thực sự là đảm bảo rằng không có hiện tượng không mong muốn nào phát sinh khi chúng tôi đã tách phạm vi của lô của chúng tôi bằng các khóa của chính chúng tôi và sau đó trước khi xóa lô. Mức cô lập SERIALIZABLE đạt được mục tiêu này. Điều quan trọng là giữ cho số lượng nhỏ của bạn nhỏ, nhật ký giao dịch của bạn được kiểm soát và loại bỏ các hiện tượng không mong muốn.

Nếu bạn muốn tốc độ, thì đừng xây dựng các bảng sâu khổng lồ không thể phân vùng và do đó không thể sử dụng chuyển đổi phân vùng để có kết quả nhanh nhất. Chìa khóa của tốc độ là phân vùng và song song; chìa khóa của sự đau khổ là nhấm nháp và khóa trực tiếp.

Xin vui lòng cho tôi biết những gì bạn nghĩ.

Tôi đã tạo thêm một số ví dụ về mức độ cô lập SERIALIZABLE trong hành động. Họ nên có sẵn tại các liên kết dưới đây.

Xóa hoạt động

Chèn hoạt động

Hoạt động bình đẳng - Khóa phạm vi khóa trên các giá trị quan trọng tiếp theo

Hoạt động bình đẳng - Lấy dữ liệu đơn lẻ

Hoạt động bình đẳng - Lấy dữ liệu đơn lẻ

Hoạt động bất bình đẳng - Khóa phạm vi khóa trên phạm vi và các giá trị khóa tiếp theo


9

Vì vậy, chúng tôi đang thay đổi cách tiếp cận để xóa hàng loạt hàng nhỏ tại một thời điểm.

Đây là một ý tưởng thực sự tốt để xóa trong các lô hoặc khối cẩn thận nhỏ . Tôi sẽ thêm một phần nhỏ waitfor delay '00:00:05'và tùy thuộc vào mô hình khôi phục của cơ sở dữ liệu - nếu FULL, sau đó thực hiện log backupSIMPLEsau đó thực hiện manual CHECKPOINTđể tránh làm đầy nhật ký giao dịch - giữa các đợt.

Nhưng chúng tôi muốn kiểm tra xem hàng đã chọn (giả sử 100 hoặc 1000 hoặc 2000 hàng) hiện có bị khóa bởi một quy trình khác hay không.

Những gì bạn đang nói không hoàn toàn có thể nằm ngoài khả năng (hãy ghi nhớ 3 điểm đạn của bạn). Nếu đề xuất trên - small batches + waitfor delaykhông hoạt động (miễn là bạn thực hiện kiểm tra đúng cách), thì bạn có thể sử dụng query HINT.

Không sử dụng NOLOCK- xem kb / 308886 , Các vấn đề về tính nhất quán của máy chủ SQL của Itzik Ben-Gan , Đưa NOLOCK ở mọi nơi - Tác giả Aaron BertrandSQL Server NOLOCK Hint và các ý tưởng kém khác .

READPASTgợi ý sẽ giúp trong kịch bản của bạn. Ý chính của READPASTgợi ý là - nếu có khóa cấp hàng thì máy chủ SQL sẽ không đọc nó.

Chỉ định rằng Công cụ cơ sở dữ liệu không đọc các hàng bị khóa bởi các giao dịch khác. Khi READPASTđược chỉ định, khóa cấp hàng được bỏ qua. Đó là, Cơ sở dữ liệu bỏ qua các hàng thay vì chặn giao dịch hiện tại cho đến khi các khóa được phát hành.

Trong quá trình thử nghiệm giới hạn của mình, tôi thấy thông lượng thực sự tốt khi sử dụng DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK)và đặt mức cô lập phiên truy vấn thành READ COMMITTEDsử dụng SET TRANSACTION ISOLATION LEVEL READ COMMITTEDmức cô lập mặc định.


2

Tóm tắt các cách tiếp cận khác ban đầu được đưa ra trong các ý kiến cho câu hỏi.


  1. Sử dụng NOWAITnếu hành vi mong muốn là làm hỏng toàn bộ khối ngay khi gặp phải khóa không tương thích.

    Từ NOWAITtài liệu :

    Hướng dẫn Công cụ cơ sở dữ liệu trả về một thông báo ngay khi gặp khóa trên bàn. NOWAITtương đương với việc chỉ định SET LOCK_TIMEOUT 0cho một bảng cụ thể. Các NOWAITgợi ý không hoạt động khi các TABLOCKgợi ý cũng được bao gồm. Để chấm dứt truy vấn mà không phải chờ đợi khi sử dụng TABLOCKgợi ý, SETLOCK_TIMEOUT 0;thay vào đó hãy đặt trước truy vấn .

  2. Sử dụng SET LOCK_TIMEOUTđể đạt được kết quả tương tự, nhưng với thời gian chờ cấu hình:

    Từ SET LOCK_TIMEOUTtài liệu

    Chỉ định số mili giây mà câu lệnh chờ khóa được phát hành.

    Khi chờ khóa vượt quá giá trị thời gian chờ, lỗi sẽ được trả về. Giá trị 0 có nghĩa là không chờ đợi gì cả và trả lại tin nhắn ngay khi gặp khóa.


0

Giả sử chúng ta có 2 truy vấn tương tự:

kết nối / phiên 1: sẽ khóa hàng = 777

SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777

kết nối / phiên 2: sẽ bỏ qua hàng bị khóa = 777

SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777

HOẶC kết nối / phiên 2: sẽ ném ngoại lệ

DECLARE @id integer;
SELECT @id = id FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777;
IF @id is NULL
  THROW 51000, 'Hi, a record is locked or does not exist.', 1;

-1

Hãy thử lọc một cái gì đó như thế này - nó có thể trở nên phức tạp nếu bạn muốn thực sự, thực sự cụ thể. Tìm trong BOL để biết mô tả về sys.dm_tran_locks

SELECT 
tl.request_session_id,
tl.resource_type,
tl.resource_associated_entity_id,
db_name(tl.resource_database_id) 'Database',
CASE 
    WHEN tl.resource_type = 'object' THEN object_name(tl.resource_associated_entity_id, tl.resource_database_id)
    ELSE NULL
END 'LockedObject',
tl.resource_database_id,
tl.resource_description,
tl.request_mode,
tl.request_type,
tl.request_status FROM [sys].[dm_tran_locks] tl WHERE resource_database_id <> 2order by tl.request_session_id

chỉ tò mò - tại sao các downvote?
rottengeek

-11

Bạn có thể sử dụng NoLOCK trong khi bạn đang xóa và nếu các hàng bị khóa, chúng sẽ không bị xóa. Nó không lý tưởng nhưng có thể làm các mẹo cho bạn.

DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True

7
Nếu tôi cố gắng đó trên máy tính địa phương của tôi tôi nhận được Msg 1065, Level 15, State 1, Line 15 The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements., phản đối từ năm 2005
Tom V - Đội Monica
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.