Duplikat catatan dikembalikan dari tabel tanpa duplikat

8

Saya memiliki prosedur tersimpan yang menanyakan tabel antrian sibuk yang digunakan untuk mendistribusikan pekerjaan di sistem kami. Tabel tersebut memiliki kunci utama pada WorkID dan tidak ada duplikat.

Versi kueri yang disederhanakan adalah:

INSERT INTO #TempWorkIDs (WorkID)
SELECT
        W.WorkID

    FROM
        dbo.WorkTable W

    WHERE
        (@bool_param = 0 AND
        ((W.InProgress = 0
         AND ISNULL(W.UserID, -1) != @userid_param
         AND (@bool_filtered = 0
              OR W.TypeID IN (SELECT TypeID FROM #Types AS t)))
         OR 
         (@bool_param = 1
          AND W.InProgress = 1
          AND W.UserID != @userid_param)
        OR
        (@Auto_Param = 0
         AND W.UserID = @userid_param)))
         OR
         (@bool_param = 1 AND W.UserID = @userid_param)
    OPTION
        (RECOMPILE)

The #Typesmeja dihuni awal prosedur.

Seperti yang saya katakan, WorkTablesibuk, dan kadang-kadang saat kueri ini berjalan, saya MENCURANGI salah satu catatan bergerak dari satu set filter WHEREke yang lain. Khususnya, ini terjadi ketika seseorang mulai mengerjakan suatu item, dan W.InProgressperubahan dari 0 menjadi 1. Ketika ini terjadi, saya mendapatkan pelanggaran kunci duplikat ketika saya mencoba menambahkan kunci utama ke tabel temp sementara kueri ini dimasukkan ke dalam.

Saya telah mengkonfirmasi dalam rencana kueri yang dihasilkan ketika kesalahan terjadi bahwa tidak ada paralelisme, tingkat isolasi READ COMMITTED, dan tidak ada rekaman duplikat di tabel sumber. Anda juga dapat melihat tidak ada JOINcara lain untuk mendapatkan produk kartesian di sini.

Ini adalah paket permintaan anonim:

masukkan deskripsi gambar di sini

Pertanyaannya adalah, apa yang menyebabkan duplikat dan bagaimana saya bisa menghentikannya?

Saya pikir READ COMMITTEDharus bekerja di sini, saya perlu mengunci. Saya hampir positif dupes terjadi ketika InProgressbit pada catatan berubah saat saya bertanya. Saya tahu ini karena tabel menyimpan waktu perubahan itu dan itu dalam milidetik ketika saya meminta dan mendapatkan kesalahan.

JNK
sumber

Jawaban:

9

Ada beberapa skenario yang rumit yang dapat mengakibatkan baris yang sama sedang dibaca dua kali dari indeks, bahkan di bawah yang READ COMMITTEDtingkat isolasi .

Kueri Anda tidak memenuhi syarat untuk pemindaian pesanan alokasi, sehingga mesin penyimpanan akan membaca data dari tabel dalam urutan kunci berkerumun.

Untuk tabel Anda, Anda memiliki InProgresssebagai kolom pertama dari kunci berkerumun. Kemungkinan Anda mendapatkan kunci baris atau halaman saat Anda memindai melalui tabel. Jika Anda membaca baris di dekat awal pemindaian, lepaskan kuncinya, baris itu diperbarui sedemikian rupa sehingga InProgressberubah dari 0 menjadi 1, dan kemudian baris tersebut dibaca lagi di halaman yang berbeda maka Anda dapat melihat WorkIDnilai duplikat dari kueri Anda .

Ada banyak solusi. Anda bisa memasukkan ke tumpukan dan hanya menghapus nilai duplikat. Anda bisa menambahkan DISTINCTke kueri. Anda juga dapat mengaktifkan tingkat isolasi versi baris, untuk memberikan pandangan yang stabil dari keadaan database yang dilakukan, baik pada awal transaksi ( isolasi snapshot ), atau pada awal pernyataan ( baca isolasi snapshot yang dilakukan ).

Mungkin lebih tepat untuk menambahkan petunjuk penguncian atau mengubah struktur tabel. Untuk solusi yang agak menyenangkan (mungkin tidak sesuai untuk produksi), Anda dapat mencoba membaca indeks mundur. Ini dapat dilakukan dengan berlebihan TOPbersama dengan ORDER BY. Di bawah ini adalah demo yang sangat sederhana untuk menggambarkan poin:

CREATE TABLE #WorkTable (
    InProgress TINYINT NOT NULL,
    WorkID INT NOT NULL
    , PRIMARY KEY (InProgress, WorkID)
);

INSERT INTO #WorkTable WITH (TABLOCK)
SELECT (RN - 1) / 5000, RN
FROM
(
    SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) t
OPTION (MAXDOP 1);

Kueri berikut memiliki Urutan: properti palsu tetapi masih akan membaca data dalam urutan kunci bergerombol:

SELECT WorkId
FROM #WorkTable;

Namun, kueri berikut akan membaca data dalam urutan berkerumun terbalik:

SELECT TOP (9223372036854775807) WorkId
FROM #WorkTable
ORDER BY InProgress DESC, WorkId DESC;

Kita dapat melihat ini dengan melihat properti pemindaian:

pemindaian mundur

Untuk tabel Anda, ini berarti bahwa jika satu baris diperbarui sedemikian rupa sehingga InProgressberubah dari 0 menjadi 1, kemungkinannya kecil bahwa itu akan muncul dua kali. Mungkin tidak muncul sama sekali yang bisa menjadi masalah yang berbeda.

Joe Obbish
sumber