Apakah kunci alami memberikan kinerja yang lebih tinggi atau lebih rendah dalam SQL Server daripada kunci integer pengganti?

25

Saya penggemar kunci pengganti. Ada risiko temuan saya bias bias.

Banyak pertanyaan yang pernah saya lihat di sini dan di http://stackoverflow.com menggunakan kunci alami alih-alih kunci pengganti berdasarkan IDENTITY()nilai.

Latar belakang saya dalam sistem komputer memberi tahu saya melakukan operasi perbandingan pada bilangan bulat akan lebih cepat daripada membandingkan string.

Komentar ini membuat saya mempertanyakan keyakinan saya, jadi saya pikir saya akan membuat sistem untuk menyelidiki tesis saya bahwa bilangan bulat lebih cepat daripada string untuk digunakan sebagai kunci dalam SQL Server.

Karena ada kemungkinan perbedaan yang sangat kecil dalam kumpulan data kecil, saya langsung memikirkan pengaturan dua tabel di mana tabel primer memiliki 1.000.000 baris dan tabel sekunder memiliki 10 baris untuk setiap baris di tabel primer dengan total 10.000.000 baris di tabel sekunder. Premis pengujian saya adalah membuat dua set tabel seperti ini, satu menggunakan kunci alami dan satu menggunakan kunci integer, dan menjalankan tes pengaturan waktu pada permintaan sederhana seperti:

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

Berikut ini adalah kode yang saya buat sebagai test bed:

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

Kode di atas membuat database dan 4 tabel, dan mengisi tabel dengan data, siap untuk diuji. Kode tes yang saya jalankan adalah:

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;

Inilah hasilnya:

masukkan deskripsi gambar di sini

Apakah saya melakukan sesuatu yang salah di sini, atau apakah kunci INT 3 kali lebih cepat dari 25 karakter alami?

Catatan, saya telah menulis pertanyaan tindak lanjut di sini .

Max Vernon
sumber
1
Baik INT adalah 4 byte dan NVARCHAR (25) yang efektif adalah sekitar 14 kali lebih lama (termasuk data sistem seperti panjang), jadi dalam hal indeks saja saya percaya bahwa Anda akan memiliki indeks PK yang secara signifikan lebih luas dan lebih dalam dan oleh karena itu lebih banyak saya / O diperlukan yang akan memengaruhi waktu pemrosesan. Namun demikian, bilangan bulat alami (bahkan mungkin memeriksa digit) akan cukup banyak INT yang sama yang kami pikir menggunakan untuk kolom Identitas pengganti. Jadi, "kunci alami" mungkin INT, BIGINT, CHAR, NVARCHAR dan itu semua penting.
RLF
7
Saya pikir peningkatan kinerja @ MikeSherrill'Catcall 'adalah bahwa Anda tidak benar-benar perlu bergabung dengan tabel "lookup" ketika Anda menggunakan kunci alami. Bandingkan kueri untuk mendapatkan nilai pencarian dengan gabungan, dengan kueri di mana nilai sudah disimpan di tabel utama. Anda mungkin mendapatkan "pemenang" yang berbeda tergantung pada panjang kunci alami dan jumlah baris dalam tabel pencarian.
Mikael Eriksson
3
Apa yang dikatakan @MikaelEriksson ditambah kasus ketika Anda memiliki gabungan antara lebih dari 2 tabel (misalnya 4) di mana dengan pengganti Anda harus bergabung dengan tabel A ke D melalui B dan C sementara dengan kunci alami Anda dapat bergabung dengan A ke D secara langsung
ypercubeᵀᴹ

Jawaban:

18

Secara umum, SQL Server menggunakan B + Trees untuk indeks. Biaya pencarian indeks secara langsung berkaitan dengan panjang kunci dalam format penyimpanan ini. Oleh karena itu, kunci pengganti biasanya mengungguli kunci alami pada indeks yang dicari.

SQL Server mengelompokkan tabel pada kunci utama secara default. Kunci indeks berkerumun digunakan untuk mengidentifikasi baris, sehingga ditambahkan sebagai kolom yang disertakan ke setiap indeks lainnya. Semakin luas kunci itu, semakin besar setiap indeks sekunder.

Lebih buruk lagi, jika indeks sekunder tidak secara eksplisit didefinisikan sebagai UNIQUEkunci indeks yang dikelompokkan secara otomatis menjadi bagian dari kunci masing-masing. Itu biasanya berlaku untuk sebagian besar indeks, karena biasanya indeks dinyatakan sebagai unik hanya ketika persyaratannya adalah untuk menegakkan keunikan.

Jadi, jika pertanyaannya adalah, indeks kluster alami versus pengganti, pengganti hampir selalu menang.

Di sisi lain, Anda menambahkan kolom pengganti ke tabel yang membuat tabel itu sendiri lebih besar. Itu akan menyebabkan pemindaian indeks berkerumun menjadi lebih mahal. Jadi, jika Anda hanya memiliki sedikit indeks sekunder dan beban kerja Anda perlu melihat semua (atau sebagian besar) baris sering, Anda sebenarnya mungkin lebih baik dengan kunci alami menyimpan beberapa byte tambahan itu.

Akhirnya, kunci alami sering membuatnya lebih mudah untuk memahami model data. Sementara menggunakan lebih banyak ruang penyimpanan, kunci primer alami mengarah ke kunci asing alami yang pada gilirannya meningkatkan kepadatan informasi lokal.

Jadi, seperti yang sering terjadi di dunia basis data, jawaban sebenarnya adalah "itu tergantung". Dan - selalu uji di lingkungan Anda sendiri dengan data realistis.

Sebastian Meine
sumber
10

Saya percaya, bahwa yang terbaik terletak di tengah .

Ikhtisar kunci alami:

  1. Mereka membuat model data lebih jelas karena mereka berasal dari area subjek, dan bukan dari kepala seseorang.
  2. Kunci sederhana (satu kolom, antara CHAR(4)dan CHAR(20)) menyimpan beberapa byte tambahan, tetapi Anda harus memperhatikan konsistensi mereka ( ON UPDATE CASCADEmenjadi penting untuk kunci-kunci itu, yang mungkin diubah).
  3. Banyak kasus, ketika kunci alami kompleks: terdiri dari dua kolom atau lebih. Jika kunci tersebut dapat bermigrasi ke entitas lain sebagai kunci foreing, maka itu akan menambah overhead data (indeks dan kolom data mungkin menjadi besar) dan kinerja longgar.
  4. Jika kunci adalah string besar, maka mungkin selalu akan kehilangan kunci integer, karena kondisi pencarian sederhana menjadi perbandingan array byte dalam mesin database, yang dalam banyak kasus lebih lambat, daripada perbandingan integer.
  5. Jika kuncinya adalah string multi bahasa, maka perlu memperhatikan juga koleksinya.

Manfaat: 1 dan 2.

Watchouts: 3, 4 dan 5.


Ikhtisar kunci identitas buatan:

  1. Anda tidak perlu repot tentang pembuatan dan penanganannya (dalam kebanyakan kasus) karena fitur ini ditangani oleh mesin basis data. Mereka unik secara default dan tidak memakan banyak ruang. Operasi kustom seperti ON UPDATE CASCADEmungkin dihentikan, karena nilai kunci tidak berubah.

  2. Mereka (sering) adalah kandidat terbaik untuk migrasi sebagai kunci asing karena:

    2.1. terdiri dari satu kolom;

    2.2. menggunakan tipe sederhana yang memiliki bobot kecil dan bertindak cepat untuk operasi perbandingan.

  3. Untuk entitas asosiasi, kunci mana yang tidak dimigrasikan di mana saja, mungkin menjadi overhead data murni, karena kegunaannya hilang. Kunci primer alami yang kompleks (jika tidak ada kolom string di sana) akan lebih bermanfaat.

Manfaat: 1 dan 2.

Watchouts: 3.


KESIMPULAN:

Kunci Arificial lebih mudah dirawat, andal, dan cepat karena telah dirancang untuk fitur ini. Tetapi dalam beberapa kasus tidak diperlukan. Misalnya, CHAR(4)kandidat kolom tunggal dalam banyak kasus berperilaku seperti INT IDENTITY. Jadi ada pertanyaan lain di sini juga: rawatan + stabilitas atau kejelasan ?

Pertanyaan "Haruskah saya menyuntikkan kunci buatan atau tidak?" selalu tergantung pada struktur kunci alami:

  • Jika itu berisi string besar, maka itu lebih lambat dan akan menambahkan overhead data jika bermigrasi sebagai asing ke entitas lain.
  • Jika terdiri dari beberapa kolom, maka lebih lambat dan akan menambahkan overhead data jika bermigrasi sebagai asing ke entitas lain.
Membombardir
sumber
5
"Operasi khusus seperti ON UPDATE CASCADE mungkin dihentikan, karena nilai-nilai kunci tidak berubah." Efek dari kunci pengganti adalah membuat setiap referensi kunci asing setara dengan "ON UPDATE CASCADE". Kuncinya tidak berubah, tetapi nilai yang diwakilinya tidak .
Mike Sherrill 'Cat Recall'
@ MikeSherrill'Catcall 'Ya, tentu saja. Namun, ON UPDATE CASCADEtidak digunakan, sementara kunci tidak pernah diperbarui. Tapi, jika mereka, maka itu bisa menjadi masalah jika ON UPDATE NO ACTIONdikonfigurasi. Maksud saya, DBMS itu tidak pernah menggunakannya, sedangkan nilai kolom kunci tidak berubah.
BlitZ
4

Kunci adalah fitur logis dari database sedangkan kinerja selalu ditentukan oleh implementasi fisik dalam penyimpanan dan oleh operasi fisik yang berjalan terhadap implementasi itu. Oleh karena itu kesalahan atribut atribut kinerja untuk kunci.

Namun dalam contoh khusus ini, dua kemungkinan implementasi tabel dan kueri dibandingkan satu sama lain. Contoh tidak menjawab pertanyaan yang diajukan dalam judul di sini. Perbandingan yang dibuat adalah gabungan menggunakan dua tipe data yang berbeda (integer dan karakter) menggunakan hanya satu jenis indeks (B-tree). Poin "jelas" adalah bahwa jika indeks hash atau jenis indeks lainnya telah digunakan, tidak akan ada perbedaan kinerja yang dapat diukur antara kedua implementasi. Namun ada masalah yang lebih mendasar dengan contoh tersebut.

Dua kueri dibandingkan untuk kinerja tetapi dua kueri tidak setara secara logis karena menghasilkan hasil yang berbeda! Tes yang lebih realistis akan membandingkan dua pertanyaan yang mengembalikan hasil yang sama tetapi menggunakan implementasi yang berbeda.

Poin penting tentang kunci pengganti adalah bahwa itu adalah atribut tambahan dalam tabel di mana tabel tersebut juga memiliki atribut kunci "bermakna" yang digunakan dalam domain bisnis. Atribut non-pengganti yang menarik agar hasil kueri bermanfaat. Oleh karena itu tes realistis akan membandingkan tabel yang hanya menggunakan kunci alami dengan implementasi alternatif yang memiliki kunci alami dan pengganti dalam tabel yang sama. Kunci pengganti biasanya membutuhkan penyimpanan dan pengindeksan tambahan dan menurut definisi memerlukan kendala keunikan tambahan. Pengganti membutuhkan pemrosesan tambahan untuk memetakan nilai kunci alami eksternal ke penggantinya dan sebaliknya.

Sekarang bandingkan permintaan potensial ini:

SEBUAH.

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

Untuk setara logis jika atribut NaturalTable1Key di Table2 diganti dengan IDTable1Key pengganti:

B.

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

Kueri B membutuhkan gabungan; Kueri A tidak. Ini adalah situasi yang umum dalam database yang (lebih) menggunakan pengganti. Pertanyaan menjadi rumit tanpa perlu dan jauh lebih sulit untuk dioptimalkan. Logika bisnis (terutama kendala integritas data) menjadi lebih sulit untuk diterapkan, diuji, dan diverifikasi.

nvogel
sumber