Di Sql Server, apakah ada cara untuk memeriksa apakah sekelompok baris yang dipilih terkunci atau tidak?

21

Kami berusaha memperbarui / menghapus sejumlah besar catatan dalam tabel baris multi-miliar. Karena ini adalah tabel yang populer, ada banyak aktivitas di berbagai bagian tabel ini. Setiap aktivitas pembaruan / penghapusan besar sedang diblokir untuk periode waktu yang lama (karena menunggu untuk mendapatkan kunci pada semua baris atau kunci halaman atau kunci tabel) yang mengakibatkan timeout atau mengambil beberapa hari untuk menyelesaikan tugas.

Jadi, kami mengubah pendekatan untuk menghapus sejumlah kecil baris sekaligus. Tetapi kami ingin memeriksa apakah baris yang dipilih (katakanlah 100 atau 1000 atau 2000) saat ini dikunci oleh proses yang berbeda atau tidak.

  • Jika tidak, maka lanjutkan dengan menghapus / memperbarui.
  • Jika dikunci, maka pindah ke grup catatan berikutnya.
  • Pada akhirnya, kembali ke awal dan berusaha memperbarui / menghapus yang ditinggalkan.

Apakah ini bisa dilakukan?

Terima kasih, ToC

ToC
sumber
2
Sudahkah Anda melihat READPAST sebagai bagian dari pernyataan hapus atau NOWAIT (untuk gagal seluruh grup)? Salah satunya dapat bekerja untuk Anda. msdn.microsoft.com/en-us/library/ms187373.aspx
Sean mengatakan Hapus Sara Chipps
@SeanGallardy Saya belum mempertimbangkan ide itu, tapi sekarang saya akan mempertimbangkannya. Tetapi apakah ada cara yang lebih mudah untuk memeriksa apakah baris tertentu terkunci atau tidak? Terima kasih.
ToC
3
Anda mungkin juga melihat ke LOCK_TIMEOUT ( msdn.microsoft.com/en-us/library/ms189470.aspx ). Sebagai contoh, ini adalah cara sp_whoisactive Adam Machanic memastikan bahwa prosedur tidak menunggu terlalu lama jika diblokir ketika mencoba mengumpulkan rencana eksekusi. Anda dapat mengatur batas waktu singkat atau bahkan menggunakan nilai 0 ("0 berarti tidak menunggu sama sekali dan mengembalikan pesan segera setelah kunci ditemukan.") Anda dapat menggabungkan ini dengan TRY / CATCH untuk menangkap kesalahan 1222 ( "Kunci batas waktu permintaan habis terlampaui") dan lanjutkan ke batch berikutnya.
Geoff Patterson
@ gpatterson Pendekatan yang menarik. Saya akan coba ini juga.
ToC
2
Untuk menjawab, tidak, tidak ada cara yang lebih mudah untuk melihat apakah baris dikunci kecuali ada sesuatu yang dilakukan secara khusus dalam aplikasi. Pada dasarnya Anda pertama-tama dapat melakukan seleksi dengan HOLDLOCK dan XLOCK dengan set lock_timeout (yang adalah tentang NOWAIT dalam komentar asli saya, mengatur batas waktu ke 0). Jika Anda tidak mendapatkannya, maka Anda tahu ada sesuatu yang terkunci. Tidak ada yang mudah untuk mengatakan "Apakah baris X di Tabel Y menggunakan Indeks Z dikunci oleh sesuatu". Kita dapat melihat apakah tabel memiliki kunci atau halaman / baris / kunci / dll memiliki kunci, tetapi menerjemahkannya ke baris tertentu dalam kueri tidak akan mudah.
Sean bilang Hapus Sara Chipps

Jawaban:

10

Jika saya memahami permintaan dengan benar, tujuannya adalah untuk menghapus kumpulan baris, sementara pada saat yang sama, operasi DML terjadi pada baris di seluruh tabel. Tujuannya adalah untuk menghapus kumpulan; namun, jika ada baris mendasar yang terkandung dalam kisaran yang ditentukan oleh kumpulan tersebut dikunci, maka kita harus melewati kumpulan itu dan pindah ke kumpulan berikutnya. Kami kemudian harus kembali ke kumpulan mana pun yang sebelumnya tidak dihapus dan coba lagi logika hapus asli kami. Kita harus mengulangi siklus ini sampai semua kumpulan baris yang diperlukan dihapus.

Seperti yang telah disebutkan, masuk akal untuk menggunakan petunjuk READPAST dan tingkat isolasi BACA (default), untuk melewati rentang masa lalu yang mungkin berisi baris yang diblokir. Saya akan melangkah lebih jauh dan merekomendasikan menggunakan tingkat isolasi SERIALIZABLE dan menggigit menghapus.

SQL Server menggunakan kunci Rentang Kunci untuk melindungi serangkaian baris yang secara implisit termasuk dalam kumpulan catatan yang sedang dibaca oleh pernyataan Transact-SQL saat menggunakan tingkat isolasi transaksi yang dapat disesuaikan ... temukan lebih lanjut di sini: https://technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx

Dengan menggigit menghapus, tujuan kami adalah untuk mengisolasi berbagai baris dan memastikan bahwa tidak ada perubahan yang terjadi pada baris tersebut saat kami menghapusnya, artinya, kami tidak ingin pembacaan atau penyisipan phantom. Tingkat isolasi serializable dimaksudkan untuk menyelesaikan masalah ini.

Sebelum saya mendemonstrasikan solusi saya, saya ingin menambahkan bahwa saya juga tidak merekomendasikan untuk beralih tingkat isolasi standar database Anda ke SERIALIZABLE juga saya merekomendasikan solusi saya adalah yang terbaik. Saya hanya ingin menyampaikannya dan melihat ke mana kita bisa pergi dari sini.

Beberapa catatan perawatan rumah:

  1. Versi SQL Server yang saya gunakan adalah Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
  2. Basis data pengujian saya menggunakan model pemulihan LENGKAP

Untuk memulai percobaan saya, saya akan membuat basis data pengujian, tabel sampel, dan saya akan mengisi tabel dengan 2.000.000 baris.


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

Pada titik ini, kita akan memerlukan satu atau lebih indeks di mana mekanisme penguncian tingkat isolasi SERIALIZABLE dapat bertindak.


-- 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

Sekarang, mari kita periksa untuk melihat apakah 2.000.000 baris kita telah dibuat


SELECT
    COUNT(*)
FROM
    tbl;

masukkan deskripsi gambar di sini

Jadi, kami memiliki basis data, tabel, indeks, dan baris kami. Jadi, mari kita siapkan percobaan untuk menghapus gigitan. Pertama, kita harus memutuskan cara terbaik untuk membuat mekanisme penghapusan yang biasa-biasa saja.


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;

Seperti yang Anda lihat, saya menempatkan transaksi eksplisit di dalam loop sementara. Jika Anda ingin membatasi flush log, silakan letakkan di luar loop. Selain itu, karena kita berada dalam model pemulihan LENGKAP, Anda mungkin ingin membuat cadangan log transaksi lebih sering saat menjalankan operasi penghapusan gigitan Anda, untuk memastikan bahwa log transaksi Anda dapat dicegah agar tidak bertambah besar.

Jadi, saya punya beberapa tujuan dengan pengaturan ini. Pertama, saya ingin kunci rentang kunci saya; jadi, saya mencoba untuk menjaga batch sekecil mungkin. Saya juga tidak ingin berdampak negatif pada konkurensi di atas meja "raksasa" saya; jadi, saya ingin mengambil kunci saya dan meninggalkannya secepat mungkin. Jadi, saya sarankan Anda membuat ukuran batch Anda kecil.

Sekarang, saya ingin memberikan contoh singkat tentang tindakan penghapusan rutin ini. Kami harus membuka jendela baru dalam SSMS dan menghapus satu baris dari tabel kami. Saya akan melakukan ini dalam transaksi implisit menggunakan tingkat isolasi BACA KOMITMEN standar.


DELETE FROM
    dbo.tbl
WHERE
    c1 = 20005;

Apakah baris ini benar-benar dihapus?


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

Ya, sudah dihapus.

Bukti Baris Dihapus

Sekarang, untuk melihat kunci kami, mari kita buka jendela baru dalam SSMS dan tambahkan satu atau dua potongan kode. Saya menggunakan sp_whoisactive dari Adam Mechanic, yang dapat ditemukan di sini: 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;

Sekarang, kita siap untuk memulai. Di jendela SSMS baru, mari kita mulai transaksi eksplisit yang akan mencoba memasukkan kembali satu baris yang kita hapus. Pada saat yang sama, kami akan mematikan operasi penghapusan gigitan kami.

Kode yang dimasukkan:


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;

Mari kita mulai kedua operasi yang dimulai dengan sisipan dan diikuti oleh penghapusan kita. Kita dapat melihat kunci rentang kunci dan kunci eksklusif.

Rentang dan Kunci eXclusive

Sisipan menghasilkan kunci-kunci ini:

Masukkan Kunci

The nibbling delete / select memegang kunci-kunci ini:

masukkan deskripsi gambar di sini

Sisipan kami mencekal penghapusan kami seperti yang diharapkan:

Sisipkan Blok Hapus

Sekarang, mari kita lakukan transaksi insert dan lihat apa yang terjadi.

Komit Penghapusan

Dan seperti yang diharapkan, semua transaksi selesai. Sekarang, kita harus memeriksa untuk melihat apakah sisipan itu adalah hantu atau apakah operasi penghapusan juga menghapusnya.


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

Bahkan, sisipan telah dihapus; jadi, tidak ada sisipan hantu yang diizinkan.

Tanpa Phantom Insert

Jadi, sebagai kesimpulan, saya pikir maksud sebenarnya dari latihan ini bukan untuk mencoba dan melacak setiap baris, halaman, atau kunci level-tabel dan mencoba untuk menentukan apakah elemen batch dikunci dan karenanya akan memerlukan operasi penghapusan kami untuk Tunggu. Itu mungkin maksud para penanya; Namun, tugas itu sangat besar dan pada dasarnya tidak praktis jika tidak mustahil. Tujuan sebenarnya adalah untuk memastikan bahwa tidak ada fenomena yang tidak diinginkan muncul setelah kami mengisolasi kisaran batch kami dengan kunci kami sendiri dan kemudian mendahului untuk menghapus batch. Level isolasi SERIALIZABLE mencapai tujuan ini. Kuncinya adalah untuk menjaga camilan Anda tetap kecil, log transaksi Anda terkendali, dan menghilangkan fenomena yang tidak diinginkan.

Jika Anda menginginkan kecepatan, maka jangan buat tabel raksasa yang tidak dapat dipartisi dan oleh karena itu tidak dapat menggunakan pengalihan partisi untuk hasil tercepat. Kunci kecepatan adalah partisi dan paralelisme; kunci penderitaan adalah mengunyah dan mengunci hidup.

Tolong beritahu saya bagaimana menurut anda.

Saya membuat beberapa contoh lebih lanjut dari tingkat isolasi SERIALIZABLE dalam aksi. Mereka harus tersedia di tautan di bawah ini.

Hapus Operasi

Masukkan Operasi

Operasi Kesetaraan - Kunci-Rentang Kunci pada Nilai-nilai Kunci Berikutnya

Operasi Kesetaraan - Pengambilan Singleton dari Data yang Ada

Operasi Kesetaraan - Pengambilan Singleton dari Data Tidak Ada

Operasi Ketimpangan - Kunci-Rentang Kunci pada Rentang dan Nilai-nilai Kunci Berikutnya

ooutwire
sumber
9

Jadi, kami mengubah pendekatan untuk menghapus sejumlah kecil baris sekaligus.

Ini adalah ide yang sangat bagus untuk dihapus dalam kumpulan atau potongan kecil yang cermat . Saya akan menambahkan kecil waitfor delay '00:00:05'dan tergantung pada model pemulihan dari database - jika FULL, maka lakukan log backupdan jika SIMPLEkemudian lakukan manual CHECKPOINTuntuk menghindari kembung dari transaksi log - antar batch.

Tetapi kami ingin memeriksa apakah baris yang dipilih (katakanlah 100 atau 1000 atau 2000) saat ini dikunci oleh proses yang berbeda atau tidak.

Apa yang Anda katakan tidak sepenuhnya mungkin di luar kotak (ingat 3 poin utama Anda). Jika saran di atas - small batches + waitfor delaytidak berfungsi (asalkan Anda melakukan pengujian yang tepat), maka Anda dapat menggunakan query HINT.

Jangan gunakan NOLOCK- lihat kb / 308886 , SQL Server Read-Consistency Problem oleh Itzik Ben-Gan , Menempatkan NOLOCK di mana-mana - Oleh Aaron Bertrand dan SQL Server NOLOCK Petunjuk & ide-ide buruk lainnya .

READPASTpetunjuk akan membantu dalam skenario Anda. Inti dari READPASTpetunjuk adalah - jika ada kunci tingkat baris maka SQL server tidak akan membacanya.

Menentukan bahwa Mesin Database tidak membaca baris yang dikunci oleh transaksi lainnya. Saat READPASTditentukan, kunci level baris dilewati. Yaitu, Mesin Basis Data melompat melewati baris alih-alih memblokir transaksi saat ini sampai kunci dilepaskan.

Selama pengujian terbatas saya, saya menemukan throughput yang sangat bagus ketika menggunakan DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK)dan mengatur tingkat isolasi sesi permintaan untuk READ COMMITTEDmenggunakan SET TRANSACTION ISOLATION LEVEL READ COMMITTEDyang tingkat isolasi standar pula.

Kin Shah
sumber
2

Merangkum pendekatan-pendekatan lain yang semula ditawarkan dalam komentar atas pertanyaan.


  1. Gunakan NOWAITjika perilaku yang diinginkan adalah untuk gagal seluruh potongan segera setelah kunci yang tidak kompatibel ditemukan.

    Dari NOWAITdokumentasi :

    Menginstruksikan Mesin Basis Data untuk mengembalikan pesan segera setelah kunci ditemukan di atas meja. NOWAITsetara dengan menentukan SET LOCK_TIMEOUT 0untuk tabel tertentu. The NOWAIThint tidak bekerja ketika TABLOCKpetunjuk juga disertakan. Untuk mengakhiri kueri tanpa menunggu saat menggunakan TABLOCKpetunjuk, SETLOCK_TIMEOUT 0;ganti kata pengantar dengan .

  2. Gunakan SET LOCK_TIMEOUTuntuk mencapai hasil yang serupa, tetapi dengan batas waktu yang dapat dikonfigurasi:

    Dari SET LOCK_TIMEOUTdokumentasi

    Menentukan jumlah milidetik, pernyataan menunggu kunci untuk dirilis.

    Ketika menunggu kunci melebihi nilai batas waktu, kesalahan dikembalikan. Nilai 0 berarti tidak menunggu sama sekali dan mengembalikan pesan segera setelah kunci ditemukan.

Paul White mengatakan GoFundMonica
sumber
0

Untuk mengasumsikan kami memiliki 2 permintaan paralel:

terhubung / sesi 1: akan mengunci baris = 777

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

menghubungkan / sesi 2: akan mengabaikan baris terkunci = 777

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

ATAU hubungkan / sesi 2: akan melempar pengecualian

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;
Lebnik
sumber
-1

Cobalah memfilter sesuatu seperti ini - ini bisa menjadi rumit jika Anda ingin benar-benar spesifik. Lihat di BOL untuk deskripsi 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
rottengeek
sumber
hanya ingin tahu - mengapa downvote?
rottengeek
-11

Anda dapat menggunakan NoLOCK saat Anda menghapus dan jika baris dikunci, mereka tidak akan dihapus. Ini tidak ideal tetapi dapat melakukan trik untuk Anda.

DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True
mouliin
sumber
7
Jika saya mencobanya di komputer lokal saya 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., saya sudah tidak digunakan lagi sejak 2005
Tom V - Team Monica