Menangani akses bersamaan ke tabel kunci tanpa deadlock di SQL Server


32

Saya memiliki tabel yang digunakan oleh aplikasi lawas sebagai pengganti IDENTITYbidang di berbagai tabel lainnya.

Setiap baris dalam tabel menyimpan ID yang terakhir digunakan LastIDuntuk bidang yang disebutkan dalam IDName.

Terkadang proc yang disimpan menemui jalan buntu - Saya yakin saya telah membangun penangan kesalahan yang sesuai; namun saya tertarik untuk melihat apakah metodologi ini berfungsi seperti yang saya kira, atau jika saya menggonggong pohon yang salah di sini.

Saya cukup yakin harus ada cara untuk mengakses tabel ini tanpa ada deadlock sama sekali.

Basis data itu sendiri dikonfigurasi dengan READ_COMMITTED_SNAPSHOT = 1 .

Pertama, ini adalah tabelnya:

CREATE TABLE [dbo].[tblIDs](
    [IDListID] [int] NOT NULL 
        CONSTRAINT PK_tblIDs 
        PRIMARY KEY CLUSTERED 
        IDENTITY(1,1) ,
    [IDName] [nvarchar](255) NULL,
    [LastID] [int] NULL,
);

Dan indeks nonclustered di IDNamelapangan:

CREATE NONCLUSTERED INDEX [IX_tblIDs_IDName] 
ON [dbo].[tblIDs]
(
    [IDName] ASC
) 
WITH (
    PAD_INDEX = OFF
    , STATISTICS_NORECOMPUTE = OFF
    , SORT_IN_TEMPDB = OFF
    , DROP_EXISTING = OFF
    , ONLINE = OFF
    , ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON
    , FILLFACTOR = 80
);

GO

Beberapa data sampel:

INSERT INTO tblIDs (IDName, LastID) 
    VALUES ('SomeTestID', 1);
INSERT INTO tblIDs (IDName, LastID) 
    VALUES ('SomeOtherTestID', 1);
GO

Prosedur tersimpan digunakan untuk memperbarui nilai-nilai yang disimpan dalam tabel, dan mengembalikan ID berikutnya:

CREATE PROCEDURE [dbo].[GetNextID](
    @IDName nvarchar(255)
)
AS
BEGIN
    /*
        Description:    Increments and returns the LastID value from tblIDs
        for a given IDName
        Author:         Max Vernon
        Date:           2012-07-19
    */

    DECLARE @Retry int;
    DECLARE @EN int, @ES int, @ET int;
    SET @Retry = 5;
    DECLARE @NewID int;
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    SET NOCOUNT ON;
    WHILE @Retry > 0
    BEGIN
        BEGIN TRY
            BEGIN TRANSACTION;
            SET @NewID = COALESCE((SELECT LastID 
                FROM tblIDs 
                WHERE IDName = @IDName),0)+1;
            IF (SELECT COUNT(IDName) 
                FROM tblIDs 
                WHERE IDName = @IDName) = 0 
                    INSERT INTO tblIDs (IDName, LastID) 
                    VALUES (@IDName, @NewID)
            ELSE
                UPDATE tblIDs 
                SET LastID = @NewID 
                WHERE IDName = @IDName;
            COMMIT TRANSACTION;
            SET @Retry = -2; /* no need to retry since the operation completed */
        END TRY
        BEGIN CATCH
            IF (ERROR_NUMBER() = 1205) /* DEADLOCK */
                SET @Retry = @Retry - 1;
            ELSE
                BEGIN
                SET @Retry = -1;
                SET @EN = ERROR_NUMBER();
                SET @ES = ERROR_SEVERITY();
                SET @ET = ERROR_STATE()
                RAISERROR (@EN,@ES,@ET);
                END
            ROLLBACK TRANSACTION;
        END CATCH
    END
    IF @Retry = 0 /* must have deadlock'd 5 times. */
    BEGIN
        SET @EN = 1205;
        SET @ES = 13;
        SET @ET = 1
        RAISERROR (@EN,@ES,@ET);
    END
    ELSE
        SELECT @NewID AS NewID;
END
GO

Contoh eksekusi dari proc yang disimpan:

EXEC GetNextID 'SomeTestID';

NewID
2

EXEC GetNextID 'SomeTestID';

NewID
3

EXEC GetNextID 'SomeOtherTestID';

NewID
2

EDIT:

Saya telah menambahkan indeks baru, karena indeks yang ada IX_tblIDs_Name tidak digunakan oleh SP; Saya berasumsi prosesor permintaan menggunakan indeks berkerumun karena membutuhkan nilai yang disimpan dalam LastID. Bagaimanapun, indeks ini digunakan oleh rencana eksekusi aktual:

CREATE NONCLUSTERED INDEX IX_tblIDs_IDName_LastID 
ON dbo.tblIDs
(
    IDName ASC
) 
INCLUDE
(
    LastID
)
WITH (FILLFACTOR = 100
    , ONLINE=ON
    , ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON);

EDIT # 2:

Saya telah menerima saran yang diberikan @AaronBertrand dan memodifikasinya sedikit. Gagasan umum di sini adalah untuk memperbaiki pernyataan untuk menghilangkan penguncian yang tidak perlu, dan secara keseluruhan untuk membuat SP lebih efisien.

Kode di bawah ini menggantikan kode di atas dari BEGIN TRANSACTIONke END TRANSACTION:

BEGIN TRANSACTION;
SET @NewID = COALESCE((SELECT LastID 
        FROM dbo.tblIDs 
        WHERE IDName = @IDName), 0) + 1;

IF @NewID = 1
    INSERT INTO tblIDs (IDName, LastID) 
    VALUES (@IDName, @NewID);
ELSE
    UPDATE dbo.tblIDs 
    SET LastID = @NewID 
    WHERE IDName = @IDName;

COMMIT TRANSACTION;

Karena kode kita tidak pernah menambahkan catatan ke tabel ini dengan 0 di LastIDkita dapat membuat asumsi bahwa jika @NewID adalah 1 maka maksudnya adalah menambahkan ID baru ke daftar, kalau tidak kita memperbarui baris yang ada dalam daftar.


Bagaimana Anda mengonfigurasi basis data untuk mendukung RCSI tidak relevan. Anda sengaja naik ke SERIALIZABLEsini.
Aaron Bertrand

ya, saya hanya ingin menambahkan semua info yang relevan. Saya senang Anda mengkonfirmasikan bahwa itu tidak relevan!
Max Vernon

sangat mudah untuk membuat sp_getapplock menjadi korban kebuntuan, tetapi tidak jika Anda memulai transaksi, panggil sp_getapplock sekali untuk mendapatkan kunci eksklusif, dan teruskan modifikasi Anda.
AK

1
Apakah IDName unik? Kemudian rekomendasikan "buat indeks nonclustered unik ". Namun jika Anda membutuhkan nilai nol maka indeks juga perlu disaring .
crokusek

Jawaban:


15

Pertama, saya akan menghindari melakukan perjalanan pulang pergi ke database untuk setiap nilai. Misalnya, jika aplikasi Anda tahu bahwa ia memerlukan 20 ID baru, jangan lakukan 20 perjalanan pulang pergi. Buat hanya satu panggilan prosedur yang tersimpan, dan tambahkan penghitung dengan 20. Juga mungkin lebih baik untuk membagi meja Anda menjadi beberapa.

Dimungkinkan untuk menghindari kebuntuan sama sekali. Saya tidak punya deadlock sama sekali di sistem saya. Ada beberapa cara untuk mencapai itu. Saya akan menunjukkan bagaimana saya akan menggunakan sp_getapplock untuk menghilangkan kebuntuan. Saya tidak tahu apakah ini akan bekerja untuk Anda, karena SQL Server adalah sumber tertutup, jadi saya tidak dapat melihat kode sumber, dan karena itu saya tidak tahu apakah saya telah menguji semua kemungkinan kasus.

Yang berikut ini menjelaskan apa yang berhasil untuk saya. YMMV.

Pertama, mari kita mulai dengan skenario di mana kita selalu mendapatkan kebuntuan yang cukup besar. Kedua, kita akan menggunakan menghilangkan sp_getapplock mereka. Poin paling penting di sini adalah untuk menguji stres solusi Anda. Solusi Anda mungkin berbeda, tetapi Anda perlu mengeksposnya ke konkurensi tinggi, seperti yang akan saya tunjukkan nanti.

Prasyarat

Mari kita buat tabel dengan beberapa data uji:

CREATE TABLE dbo.Numbers(n INT NOT NULL PRIMARY KEY); 
GO 

INSERT INTO dbo.Numbers 
    ( n ) 
        VALUES  ( 1 ); 
GO 
DECLARE @i INT; 
    SET @i=0; 
WHILE @i<21  
    BEGIN 
    INSERT INTO dbo.Numbers 
        ( n ) 
        SELECT n + POWER(2, @i) 
        FROM dbo.Numbers; 
    SET @i = @i + 1; 
    END;  
GO

SELECT n AS ID, n AS Key1, n AS Key2, 0 AS Counter1, 0 AS Counter2
INTO dbo.DeadlockTest FROM dbo.Numbers
GO

ALTER TABLE dbo.DeadlockTest ADD CONSTRAINT PK_DeadlockTest PRIMARY KEY(ID);
GO

CREATE INDEX DeadlockTestKey1 ON dbo.DeadlockTest(Key1);
GO

CREATE INDEX DeadlockTestKey2 ON dbo.DeadlockTest(Key2);
GO

Dua prosedur berikut ini kemungkinan besar akan mengalami kebuntuan:

CREATE PROCEDURE dbo.UpdateCounter1 @Key1 INT
AS
SET NOCOUNT ON ;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION ;
UPDATE dbo.DeadlockTest SET Counter1=Counter1+1 WHERE Key1=@Key1;
SET @Key1=@Key1-10000;
UPDATE dbo.DeadlockTest SET Counter1=Counter1+1 WHERE Key1=@Key1;
COMMIT;
GO

CREATE PROCEDURE dbo.UpdateCounter2 @Key2 INT
AS
SET NOCOUNT ON ;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION ;
SET @Key2=@Key2-10000;
UPDATE dbo.DeadlockTest SET Counter2=Counter2+1 WHERE Key2=@Key2;
SET @Key2=@Key2+10000;
UPDATE dbo.DeadlockTest SET Counter2=Counter2+1 WHERE Key2=@Key2;
COMMIT;
GO

Kebuntuan mereproduksi

Loop berikut harus mereproduksi lebih dari 20 deadlock setiap kali Anda menjalankannya. Jika Anda mendapatkan kurang dari 20, tambah jumlah iterasi.

Dalam satu tab, jalankan ini;

DECLARE @i INT, @DeadlockCount INT;
SELECT @i=0, @DeadlockCount=0;

WHILE @i<5000 BEGIN ;
  BEGIN TRY 
    EXEC dbo.UpdateCounter1 @Key1=123456;
  END TRY
  BEGIN CATCH
    SET @DeadlockCount = @DeadlockCount + 1;
    ROLLBACK;
  END CATCH ;
  SET @i = @i + 1;
END;
SELECT 'Deadlocks caught: ', @DeadlockCount ;

Di tab lain, jalankan skrip ini.

DECLARE @i INT, @DeadlockCount INT;
SELECT @i=0, @DeadlockCount=0;

WHILE @i<5000 BEGIN ;
  BEGIN TRY 
    EXEC dbo.UpdateCounter2 @Key2=123456;
  END TRY
  BEGIN CATCH
    SET @DeadlockCount = @DeadlockCount + 1;
    ROLLBACK;
  END CATCH ;
  SET @i = @i + 1;
END;
SELECT 'Deadlocks caught: ', @DeadlockCount ;

Pastikan Anda memulai keduanya dalam beberapa detik.

Menggunakan sp_getapplock untuk menghilangkan kebuntuan

Ubah kedua prosedur, jalankan kembali loop, dan pastikan Anda tidak lagi memiliki deadlock:

ALTER PROCEDURE dbo.UpdateCounter1 @Key1 INT
AS
SET NOCOUNT ON ;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION ;
EXEC sp_getapplock @Resource='DeadlockTest', @LockMode='Exclusive';
UPDATE dbo.DeadlockTest SET Counter1=Counter1+1 WHERE Key1=@Key1;
SET @Key1=@Key1-10000;
UPDATE dbo.DeadlockTest SET Counter1=Counter1+1 WHERE Key1=@Key1;
COMMIT;
GO

ALTER PROCEDURE dbo.UpdateCounter2 @Key2 INT
AS
SET NOCOUNT ON ;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION ;
EXEC sp_getapplock @Resource='DeadlockTest', @LockMode='Exclusive';
SET @Key2=@Key2-10000;
UPDATE dbo.DeadlockTest SET Counter2=Counter2+1 WHERE Key2=@Key2;
SET @Key2=@Key2+10000;
UPDATE dbo.DeadlockTest SET Counter2=Counter2+1 WHERE Key2=@Key2;
COMMIT;
GO

Menggunakan tabel dengan satu baris untuk menghilangkan kebuntuan

Alih-alih memanggil sp_getapplock, kita dapat memodifikasi tabel berikut:

CREATE TABLE dbo.DeadlockTestMutex(
ID INT NOT NULL,
CONSTRAINT PK_DeadlockTestMutex PRIMARY KEY(ID),
Toggle INT NOT NULL);
GO

INSERT INTO dbo.DeadlockTestMutex(ID, Toggle)
VALUES(1,0);

Setelah tabel ini dibuat dan diisi, kita dapat mengganti baris berikut

EXEC sp_getapplock @Resource='DeadlockTest', @LockMode='Exclusive';

dengan yang ini, dalam kedua prosedur:

UPDATE dbo.DeadlockTestMutex SET Toggle = 1 - Toggle WHERE ID = 1;

Anda dapat menjalankan kembali tes stres, dan lihat sendiri bahwa kami tidak memiliki kebuntuan.

Kesimpulan

Seperti yang telah kita lihat, sp_getapplock dapat digunakan untuk membuat serialisasi akses ke sumber daya lain. Dengan demikian dapat digunakan untuk menghilangkan kebuntuan.

Tentu saja, ini dapat memperlambat modifikasi secara signifikan. Untuk mengatasinya, kita perlu memilih perincian yang tepat untuk kunci eksklusif, dan jika memungkinkan, bekerja dengan set alih-alih baris individual.

Sebelum menggunakan pendekatan ini, Anda perlu melakukan stress test sendiri. Pertama, Anda harus memastikan Anda mendapatkan setidaknya beberapa lusin kebuntuan dengan pendekatan awal Anda. Kedua, Anda seharusnya tidak mendapatkan deadlock ketika Anda menjalankan kembali script repro yang sama menggunakan prosedur tersimpan yang dimodifikasi.

Secara umum, saya tidak berpikir ada cara yang baik untuk menentukan apakah T-SQL Anda aman dari kebuntuan hanya dengan melihatnya atau melihat rencana eksekusi. IMO satu-satunya cara untuk menentukan apakah kode Anda rentan terhadap deadlock adalah dengan mengeksposnya ke konkurensi tinggi.

Semoga berhasil dengan menghilangkan kebuntuan! Kami tidak memiliki kebuntuan dalam sistem kami sama sekali, yang sangat bagus untuk keseimbangan kehidupan kerja kami.


2
Memberi +1 sebagai sp_getapplock adalah alat bermanfaat yang tidak dikenal. Mengingat kekacauan yang mengerikan yang mungkin membutuhkan waktu untuk dipisahkan, ini adalah trik yang berguna untuk membuat serial proses yang menemui jalan buntu. Tetapi, haruskah itu menjadi pilihan pertama untuk kasus seperti ini yang mudah dipahami dan dapat (mungkin harus) ditangani dengan mekanisme penguncian standar?
Mark Storey-Smith

2
@ MarkStorey-Smith Ini adalah pilihan pertama saya karena saya telah meneliti dan stress hanya mengujinya sekali, dan saya dapat menggunakannya kembali dalam situasi apa pun - serialisasi telah terjadi, jadi semua yang terjadi setelah sp_getapplock tidak mempengaruhi hasilnya. Dengan mekanisme penguncian standar, saya tidak pernah bisa begitu yakin - menambahkan indeks atau hanya mendapatkan rencana eksekusi lain dapat menyebabkan kebuntuan di mana sebelumnya tidak ada. Tanya saya bagaimana saya tahu.
AK

Saya kira saya kehilangan sesuatu yang jelas, tetapi bagaimana cara UPDATE dbo.DeadlockTestMutex SET Toggle = 1 - Toggle WHERE ID = 1;mencegah kebuntuan?
Dale K

9

Penggunaan XLOCKpetunjuk pada SELECTpendekatan Anda atau yang berikut ini UPDATEharus kebal terhadap jenis kebuntuan ini:

DECLARE @Output TABLE ([NewId] INT);
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

BEGIN TRANSACTION;

UPDATE
    dbo.tblIDs WITH (XLOCK)
SET 
    LastID = LastID + 1
OUTPUT
    INSERTED.[LastId] INTO @Output
WHERE
    IDName = @IDName;

IF(@@ROWCOUNT = 1)
BEGIN
    SELECT @NewId = [NewId] FROM @Output;
END
ELSE
BEGIN
    SET @NewId = 1;

    INSERT dbo.tblIDs
        (IDName, LastID)
    VALUES
        (@IDName, @NewId);
END

SELECT [NewId] = @NewId ;

COMMIT TRANSACTION;

Akan kembali dengan beberapa varian lain (jika tidak dikalahkan!).


Meskipun XLOCKakan mencegah penghitung yang ada diperbarui dari banyak koneksi, tidakkah Anda perlu TABLOCKXuntuk mencegah beberapa koneksi menambahkan penghitung baru yang sama?
Dale K

1
@DaleBurrell Tidak, Anda akan memiliki PK atau batasan unik pada IDName.
Mark Storey-Smith

7

Mike Defehr menunjukkan kepada saya cara yang elegan untuk mencapai ini dengan cara yang sangat ringan:

ALTER PROCEDURE [dbo].[GetNextID](
    @IDName nvarchar(255)
)
AS
BEGIN
    /*
        Description:    Increments and returns the LastID value from tblIDs for a given IDName
        Author:         Max Vernon / Mike Defehr
        Date:           2012-07-19

    */

    DECLARE @Retry int;
    DECLARE @EN int, @ES int, @ET int;
    SET @Retry = 5;
    DECLARE @NewID int;
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    SET NOCOUNT ON;
    WHILE @Retry > 0
    BEGIN
        BEGIN TRY
            UPDATE dbo.tblIDs 
            SET @NewID = LastID = LastID + 1 
            WHERE IDName = @IDName;

            IF @NewID IS NULL
            BEGIN
                SET @NewID = 1;
                INSERT INTO tblIDs (IDName, LastID) VALUES (@IDName, @NewID);
            END
            SET @Retry = -2; /* no need to retry since the operation completed */
        END TRY
        BEGIN CATCH
            IF (ERROR_NUMBER() = 1205) /* DEADLOCK */
                SET @Retry = @Retry - 1;
            ELSE
                BEGIN
                SET @Retry = -1;
                SET @EN = ERROR_NUMBER();
                SET @ES = ERROR_SEVERITY();
                SET @ET = ERROR_STATE()
                RAISERROR (@EN,@ES,@ET);
                END
        END CATCH
    END
    IF @Retry = 0 /* must have deadlock'd 5 times. */
    BEGIN
        SET @EN = 1205;
        SET @ES = 13;
        SET @ET = 1
        RAISERROR (@EN,@ES,@ET);
    END
    ELSE
        SELECT @NewID AS NewID;
END
GO

(Untuk kelengkapan, berikut adalah tabel yang terkait dengan proc yang disimpan)

CREATE TABLE [dbo].[tblIDs]
(
    IDName nvarchar(255) NOT NULL,
    LastID int NULL,
    CONSTRAINT [PK_tblIDs] PRIMARY KEY CLUSTERED 
    (
        [IDName] ASC
    ) WITH 
    (
        PAD_INDEX = OFF
        , STATISTICS_NORECOMPUTE = OFF
        , IGNORE_DUP_KEY = OFF
        , ALLOW_ROW_LOCKS = ON
        , ALLOW_PAGE_LOCKS = ON
        , FILLFACTOR = 100
    ) 
);
GO

Ini adalah paket eksekusi untuk versi terbaru:

masukkan deskripsi gambar di sini

Dan ini adalah rencana eksekusi untuk versi asli (rentan kebuntuan):

masukkan deskripsi gambar di sini

Jelas, versi baru menang!

Sebagai perbandingan, versi perantara dengan (XLOCK)dll, menghasilkan paket berikut:

masukkan deskripsi gambar di sini

Saya akan mengatakan itu kemenangan! Terima kasih atas bantuan semua orang!


2
Seharusnya memang berfungsi tetapi Anda menggunakan SERIALIZABLE di mana itu tidak berlaku. Baris phantom tidak bisa ada di sini, jadi mengapa menggunakan level isolasi yang ada untuk mencegahnya? Juga, jika seseorang memanggil prosedur Anda dari orang lain atau dari koneksi tempat transaksi luar dimulai, tindakan lebih lanjut yang mereka lakukan akan dilanjutkan di SERIALIZABLE. Itu bisa berantakan.
Mark Storey-Smith

2
SERIALIZABLEtidak ada untuk mencegah hantu. Itu ada untuk memberikan semantik isolasi serializable , yaitu efek persisten yang sama pada database seolah-olah transaksi yang terlibat telah dieksekusi secara seri dalam beberapa urutan yang tidak ditentukan.
Paul White mengatakan GoFundMonica

6

Bukan untuk mencuri guntur Mark Storey-Smith, tetapi ia ke sesuatu dengan jabatannya di atas (yang notabene menerima paling banyak suara positif). Saran yang saya berikan kepada Max berpusat di sekitar "UPDATE set @variable = column = kolom + value" yang menurut saya sangat keren, tapi saya pikir mungkin tidak berdokumen (harus didukung, karena ada khusus untuk TCP tolok ukur).

Berikut adalah variasi jawaban Mark - karena Anda mengembalikan nilai ID baru sebagai rekaman, Anda dapat menghapus variabel skalar sepenuhnya, tidak ada transaksi eksplisit yang diperlukan juga, dan saya akan setuju bahwa bermain-main dengan tingkat isolasi tidak diperlukan demikian juga. Hasilnya sangat bersih dan cukup apik ...

ALTER PROC [dbo].[GetNextID]
  @IDName nvarchar(255)
  AS
BEGIN
SET NOCOUNT ON;

DECLARE @Output TABLE ([NewID] INT);

UPDATE dbo.tblIDs SET LastID = LastID + 1
OUTPUT inserted.[LastId] INTO @Output
WHERE IDName = @IDName;

IF(@@ROWCOUNT = 1)
    SELECT [NewID] FROM @Output;
ELSE
    INSERT dbo.tblIDs (IDName, LastID)
    OUTPUT INSERTED.LastID AS [NewID]
    VALUES (@IDName,1);
END

3
Setuju ini harus kebal terhadap kebuntuan tetapi rentan terhadap kondisi balapan pada sisipan, jika Anda menghilangkan transaksi.
Mark Storey-Smith

4

Saya memperbaiki kebuntuan serupa dalam suatu sistem tahun lalu dengan mengubah ini:

IF (SELECT COUNT(IDName) FROM tblIDs WHERE IDName = @IDName) = 0 
  INSERT INTO tblIDs (IDName, LastID) VALUES (@IDName, @NewID)
ELSE
  UPDATE tblIDs SET LastID = @NewID WHERE IDName = @IDName;

Untuk ini:

UPDATE tblIDs SET LastID = @NewID WHERE IDName = @IDName;
IF @@ROWCOUNT = 0
BEGIN
  INSERT ...
END

Secara umum, memilih COUNThanya untuk menentukan ada atau tidaknya cukup boros. Dalam hal ini karena 0 atau 1 tidak seperti itu banyak pekerjaan, tetapi (a) kebiasaan itu dapat berdarah ke dalam kasus-kasus lain di mana itu akan jauh lebih mahal (dalam kasus-kasus itu, gunakan IF NOT EXISTSbukan IF COUNT() = 0), dan (b) pemindaian tambahan sama sekali tidak perlu. ItuUPDATE dasarnya melakukan pemeriksaan yang sama.

Juga, ini sepertinya bau kode serius bagi saya:

SET @NewID = COALESCE((SELECT LastID FROM tblIDs WHERE IDName = @IDName),0)+1;

Apa gunanya di sini? Mengapa tidak menggunakan kolom identitas atau mendapatkan urutan yang menggunakan ROW_NUMBER()pada waktu permintaan?


Sebagian besar tabel yang kita miliki menggunakan IDENTITY. Tabel ini mendukung beberapa kode lawas yang ditulis dalam MS Access yang akan cukup terlibat untuk retrofit. The SET @NewID=garis hanya akan menambahkan nilai yang disimpan dalam tabel untuk ID yang diberikan (tetapi Anda sudah tahu itu). Bisakah Anda memperluas bagaimana saya bisa menggunakan ROW_NUMBER()?
Max Vernon

@ MaxVernon bukan tanpa tahu apa yang LastIDsebenarnya berarti dalam model Anda. Apa tujuannya? Nama itu tidak terlalu jelas. Bagaimana Access menggunakannya?
Aaron Bertrand

Fungsi di Access ingin menambahkan baris ke tabel yang diberikan yang tidak memiliki IDENTITAS. Akses pertama panggilan GetNextID('WhatevertheIDFieldIsCalled')untuk mendapatkan ID berikutnya untuk digunakan, kemudian memasukkannya ke baris baru bersama dengan data apa pun yang diperlukan.
Max Vernon

Saya akan menerapkan perubahan Anda. Kasus murni "kurang lebih"!
Max Vernon

1
Jalan buntu Anda dapat muncul kembali. Pola kedua Anda juga rentan: sqlblog.com/blogs/alexander_kuznetsov/archive/2010/01/12/... Untuk menghilangkan deadlock, saya akan menggunakan sp_getapplock. Semoga sistem muatan campuran dengan ratusan pengguna tidak memiliki deadlock.
AK
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.