Doğal anahtarlar, SQL Server'da yedek tamsayı anahtarlarından daha yüksek veya daha düşük performans sağlıyor mu?


25

Ben taşıyıcı anahtarların hayranıyım. Bulgularımın onaylanmasının önyargılı olma riski vardır.

Hem burada hem de http://stackoverflow.com adresinde gördüğüm birçok soru , IDENTITY()değerleri temel alan yedek anahtarlar yerine doğal anahtarlar kullanıyor .

Bilgisayar sistemlerimdeki geçmişim, bir tamsayıdaki herhangi bir karşılaştırmalı işlemi gerçekleştirmemin dizeleri karşılaştırmaktan daha hızlı olacağını söylüyor.

Bu yorum benim inançlarımı sorgulamama neden oldu, bu yüzden tamsayıların SQL Server'da anahtar olarak kullanmak için dizelerden daha hızlı olduğu konusunda tezimi araştırmak için bir sistem oluşturacağımı düşündüm.

Küçük veri kümelerinde fark çok az fark olması muhtemel olduğundan, derhal iki tablo kurulumunu düşündüm, burada birincil tablo 1.000.000 satır ve ikincil tablo birincil satırdaki her satır için 10 satır toplam 10.000.000 satır ikincil tablo. Testimin öncülü, biri doğal anahtarlar ve diğeri tamsayı anahtarlar kullanarak iki tane tablo kümesi oluşturmak ve bir zamanlama testini aşağıdaki gibi basit bir sorguda çalıştırmaktır:

SELECT *
FROM Table1
    INNER JOIN Table2 ON Table1.Key = Table2.Key;

Test yatağı olarak oluşturduğum kod şudur:

USE Master;
IF (SELECT COUNT(database_id) FROM sys.databases d WHERE d.name = 'NaturalKeyTest') = 1
BEGIN
    ALTER DATABASE NaturalKeyTest SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
    DROP DATABASE NaturalKeyTest;
END
GO
CREATE DATABASE NaturalKeyTest 
    ON (NAME = 'NaturalKeyTest', FILENAME = 
        'C:\SQLServer\Data\NaturalKeyTest.mdf', SIZE=8GB, FILEGROWTH=1GB) 
    LOG ON (NAME='NaturalKeyTestLog', FILENAME = 
        'C:\SQLServer\Logs\NaturalKeyTest.mdf', SIZE=256MB, FILEGROWTH=128MB);
GO
ALTER DATABASE NaturalKeyTest SET RECOVERY SIMPLE;
GO
USE NaturalKeyTest;
GO
CREATE VIEW GetRand
AS 
    SELECT RAND() AS RandomNumber;
GO
CREATE FUNCTION RandomString
(
    @StringLength INT
)
RETURNS NVARCHAR(max)
AS
BEGIN
    DECLARE @cnt INT = 0
    DECLARE @str NVARCHAR(MAX) = '';
    DECLARE @RandomNum FLOAT = 0;
    WHILE @cnt < @StringLength
    BEGIN
        SELECT @RandomNum = RandomNumber
        FROM GetRand;
        SET @str = @str + CAST(CHAR((@RandomNum * 64.) + 32) AS NVARCHAR(MAX)); 
        SET @cnt = @cnt + 1;
    END
    RETURN @str;
END;
GO
CREATE TABLE NaturalTable1
(
    NaturalTable1Key NVARCHAR(255) NOT NULL 
        CONSTRAINT PK_NaturalTable1 PRIMARY KEY CLUSTERED 
    , Table1TestData NVARCHAR(255) NOT NULL 
);
CREATE TABLE NaturalTable2
(
    NaturalTable2Key NVARCHAR(255) NOT NULL 
        CONSTRAINT PK_NaturalTable2 PRIMARY KEY CLUSTERED 
    , NaturalTable1Key NVARCHAR(255) NOT NULL 
        CONSTRAINT FK_NaturalTable2_NaturalTable1Key 
        FOREIGN KEY REFERENCES dbo.NaturalTable1 (NaturalTable1Key) 
        ON DELETE CASCADE ON UPDATE CASCADE
    , Table2TestData NVARCHAR(255) NOT NULL  
);
GO

/* insert 1,000,000 rows into NaturalTable1 */
INSERT INTO NaturalTable1 (NaturalTable1Key, Table1TestData) 
    VALUES (dbo.RandomString(25), dbo.RandomString(100));
GO 1000000 

/* insert 10,000,000 rows into NaturalTable2 */
INSERT INTO NaturalTable2 (NaturalTable2Key, NaturalTable1Key, Table2TestData)
SELECT dbo.RandomString(25), T1.NaturalTable1Key, dbo.RandomString(100)
FROM NaturalTable1 T1
GO 10 

CREATE TABLE IDTable1
(
    IDTable1Key INT NOT NULL CONSTRAINT PK_IDTable1 
    PRIMARY KEY CLUSTERED IDENTITY(1,1)
    , Table1TestData NVARCHAR(255) NOT NULL 
    CONSTRAINT DF_IDTable1_TestData DEFAULT dbo.RandomString(100)
);
CREATE TABLE IDTable2
(
    IDTable2Key INT NOT NULL CONSTRAINT PK_IDTable2 
        PRIMARY KEY CLUSTERED IDENTITY(1,1)
    , IDTable1Key INT NOT NULL 
        CONSTRAINT FK_IDTable2_IDTable1Key FOREIGN KEY 
        REFERENCES dbo.IDTable1 (IDTable1Key) 
        ON DELETE CASCADE ON UPDATE CASCADE
    , Table2TestData NVARCHAR(255) NOT NULL 
        CONSTRAINT DF_IDTable2_TestData DEFAULT dbo.RandomString(100)
);
GO
INSERT INTO IDTable1 DEFAULT VALUES;
GO 1000000
INSERT INTO IDTable2 (IDTable1Key)
SELECT T1.IDTable1Key
FROM IDTable1 T1
GO 10

Yukarıdaki kod bir veritabanı ve 4 tablo oluşturur ve tabloları test etmeye hazır olan verilerle doldurur. Çalıştırdığım test kodu:

USE NaturalKeyTest;
GO
DECLARE @loops INT = 0;
DECLARE @MaxLoops INT = 10;
DECLARE @Results TABLE (
    FinishedAt DATETIME DEFAULT (GETDATE())
    , KeyType NVARCHAR(255)
    , ElapsedTime FLOAT
);
WHILE @loops < @MaxLoops
BEGIN
    DBCC FREEPROCCACHE;
    DBCC FREESESSIONCACHE;
    DBCC FREESYSTEMCACHE ('ALL');
    DBCC DROPCLEANBUFFERS;
    WAITFOR DELAY '00:00:05';
    DECLARE @start DATETIME = GETDATE();
    DECLARE @end DATETIME;
    DECLARE @count INT;
    SELECT @count = COUNT(*) 
    FROM dbo.NaturalTable1 T1
        INNER JOIN dbo.NaturalTable2 T2 ON T1.NaturalTable1Key = T2.NaturalTable1Key;
    SET @end = GETDATE();
    INSERT INTO @Results (KeyType, ElapsedTime)
    SELECT 'Natural PK' AS KeyType, CAST((@end - @start) AS FLOAT) AS ElapsedTime;

    DBCC FREEPROCCACHE;
    DBCC FREESESSIONCACHE;
    DBCC FREESYSTEMCACHE ('ALL');
    DBCC DROPCLEANBUFFERS;
    WAITFOR DELAY '00:00:05';
    SET @start = GETDATE();
    SELECT @count = COUNT(*) 
    FROM dbo.IDTable1 T1
        INNER JOIN dbo.IDTable2 T2 ON T1.IDTable1Key = T2.IDTable1Key;
    SET @end = GETDATE();
    INSERT INTO @Results (KeyType, ElapsedTime)
    SELECT 'IDENTITY() PK' AS KeyType, CAST((@end - @start) AS FLOAT) AS ElapsedTime;

    SET @loops = @loops + 1;
END
SELECT KeyType, FORMAT(CAST(AVG(ElapsedTime) AS DATETIME), 'HH:mm:ss.fff') AS AvgTime 
FROM @Results
GROUP BY KeyType;

Sonuçlar bunlar:

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

Burada yanlış bir şey mi yapıyorum, yoksa INT tuşları 25 karakterlik doğal tuşlardan 3 kat daha hızlı mı?

Not, buraya bir takip sorusu yazdım .


1
INT 4 bayttır ve etkili NVARCHAR (25) yaklaşık 14 kat daha uzundur (uzunluk gibi sistem verileri dahil), bu yüzden tek başına indeks açısından çok daha geniş ve daha derin bir PK indeksi olacağına inanıyorum. / O işlem süresini etkileyecek olan bir O gereklidir. Nasıl olsa doğal bir tamsayı (belki de rakamları işaretli kontrol edin), taşıyıcı bir Kimlik sütunu için kullanmayı düşündüğümüz INT ile hemen hemen aynı olurdu. Yani, "doğal anahtar" belki bir INT, BIGINT, CHAR, NVARCHAR ve hepsi önemlidir.
RLF,

7
Sanırım, MikeSherrill'Catcall'ın performans kazancı, doğal bir anahtar kullandığınızda aslında "arama" masasına katılmanıza gerek olmamasıdır. Arama değerini birleştirme ile almak için, değerin zaten ana tabloda depolandığı bir sorgu ile bir sorgu karşılaştırın. Arama anahtarındaki doğal anahtar uzunluğuna ve satır sayısına bağlı olarak farklı bir "kazanan" alabilirsiniz.
Mikael Eriksson

3
@MikaelEriksson ne dedi, ayrıca 2'den fazla masa arasında bir birleşme varken (örneğin 4), vekillerle birlikte A'dan D'ye ve A'dan D'ye kadar olan masalara katılmak zorunda kalacaksınız.
ypercubeᵀᴹ

Yanıtlar:


18

Genel olarak, SQL Server dizinler için B + Ağaçlarını kullanır . Bir endeks araştırmasının gideri doğrudan bu depolama formatındaki anahtarın uzunluğu ile ilgilidir. Bu nedenle, bir vekil anahtar genellikle endeks arayışlarında doğal bir anahtardan daha iyi performans gösterir.

SQL Server, birincil anahtardaki bir tabloyu varsayılan olarak kümeler. Kümelenmiş dizin anahtarı, satırları tanımlamak için kullanılır, bu nedenle diğer tüm dizinlere sütun olarak eklenir. Bu anahtar ne kadar genişse, her ikincil dizin o kadar büyük olur.

Daha da kötüsü, ikincil dizinler açıkça UNIQUEkümelenmiş dizin anahtarı olarak tanımlanmadıysa, otomatik olarak her birinin anahtarının bir parçası haline gelir. Bu genellikle çoğu endeks için geçerlidir, çünkü genellikle endeksler benzersizliği uygulamak olduğunda sadece endeksler benzersiz olarak ilan edilir.

Yani eğer soru doğalsa, taşıyıcı kümelenmiş endeksine karşı doğal ise, taşıyıcı hemen hemen her zaman kazanacaktır.

Öte yandan, bu vekil sütunu, masayı kendi içinde büyüten masanın üzerine ekliyorsunuz. Bu, kümelenmiş dizin taramalarının daha pahalı hale gelmesine neden olur. Bu nedenle, yalnızca çok az sayıda ikincil dizininiz varsa ve iş yükünüz sık sık (veya çoğu) satırlara bakmayı gerektiriyorsa, aslında bu birkaç ekstra baytı koruyan doğal bir anahtarla daha iyi olabilirsiniz.

Son olarak, doğal anahtarlar genellikle veri modelini anlamayı kolaylaştırır. Daha fazla depolama alanı kullanırken, doğal birincil anahtarlar, yerel bilgi yoğunluğunu artıran doğal yabancı anahtarlara yol açar.

Dolayısıyla, veritabanı dünyasında sıkça olduğu gibi, asıl cevap “buna bağlı” dır. Ve - her zaman kendi ortamınızda gerçekçi verilerle test edin.


10

En iyisinin ortasındaki yalan olduğuna inanıyorum .

Doğal anahtarlara genel bakış:

  1. Veri modelini daha açık kılarlar çünkü bir başından değil konu alanından geliyorlar.
  2. Basit tuşlar (bir sütun, aralarındaki CHAR(4)ve arasındaki CHAR(20)) bazı ekstra baytları kurtarıyor, ancak tutarlılıklarını izlemeniz gerekiyor ( ON UPDATE CASCADEdeğiştirilebilecek anahtarlar için kritik hale geliyor).
  3. Doğal anahtarların karmaşık olduğu birçok durum: iki veya daha fazla sütundan oluşur. Eğer böyle bir anahtar bir yabancı anahtar olarak başka bir işletmeye geçebilirse, ek yük verileri ekler (endeksler ve veri sütunları büyüyebilir) ve performans gevşeyecektir.
  4. Anahtar büyük bir dize ise, muhtemelen her zaman bir tamsayı anahtarına kaybeder, çünkü basit arama koşulu, çoğu durumda tamsayı karşılaştırmasından daha yavaş olan bir veritabanı motorunda bir bayt dizisi karşılaştırması yapar.
  5. Anahtar çok dilli bir dize ise, harmanlamaları da izlemeniz gerekir.

Yararları: 1 ve 2.

Watchouts: 3, 4 ve 5.


Yapay kimlik anahtarlarına genel bakış:

  1. Bu özellik veritabanı altyapısı tarafından işlendiğinden, bunların yaratılması ve kullanılması (çoğu durumda) için uğraşmanıza gerek yoktur. Varsayılan olarak benzersizdirler ve fazla yer kaplamazlar. Gibi özel işlemler ON UPDATE CASCADEatlanabilir, çünkü anahtar değerler değişmez.

  2. Bunlar (genellikle) yabancı anahtar olarak göç için en iyi adaylardır çünkü:

    2.1. bir sütundan oluşur;

    2.2. küçük bir ağırlığa sahip ve karşılaştırma işlemleri için hızlı davranan basit bir tür kullanarak.

  3. Anahtarların hiçbir yere taşınamadığı bir ilişkilendirme varlıkları için, yararlılığı kaybolduğundan, genel bir veri yükü haline gelebilir. Karmaşık doğal birincil anahtar (orada dize sütunları yoksa) daha yararlı olacaktır.

Yararları: 1 ve 2.

Watchouts: 3.


SONUÇ:

Orijinal anahtarlar bu özellikler için tasarlandıklarından daha bakımlı, güvenilir ve hızlıdır. Ancak bazı durumlarda gerekli değildir. Örneğin, CHAR(4)çoğu durumda tek sütun adayı gibi davranır INT IDENTITY. Öyleyse burada başka bir soru daha var: sürdürülebilirlik + istikrar veya açıklık ?

Soru "Meli Bir yapay anahtarla iğne yapmak ya da değil?" her zaman doğal anahtar yapısına bağlıdır :

  • Büyük bir dize içeriyorsa, daha yavaştır ve başka bir işletmeye yabancı olarak geçiyorsa ek yükü ekleyecektir.
  • Birden çok sütundan oluşuyorsa, daha yavaştır ve başka bir işletmeye yabancı olarak geçilmesi durumunda yükü ekler.

5
"ON UPDATE CASCADE gibi özel işlemler geçersiz sayılabilir, çünkü anahtar değerler değişmez." Vekil anahtarların etkisi, her yabancı anahtar referansını "ON UPDATE CASCADE" eşdeğeri yapmaktır. Anahtar değişmez, ancak temsil değeri yok .
Mike Sherrill 'Kedi Hatırlama'

@ MikeSherrill'Catcall 'Evet, elbette. Ancak, ON UPDATE CASCADEtuşlar hiç güncellenmemişken kullanılmaz. Ancak, eğer öyleyse, eğer ON UPDATE NO ACTIONyapılandırılmışsa bir sorun olabilir . Anahtar sütunu değerleri değişmezken, DBMS'yi asla kullanmayacak.
BlitZ

4

Anahtar, veritabanının mantıksal bir özelliğidir; performans, depolamada fiziksel uygulama tarafından ve bu uygulamaya karşı çalışan fiziksel işlemler tarafından her zaman belirlenir. Bu nedenle performans özelliklerini tuşlara atfetmek bir hatadır.

Ancak bu özel örnekte, iki olası tablo ve sorgu uygulaması birbiriyle karşılaştırılmıştır. Örnek, burada başlıkta sorulan soruya cevap vermiyor. Yapılan karşılaştırma, sadece bir indeks tipi (B-ağacı) kullanan iki farklı veri tipini (tamsayı ve karakter) kullanarak birleştirmedir. Bir "açık" nokta, bir karma endeks veya başka bir indeks tipi kullanıldığında, iki uygulama arasında ölçülebilir bir performans farkının bulunmayacağı muhtemelen muhtemeldir. Ancak bu örnekte daha temel problemler var.

İki sorgu performans açısından karşılaştırılıyor, ancak iki sorgu mantıksal olarak eşdeğer değil çünkü farklı sonuçlar elde ediyorlar! Daha gerçekçi bir test, aynı sonuçları döndüren ancak farklı uygulamalar kullanan iki sorguyu karşılaştıracaktır .

Bir vekil anahtar hakkındaki asıl nokta , tablonun işletme alanında kullanılan "anlamlı" anahtar özelliklere sahip olduğu bir tablodaki ekstra bir özellik olmasıdır. Sorgu sonuçlarının faydalı olması için ilgi çekici olmayan özelliklerdir. Gerçekçi bir test, bu nedenle, her ikisinin de doğal olan alternatif bir uygulamayla yalnızca doğal anahtarlar kullanarak tabloları karşılaştırabilir. ve aynı tabloda vekil anahtarları. Vekil anahtarlar tipik olarak ek depolama ve indeksleme gerektirir ve tanım gereği ek benzersiz kısıtlamalar gerektirir. Vekiller, dış doğal anahtar değerlerin vekillerine eşlenmesi için ek işlem yapılmasını gerektirir;

Şimdi bu potansiyel sorguyu karşılaştırın:

A.

SELECT t2.NaturalTable2Key, t2.NaturalTable1Key
FROM Table2 t2;

Table2'deki NaturalTable1Key özniteliği vekil IDTable1Key ile değiştirilirse, mantıksal eşdeğeri için:

B.

SELECT t2.NaturalTable2Key, t1.NaturalTable1Key
FROM Table2 t2
INNER JOIN Table1 t1
ON t1.IDTable1Key = t2.IDTable1Key;

B sorgusu bir birleştirme gerektirir; Sorgu A değil. Bu, (aşırı) suretler kullanan veritabanlarında bilinen bir durumdur. Sorguları gereksiz yere karmaşık hale getirmek ve optimize etmek çok daha zor hale gelir. İş mantığı (özellikle veri bütünlüğü kısıtlamaları) uygulanması, test edilmesi ve doğrulanması zorlaşır.

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.