Birincil anahtarı KİMLİK'ten kalıcılığa değiştirme COALESCE kullanarak hesaplanan sütun


10

Bir uygulamayı monolitik veritabanımızdan ayırmak amacıyla, çeşitli tabloların INT IDENTITY sütunlarını COALESCE kullanan PERSISTED hesaplanmış bir sütun olarak değiştirmeye çalıştık. Temel olarak, ayrıştırılmış uygulamanın hala birçok uygulamada paylaşılan ortak veriler için veritabanını güncelleştirme yeteneğine ihtiyacımız olurken, mevcut uygulamaların bu tablolarda kod veya yordam değişikliğine gerek kalmadan veri oluşturmasına izin veririz.

Yani, aslında, bir sütun tanımından taşındık;

PkId INT IDENTITY(1,1) PRIMARY KEY

için;

PkId AS AS COALESCE(old_id, external_id, new_id) PERSISTED NOT NULL,
old_id INT NULL, -- Values here are from existing records of PkId before table change
external_id INT NULL,
new_id INT IDENTITY(2000000,1) NOT NULL

Her durumda PkId aynı zamanda BİRİNCİL ANAHTAR'dır ve bir durum hariç tümü KÜMELENMİŞTİR. Tüm tablolar eskisi gibi yabancı anahtar ve dizinlere sahiptir. Özünde, yeni biçim PkId'in ayrıştırılmış uygulama tarafından sağlanmasına izin verir (external_id olarak), ancak PkId'in IDENTITY sütun değeri olmasına izin verir, bu nedenle SCOPE_IDENTITY ve @@ IDENTITY aracılığıyla IDENTITY sütununa dayanan mevcut kodlara izin verir eskiden olduğu gibi çalışmak.

Karşılaştığımız sorun, şu anda tamamen patlamak için kabul edilebilir bir zamanda çalıştırılan birkaç sorguyla karşılaşmamızdı. Bu sorguların kullandığı sorgu planları, eskiden olduğu gibi değildir.

Yeni sütun bir PRIMARY KEY, önceki ile aynı veri türü ve PERSISTED göz önüne alındığında, sorgular ve sorgu planları daha önce olduğu gibi davranmasını beklerdim. COMPUTED PERSISTED INT PkId, SQL Server'ın yürütme planını nasıl üreteceği açısından açık bir INT tanımıyla aynı şekilde davranmalı mıdır? Bu yaklaşımla ilgili görebileceğiniz başka sorunlar var mı?

Bu değişikliğin amacının, mevcut prosedürleri ve kodu değiştirmeye gerek kalmadan tablo tanımını değiştirmemize izin vermesi gerekiyordu. Bu sorunlar göz önüne alındığında, bu yaklaşımla gidebileceğimizi düşünmüyorum.


Yorumlar uzun tartışmalar için değildir; bu sohbet sohbete taşındı .
Paul White 9

Yanıtlar:


4

İLK

Muhtemelen her üç sütun gerekmez: old_id, external_id, new_id. Bir olan new_idsütun, IDENTITYeklediğinizde bile her satır için yeni bir değere sahip olacaktır external_id. Ancak, old_idve arasında external_id, bunlar birbirini tamamen dışlar: ya zaten bir old_iddeğer var ya da mevcut kavramada, bu sütun sadece veya NULLkullanıldığında olacak . Halihazırda var olan (yani değeri olan bir satır) yeni bir "harici" kimlik eklemeyeceğiniz ve herhangi bir yeni değer gelmeyeceği için , kullanılan bir sütun olabilir her iki amaç için.external_idnew_idold_idold_id

Bu nedenle, external_idsütundan kurtulun ve old_idbenzer bir şey olarak yeniden adlandırın old_or_external_id. Bu, hiçbir şeyde gerçek bir değişiklik gerektirmemeli, ancak bazı komplikasyonları azaltmalıdır. Eklemek için external_iduygulama kodu zaten yazıldıysa, en fazla "eski" değerler içeriyor olsa bile sütunu aramanız gerekebilir external_id.

Bu, yeni yapıyı sadece:

PkId AS AS COALESCE(old_or_external_id, new_id, -1) PERSISTED NOT NULL,
old_or_external_id INT NULL, -- values from existing record OR passed in from app
new_id INT IDENTITY(2000000, 1) NOT NULL

Artık 12 bayt yerine satır başına yalnızca 8 bayt eklediniz ( SPARSEseçeneği veya Veri Sıkıştırma'yı kullanmadığınız varsayılarak ). Ve herhangi bir kodu, T-SQL veya Uygulama kodunu değiştirmeniz gerekmez.

İKİNCİ

Bu sadeleştirme yolunda devam ederek, geriye ne bıraktığımıza bakalım:

  • old_or_external_idKolon ya da zaten değerlere sahiptir, ya da uygulama yeni bir değer verilecek veya bırakılacaktır NULL.
  • Her new_idzaman yeni bir değer oluşturulur, ancak bu değer yalnızca old_or_external_idsütun ise kullanılır NULL.

Hem old_or_external_idve hem de değerlere ihtiyaç duyacağınız bir zaman asla olmaz new_id. Her iki sütun nedeniyle değerlere sahip olduğunda Evet, bazı zamanlar olacaktır new_idbir varlık IDENTITY, ancak bu new_iddeğerler dikkate alınmaz. Yine, bu iki alan birbirini dışlar. Peki şimdi ne olacak?

Şimdi neden external_idilk başta ihtiyacımız olduğuna bakabiliriz . IDENTITYKullanarak bir sütuna eklenmenin mümkün olduğunu göz önünde bulundurarak, SET IDENTITY_INSERT {table_name} ON;hiçbir şema değişikliği yapmadan kurtulabilir ve uygulama kodunuzu yalnızca INSERTifadeleri / işlemleri SET IDENTITY_INSERT {table_name} ON;ve SET IDENTITY_INSERT {table_name} OFF;ifadeleri sarmak için değiştirebilirsiniz . Daha sonra, IDENTITYsütunu (yeni oluşturulan değerler için) sıfırlamak için hangi başlangıç ​​aralığını belirlemeniz gerekir, çünkü daha yüksek bir değer eklemek sonraki otomatik oluşturulan değerin geçerli MAX değerinden büyük olmalıdır. Ancak her zaman IDENT_CURRENT değerinin altında bir değer ekleyebilirsiniz .

old_or_external_idVe new_idsütunlarını birleştirmek, otomatik olarak oluşturulan değerler ile uygulama tarafından oluşturulan değerler arasında üst üste binen bir değer durumuna geçme şansını da artırmaz, çünkü 2 veya hatta 3 sütuna sahip olma amacı, bunları Birincil Anahtar değerinde birleştirmektir. ve bunlar her zaman benzersiz değerlerdir.

Bu yaklaşımda, yapmanız gerekenler:

  • Tabloları şu şekilde bırakın:

    PkId INT IDENTITY(1,1) PRIMARY KEY

    Bu, her satıra 8 veya 12 yerine 0 bayt ekler.

  • Uygulama tarafından oluşturulan değerler için başlangıç ​​aralığını belirleyin. Bunlar, her bir tablodaki geçerli MAX değerinden daha büyük olacak, ancak otomatik olarak oluşturulan değerler için minimum değerden daha düşük olacaktır.
  • Otomatik olarak oluşturulan aralığın hangi değerden başlaması gerektiğini belirleyin. Geçerli MAX değer arasındaki sürü oda var olmalıdır ve üst sınırda bilerek büyümeye oda bol biraz üzerinde 2140000000 olduğunu. Daha sonra bu yeni minimum tohum değerini DBCC CHECKIDENT aracılığıyla ayarlayabilirsiniz .
  • Uygulama kodu INSERT'leri SET IDENTITY_INSERT {table_name} ON;ve SET IDENTITY_INSERT {table_name} OFF;ifadeleri sarın .

İKİNCİ, Bölüm B

Doğrudan yukarıda belirtilen yaklaşımın bir varyasyonu, App kodu değerlerin -1 ile başlayıp oradan aşağıya inmesi olacaktır . Bu IDENTITYdeğerleri yükselen değerler olarak bırakır . Buradaki fayda, yalnızca şemayı karmaşıklaştırmakla kalmaz, aynı zamanda çakışan kimliklere girme konusunda da endişelenmenize gerek yoktur (uygulama tarafından oluşturulan değerler yeni otomatik oluşturulan aralığa girerse). Bu, yalnızca zaten negatif kimlik değerleri kullanmıyorsanız bir seçenektir (ve insanların otomatik olarak oluşturulan sütunlarda negatif değerler kullanması oldukça nadir görünüyor, bu nedenle çoğu durumda olası bir olasılık olmalıdır).

Bu yaklaşımda, yapmanız gerekenler:

  • Tabloları şu şekilde bırakın:

    PkId INT IDENTITY(1,1) PRIMARY KEY

    Bu, her satıra 8 veya 12 yerine 0 bayt ekler.

  • Uygulama tarafından oluşturulan değerler için başlangıç ​​aralığı olacaktır -1.
  • Uygulama kodu INSERT'leri SET IDENTITY_INSERT {table_name} ON;ve SET IDENTITY_INSERT {table_name} OFF;ifadeleri sarın .

Burada yine de yapmanız gerekir IDENTITY_INSERT, ancak: yeni sütun eklemezsiniz, hiçbir IDENTITYsütunu "yeniden düzenlemenize" gerek yoktur ve gelecekteki çakışma riski yoktur.

İKİNCİ, Bölüm 3

Bu yaklaşımın son bir varyasyonu muhtemelen IDENTITYsütunları değiştirmek ve bunun yerine Dizileri kullanmak olacaktır . Bu yaklaşımı benimsemenin nedeni, uygulama kodu değerlerini şu şekilde girebilmektir: pozitif, otomatik oluşturulan aralığın üstünde (aşağıda değil) ve gerek yoktur SET IDENTITY_INSERT ON / OFF.

Bu yaklaşımda, yapmanız gerekenler:

  • CREATE SEQUENCE kullanarak Dizi Oluşturma
  • IDENTITYSütunu, IDENTITYözelliği olmayan ancak NEXT VALUE FOR işlevini DEFAULTkullanarak bir Kısıtlama içeren yeni bir sütuna kopyalayın :

    PkId INT PRIMARY KEY CONSTRAINT [DF_TableName_NextID] DEFAULT (NEXT VALUE FOR...)

    Bu, her satıra 8 veya 12 yerine 0 bayt ekler.

  • Uygulama tarafından oluşturulan değerlerin başlangıç ​​aralığı, otomatik olarak oluşturulan değerlerin yaklaşacağını düşündüğünüz değerin çok üzerinde olacaktır.
  • Uygulama kodu INSERT'leri SET IDENTITY_INSERT {table_name} ON;ve SET IDENTITY_INSERT {table_name} OFF;ifadeleri sarın .

Ancak , ya kodlardan biri ile ya SCOPE_IDENTITY()da @@IDENTITYhala düzgün bir şekilde çalışması gerektiği için, Sekanslara geçiş şu anda bir seçenek değildir, çünkü Sekanslar için bu fonksiyonların eşdeğeri olmadığı anlaşılmaktadır :-(. Sad!


Cevabınız için çok teşekkürler. Burada dahili olarak tartışılan birkaç noktaya değiniyorsunuz. Ne yazık ki, bunlardan bazıları bizim için birkaç nedenden dolayı işe yaramayacak. Veritabanımız oldukça eski ve biraz kırılgan ve 2005 uyumluluk modunda çalışıyor, bu yüzden SEQUENCES çıktı. Uygulama veri aktarımımız, hizmet aracısı kuyruklarından yeni kayıtlar alan ve bunları birden çok iş parçacığı aracılığıyla iten bir veri yükleme aracıyla gerçekleşir. IDENTITY_INSERT, oturum başına yalnızca bir tablo için kullanılabilir ve şu anki düşünce, mimarimizin önemli bir değişiklik olmadan bunu sağlayamayacağıdır. Şimdi yumruk önerinizi test ediyorum.
Bay Moose

@MrMoose Evet, cevabımı sonunda Diziler hakkında daha fazla bilgi içerecek şekilde güncelledim. Yine de sizin durumunuzda işe yaramaz. Ve olası eşzamanlılık sorunlarını merak ediyordum IDENTITY_INSERT, ama test etmedim . Seçenek # 1'in genel sorununuzu çözeceğinden emin değilsiniz, sadece gereksiz karmaşıklığı azaltmak için bir gözlemdi. Yine de, yeni "harici" kimlikler ekleyen birden fazla iş parçanız varsa, bunların benzersiz olduklarını nasıl garanti edersiniz?
Solomon Rutzky

@MrMoose Aslında, " IDENTITY_INSERT oturum başına yalnızca bir tablo için kullanılabilir " ile ilgili olarak, burada sorun tam olarak nedir? 1) bir seferde sadece bir tabloya ekleyebilirsiniz, bu yüzden TableB'ye yerleştirmeden önce TableA için kapatırsınız ve 2) Sadece test ettim ve düşündüğümün aksine, eşzamanlılık sorunu yok - yapabildim sahip IDENTITY_INSERT ONiki oturumda aynı tablo için ve herhangi bir sorun ile hem sokmaya edildi.
Solomon Rutzky

1
Önerdiğiniz gibi, değişiklik 1 çok az fark yarattı. Kullanacağımız kimlik, geçerli veritabanının dışında tahsis edilecek ve kayıtları ilişkilendirmek için kullanılacaktır. Oturumları anlamam doğru olmayabilir, bu nedenle IDENTITY_INSERT işe yarayabilir. Bunu araştırmam biraz zaman alacaktır, bu yüzden kısa bir süre için rapor veremeyeceğim. Giriş için tekrar teşekkürler. Çok takdir edilmektedir.
Bay Moose

1
Bence IDENTITY_INSERT (mevcut uygulamalar için yüksek bir tohum değerine sahip) kullanma önerileriniz işe yarayacak. Aaron Bertrand burada eşzamanlılık testi ile ilgili iyi bir örnek verdi. Veri yükleme aracımızı, kimlik değerlerini belirtmesi gereken tabloları işleyebilecek şekilde değiştirdik ve önümüzdeki haftalarda başka testler yapacağız.
Bay Moose
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.