SQL Server'da bir kilit tabloya eşzamanlı erişimi kilitlenme olmadan işleme


32

Eski bir uygulama tarafından IDENTITYdiğer çeşitli tablolardaki alanların yerine kullanılan bir tablo var .

Tablodaki her satır, içinde LastIDadlandırılan alan için en son kullanılan kimliği saklar IDName.

Zaman zaman saklanan işlem kilitlenmeye başlar - Ben uygun bir hata işleyicisi kurduğuma inanıyorum; Bununla birlikte, bu metodolojinin düşündüğüm gibi çalışıp çalışmadığını veya burada yanlış ağaca ağaç kabuğu alıp almadığımı görmek istiyorum.

Bu tabloya hiçbir kilitlenme olmadan erişmenin bir yolu olması gerektiğinden kesinlikle eminim.

Veritabanının kendisi ile yapılandırılmıştır READ_COMMITTED_SNAPSHOT = 1.

İlk olarak, işte tablo:

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

Ve IDNamesahadaki kümelenmemiş indeks :

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

Bazı örnek veriler:

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

Tabloda depolanan değerleri güncellemek ve bir sonraki kimliği döndürmek için kullanılan saklı yordam:

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

Saklanan işlemin örnek uygulamaları:

EXEC GetNextID 'SomeTestID';

NewID
2

EXEC GetNextID 'SomeTestID';

NewID
3

EXEC GetNextID 'SomeOtherTestID';

NewID
2

DÜZENLE:

Yeni bir dizin ekledim, çünkü mevcut dizin IX_tblIDs_Name SP tarafından kullanılmıyor; Sorgu işlemcisinin LastID'de depolanan değere ihtiyacı olduğu için kümelenmiş dizini kullandığını farz ediyorum. Her neyse, bu endeks gerçek uygulama planı tarafından kullanılıyor:

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

# 2 DÜZENLEME:

@AaronBertrand’ın verdiği ve hafifçe değiştirdiği tavsiyesini aldım. Buradaki genel fikir, gereksiz kilitlemeyi ortadan kaldırmak için yapılan ifadeyi düzeltmek ve genel olarak SP'yi daha verimli kılmaktır.

Aşağıdaki kod adlı Yukarıdaki kodu yerine geçer BEGIN TRANSACTIONiçin 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;

LastIDKodumuz hiçbir zaman bu tabloya 0 ile bir kayıt eklemediğinden, @NewID 1 ise o zaman listeye yeni bir kimlik eklemek niyetindeyiz, aksi halde listedeki mevcut bir satırı güncellediğimizi varsayabiliriz.


Veritabanını RCSI'yı destekleyecek şekilde nasıl yapılandırdığınız önemli değildir. Kasıtlı olarak SERIALIZABLEburaya tırmanıyorsun .
Aaron Bertrand

evet, ilgili tüm bilgileri eklemek istedim. Bunun alakasız olduğunu onayladığına sevindim!
Max Vernon

Sp_getapplock'un kilitlenme kurbanı olması çok kolaydır, ancak işleme başlarsanız, özel bir kilit almak için sp_getapplock'u bir kez arayın ve değişikliklerinizi devam ettirin.
AK

1
IDName benzersiz mi? Ardından " benzersiz kümelenmemiş dizin yarat" önerisini yapın. Ancak null değere ihtiyacınız varsa, endeksin de filtrelenmesi gerekir .
crokusek

Yanıtlar:


15

İlk olarak, her değer için veri tabanına gidiş dönüş yapmaktan kaçınırdım. Örneğin, başvurunuz 20 yeni kimliğe ihtiyaç duyduğunu bilirse, 20 tura çıkmayın. Yalnızca bir saklı yordam çağrısı yapın ve sayacı 20 artırın. Ayrıca, tablonuzu birden çok öğeye bölmek daha iyi olabilir.

Kilitlenmeleri tamamen önlemek mümkündür. Sistemimde hiç kilitlenme yok. Bunu başarmanın birkaç yolu var. Kilitlenmeleri ortadan kaldırmak için sp_getapplock'u nasıl kullanacağımı göstereceğim. Bunun sizin için işe yarayıp yaramayacağına dair hiçbir fikrim yok, çünkü SQL Server kapalı kaynak olduğundan kaynak kodunu göremiyorum ve bu nedenle tüm olası durumları test edip etmediğimi bilmiyorum.

Aşağıda benim için neyin işe yaradığı açıklanmaktadır. YMMV.

İlk olarak, her zaman kayda değer miktarda kilitlenmenin olduğu bir senaryo ile başlayalım. İkincisi, sp_getapplock kullanacağız onları ortadan kaldırmak. Buradaki en önemli nokta, çözümünüzü stres testi yapmaktır. Çözümünüz farklı olabilir, ancak daha sonra göstereceğim gibi bunu yüksek eşzamanlılığa maruz bırakmanız gerekir.

Ön şartlar

Bazı test verilerinin yer aldığı bir tablo ayarlayalım:

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

Aşağıdaki iki prosedürün bir kilitlenmeye girme olasılığı oldukça yüksektir:

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

Kilitlenmeleri çoğaltma

Aşağıdaki döngüler, her çalıştırdığınızda 20'den fazla kilitlenme üretmelidir. 20'den az alırsanız, yineleme sayısını artırın.

Bir sekmede bunu çalıştırın;

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 ;

Başka bir sekmede bu betiği çalıştırın.

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 ;

Her ikisini de birkaç saniye içinde başlattığınızdan emin olun.

Kilitlenmeleri ortadan kaldırmak için sp_getapplock kullanma

Her iki prosedürü de değiştirin, döngüyü tekrar çalıştırın ve artık kilitlenme olmadığına bakın:

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

Kilitlenmeleri ortadan kaldırmak için bir satır içeren bir tablo kullanma

Sp_getapplock'u çağırmak yerine, aşağıdaki tabloyu değiştirebiliriz:

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

Bu tabloyu oluşturduktan ve doldurduktan sonra, aşağıdaki satırı değiştirebiliriz.

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

bununla, her iki işlemde de:

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

Stres testini tekrar çalıştırabilir ve kendimize çıkmaza girmediğimizi görebilirsiniz.

Sonuç

Görüldüğü gibi, sp_getapplock diğer kaynaklara erişimi seri hale getirmek için kullanılabilir. Bu şekilde kilitlenmeleri ortadan kaldırmak için kullanılabilir.

Tabii ki, bu değişiklikleri önemli ölçüde yavaşlatabilir. Bunu ele almak için, özel kilit için doğru ayrıntı derecesini seçmemiz ve mümkün olduğunda münferit satırlar yerine setlerle çalışmamız gerekir.

Bu yaklaşımı kullanmadan önce, kendiniz test etmeniz gerekir. Öncelikle, orijinal yaklaşımınızla en az birkaç düzine kilitlenme aldığınızdan emin olmanız gerekir. İkinci olarak, aynı repro betiğini değiştirilmiş saklı yordamı kullanarak tekrar çalıştırdığınızda kilitlenmemelisiniz.

Genel olarak, T-SQL'inizin kilitlenmelere karşı güvenli olup olmadığına karar vermenin iyi bir yöntem olduğunu sanmıyorum. IMO, kodunuzun kilitlenmelere karşı eğilimli olup olmadığını belirlemenin tek yolu yüksek eşzamanlılığa maruz bırakmaktır.

Kilitlenmeleri ortadan kaldırırken iyi şanslar! Sistemimizde iş hayatı dengesi için harika bir kilitlenme yoktur.


2
Sp_getapplock olarak +1, iyi bilinmeyen kullanışlı bir araçtır. Ayrılması zaman alabilecek 'korkunç bir karmaşa verildiğinde, kilitlenmeyen bir süreci serileştirmek için kullanışlı bir numaradır. Ancak, kolayca anlaşılabilen ve (belki de) standart kilitleme mekanizmalarıyla ele alınabilen böyle bir durum için ilk tercih mi olmalı?
Mark Storey-Smith

2
@ MarkStorey-Smith İlk tercihim çünkü araştırdım ve sadece bir kez test ettim ve herhangi bir durumda tekrar kullanabilirim - serileştirme zaten oldu, bu yüzden sp_getapplock'tan sonra olan her şey sonucu etkilemiyor. Standart kilitleme mekanizmalarıyla, asla bu kadar emin olamam - bir endeks eklemek veya sadece başka bir yürütme planı almak, daha önce bulunmayan yerlerde kilitlenmelere neden olabilir. Bana nasıl bildiğimi sor.
AK

Sanırım bariz bir şeyi özlüyorum, ancak kullanım UPDATE dbo.DeadlockTestMutex SET Toggle = 1 - Toggle WHERE ID = 1;kilitlenmeleri nasıl önler?
Dale K,

9

Kullanımı XLOCKya da üstünde ipucu SELECTyaklaşımı veya şu UPDATEçıkmazdan bu tip bağışıklık olmalıdır:

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;

Birkaç değişkenle geri dönecektir (eğer yenilmezse!).


İken XLOCKbir gerek olmazdı, birden bağlantılarından güncelleştirilmesini varolan sayacı önleyecektir TABLOCKXaynı yeni sayaç eklemeyi birden fazla bağlantıya önlemek için?
Dale K,

1
@DaleBurrell Hayır, IDName üzerinde PK ya da benzersiz bir kısıtlamaya sahip olacaksınız.
Mark Storey-Smith

7

Mike Defehr bana bunu çok hafif bir şekilde başarmanın zarif bir yolunu gösterdi:

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

(Bütünlüğü için, burada saklı işlem ile ilişkili tablo)

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

Bu, en son sürüm için yürütme planıdır:

görüntü tanımını buraya girin

Ve bu, orijinal versiyon için yürütme planıdır (kilitlenme duyarlı):

görüntü tanımını buraya girin

Açıkçası, yeni sürüm kazanır!

Karşılaştırma için, (XLOCK)etc ile olan ara sürüm aşağıdaki planı oluşturur:

görüntü tanımını buraya girin

Bunun bir kazanç olduğunu söyleyebilirim! Herkesin yardımları için teşekkürler!


2
Gerçekten işe yaramalı ama uygulanabilir olmadığı durumlarda SERİLEŞTİRİLEN kullanıyorsunuz. Fantom satırları burada bulunamıyor, öyleyse neden onları önlemek için var olan bir izolasyon seviyesi kullanıyorsunuz? Ayrıca, birisi prosedürünüzü bir başkasından veya bir dış işlemin başlatıldığı bir bağlantıdan ararsa, başlattıkları diğer işlemler SERIALIZABLE'da gerçekleşir. Bu karışık olabilir.
Mark Storey-Smith

2
SERIALIZABLEhayaletleri önlemek için var olmaz. Serileştirilebilir yalıtım semantiği sağlamak , yani söz konusu işlemlerin belirtilmemiş bir sırada seri olarak gerçekleştirilmiş olması gibi veri tabanı üzerindeki kalıcı etkiyi sağlamak için vardır.
Paul White GoFundMonica

6

Mark Storey-Smith'in gök gürültüsünü çalmak için değil, ama üstündeki görevine sahipti (bu arada en fazla oyu almış). Max'e verdiğim tavsiye, gerçekten harika bulduğum "UPDATE set @variable = column = column + value" yapısının etrafında toplanmış, ancak belgelenmemiş olabileceğini düşünüyorum (özellikle TCP için olduğu için desteklenmesi gerekiyor karşılaştırma testleri).

İşte Mark'ın cevabının bir çeşididir - yeni ID değerini bir kayıt kümesi olarak döndürdüğünüz için, skaler değişkeni tamamen ortadan kaldırabileceğinizden, açık bir işlem yapılması gerekmemelidir ve izolasyon seviyeleriyle uğraşmanın gereksiz olduğuna katılıyorum. yanı sıra. Sonuç çok temiz ve çok kaygan ...

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
Bunun, çıkmaza karşı bağışıklık kazanması gerektiği anlaşılıyor, ancak işlemi yapmazsanız uçtaki yarış koşullarına eğilimli.
Mark Storey-Smith

4

Bunu değiştirerek geçen yıl bir sistemde benzer bir kilitlenmeyi düzelttim:

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;

Buna:

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

Genel olarak, COUNTsadece varlığı veya yokluğu belirlemek için bir seçim yapmak oldukça israf edicidir. Bu durumda ya 0 ya da 1 olması çok fazla bir iş değil, (a) alışkanlığın çok daha pahalı olacağı başka durumlarda (bu durumlarda IF NOT EXISTSyerine kullanmak IF COUNT() = 0) kanamaya neden olabilir ve (b) Ek tarama tamamen gereksizdir. UPDATEEsas olarak gerçekleştirir aynı kontrol.

Ayrıca, bu ciddi bir kod kokusu bana benziyor:

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

Buradaki amaç ne? Neden bir kimlik sütunu kullanmıyor ya da ROW_NUMBER()sorgu sırasını kullanarak bu diziyi türetmiyorsunuz ?


Elimizdeki çoğu tablo bir kullanıyor IDENTITY. Bu tablo, MS Access'te yazılmış ve sonradan güçlendirme için oldukça ilgili olan bazı eski kodları desteklemektedir. SET @NewID=Çizgi basitçe (ama bunu zaten biliyorsun) Belirtilen kimlik için tabloda saklanan değeri artırır. Nasıl kullanabileceğimi genişletebilir misin ROW_NUMBER()?
Max Vernon

@MaxVernon LastIDmodelinizde gerçekten ne anlama geldiğini bilmeden . Amacı nedir? Adı tam olarak açıklayıcı değil. Access bunu nasıl kullanır?
Aaron Bertrand

Access'teki bir işlev, KİMLİK olmayan herhangi bir tabloya bir satır eklemek istiyor. Önce Access GetNextID('WhatevertheIDFieldIsCalled')kullanılacak bir sonraki kimliği almak için arama yapar, ardından ne olursa olsun gereken verilerle birlikte yeni satıra ekler.
Max Vernon

Değişikliğini uygulayacağım. Saf bir "az daha fazla" olgusu!
Max Vernon

1
Sabit kilitlenmeniz yeniden ortaya çıkabilir. İkinci modeliniz de savunmasız: sqlblog.com/blogs/alexander_kuznetsov/archive/2010/01/12/… Kilitlenmeleri ortadan kaldırmak için sp_getapplock kullanacağım. Yüzlerce kullanıcılı karma sistem yükleyebilir, kilitlenmez.
AK
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.