คีย์ธรรมชาติให้ประสิทธิภาพที่สูงขึ้นหรือต่ำลงใน SQL Server มากกว่าปุ่มจำนวนเต็มตัวแทน?


25

ฉันเป็นแฟนของกุญแจตัวแทน มีความเสี่ยงที่การค้นพบของฉันจะยืนยันลำเอียง

คำถามมากมายที่ฉันเห็นทั้งที่นี่และที่http://stackoverflow.comใช้คีย์ธรรมชาติแทนคีย์ตัวแทนโดยยึดตามIDENTITY()ค่า

พื้นหลังของฉันในระบบคอมพิวเตอร์บอกให้ฉันดำเนินการเปรียบเทียบใด ๆ กับจำนวนเต็มจะเร็วกว่าการเปรียบเทียบสตริง

ความคิดเห็นนี้ทำให้ฉันถามความเชื่อของฉันดังนั้นฉันคิดว่าฉันจะสร้างระบบเพื่อตรวจสอบวิทยานิพนธ์ของฉันว่าจำนวนเต็มเร็วกว่าสตริงเพื่อใช้เป็นกุญแจใน SQL Server

เนื่องจากมีความแตกต่างที่สังเกตได้น้อยมากในชุดข้อมูลขนาดเล็กฉันจึงนึกถึงการตั้งค่าตารางสองตารางทันทีที่ตารางหลักมี 1,000,000 แถวและตารางรองมี 10 แถวสำหรับแต่ละแถวในตารางหลักรวม 10,000,000 แถวใน ตารางที่สอง ข้อสมมติฐานของการทดสอบของฉันคือการสร้างตารางสองชุดเช่นนี้ชุดหนึ่งใช้คีย์ธรรมชาติและอีกชุดหนึ่งใช้คีย์จำนวนเต็มและเรียกใช้การทดสอบกำหนดเวลากับคำถามง่ายๆเช่น:

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

ต่อไปนี้เป็นรหัสที่ฉันสร้างเป็นเตียงทดสอบ:

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

รหัสข้างต้นสร้างฐานข้อมูลและ 4 ตารางและเติมตารางด้วยข้อมูลพร้อมที่จะทดสอบ รหัสทดสอบที่ฉันใช้คือ:

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;

นี่คือผลลัพธ์:

ป้อนคำอธิบายรูปภาพที่นี่

ฉันกำลังทำสิ่งผิดปกติที่นี่หรือปุ่ม INT เร็วกว่าแป้นธรรมชาติ 25 ตัวอักษร 3 เท่าหรือไม่

หมายเหตุผมเคยเขียนคำถามติดตามที่นี่


1
INT มีค่า 4 ไบต์และ NVARCHAR ที่มีประสิทธิภาพ (25) นั้นยาวกว่าประมาณ 14 เท่า (รวมถึงข้อมูลระบบเช่นความยาว) ดังนั้นในแง่ของดัชนีเพียงอย่างเดียวผมเชื่อว่าคุณจะมีดัชนี PK ที่กว้างกว่าและลึกกว่า / O เป็นสิ่งจำเป็นซึ่งจะมีผลต่อเวลาในการประมวล Howevev เป็นจำนวนเต็มตามธรรมชาติ (อาจจะตรวจสอบการขุด) จะเป็น INT แบบเดียวกับที่เราคิดว่าจะใช้สำหรับคอลัมน์ข้อมูลประจำตัวตัวแทน ดังนั้น "คีย์ธรรมชาติ" อาจเป็น INT, BIGINT, CHAR, NVARCHAR และนั่นเป็นเรื่องสำคัญ
RLF

7
ฉันคิดว่าประสิทธิภาพที่เพิ่มขึ้น @ MikeSherrill'Catcall 'กำลังมาถึงคือคุณไม่จำเป็นต้องเข้าร่วมกับตาราง "ค้นหา" จริง ๆ เมื่อคุณใช้คีย์ธรรมชาติ เปรียบเทียบแบบสอบถามเพื่อรับค่าการค้นหาด้วยการเข้าร่วมกับแบบสอบถามที่เก็บค่าไว้แล้วในตารางหลัก คุณอาจได้รับ "ผู้ชนะ" ที่แตกต่างกันขึ้นอยู่กับความยาวของคีย์ธรรมชาติและจำนวนแถวในตารางการค้นหา
Mikael Eriksson

3
สิ่งที่ @MikaelEriksson พูดพร้อมกับกรณีที่คุณมีการเข้าร่วมมากกว่า 2 ตาราง (พูด 4) ที่มีตัวแทนคุณจะต้องเข้าร่วมตาราง A ถึง D ถึง B และ C ในขณะที่มีคีย์ธรรมชาติคุณสามารถเข้าร่วม A ถึง D โดยตรง
ypercubeᵀᴹ

คำตอบ:


18

โดยทั่วไปแล้ว SQL Server ใช้B + Treesสำหรับดัชนี ค่าใช้จ่ายในการค้นหาดัชนีนั้นเกี่ยวข้องโดยตรงกับความยาวของคีย์ในรูปแบบที่จัดเก็บนี้ ดังนั้นคีย์ตัวแทนจึงมักจะดีกว่าคีย์ธรรมชาติในการค้นหาดัชนี

SQL Server จัดกลุ่มตารางบนคีย์หลักตามค่าเริ่มต้น คีย์ดัชนีคลัสเตอร์ถูกใช้เพื่อระบุแถวดังนั้นมันจึงถูกเพิ่มเป็นคอลัมน์ที่รวมอยู่ในดัชนีอื่น ๆ ยิ่งคีย์นั้นกว้างก็ยิ่งดัชนีทุติยภูมิยิ่งใหญ่

ยิ่งแย่กว่านั้นหากดัชนีรองไม่ได้ถูกกำหนดไว้อย่างชัดเจนเนื่องจากUNIQUEคีย์ดัชนีคลัสเตอร์จะกลายเป็นส่วนหนึ่งของคีย์ของแต่ละดัชนีโดยอัตโนมัติ ซึ่งมักจะใช้กับดัชนีส่วนใหญ่โดยปกติแล้วดัชนีจะถูกประกาศว่าไม่ซ้ำกันเฉพาะเมื่อมีข้อกำหนดเพื่อบังคับใช้เอกลักษณ์

ดังนั้นหากคำถามคือดัชนีกลุ่มคลัสเตอร์ธรรมชาติกับตัวแทนตัวแทนจะชนะเกือบทุกครั้ง

ในทางกลับกันคุณกำลังเพิ่มคอลัมน์ตัวแทนลงในตารางทำให้ตารางใหญ่ขึ้นในตัวเอง ซึ่งจะทำให้การสแกนดัชนีแบบคลัสเตอร์มีราคาแพงขึ้น ดังนั้นหากคุณมีดัชนีรองน้อยมากและภาระงานของคุณต้องการดูแถว (หรือส่วนใหญ่) บ่อยครั้งคุณอาจจะดีกว่าถ้าใช้คีย์ธรรมชาติที่บันทึกไบต์พิเศษเหล่านั้นไม่มากนัก

ในที่สุดปุ่มธรรมชาติมักทำให้เข้าใจรูปแบบข้อมูลได้ง่ายขึ้น ในขณะที่ใช้พื้นที่จัดเก็บเพิ่มเติมคีย์หลักธรรมชาตินำไปสู่คีย์ต่างประเทศตามธรรมชาติซึ่งจะเพิ่มความหนาแน่นของข้อมูลในท้องถิ่น

ดังนั้นบ่อยครั้งในโลกของฐานข้อมูลคำตอบที่แท้จริงคือ "มันขึ้นอยู่กับ" และ - ทดสอบในสภาพแวดล้อมของคุณเองด้วยข้อมูลจริงเสมอ


10

ผมเชื่อว่าโกหกที่ดีที่สุดอยู่ตรงกลาง

ภาพรวมคีย์ธรรมชาติ:

  1. พวกเขาทำให้แบบจำลองข้อมูลชัดเจนขึ้นเพราะมาจากสาขาวิชาไม่ใช่จากหัวของใครบางคน
  2. ปุ่มแบบง่าย (หนึ่งคอลัมน์ระหว่างCHAR(4)และCHAR(20)) กำลังบันทึกไบต์พิเศษบางอย่าง แต่คุณจำเป็นต้องดูความสอดคล้อง ( ON UPDATE CASCADEกลายเป็นสิ่งสำคัญสำหรับคีย์เหล่านั้นซึ่งอาจมีการเปลี่ยนแปลง)
  3. หลายกรณีเมื่อคีย์ธรรมชาติซับซ้อน: ประกอบด้วยสองคอลัมน์ขึ้นไป หากคีย์ดังกล่าวอาจโยกย้ายไปยังเอนทิตีอื่นเป็นคีย์ก่อนหน้านั้นจะเพิ่มข้อมูลค่าใช้จ่าย (ดัชนีและคอลัมน์ข้อมูลอาจมีขนาดใหญ่) และประสิทธิภาพการทำงานหลวม
  4. ถ้าคีย์เป็นสตริงที่มีขนาดใหญ่ก็อาจจะหลวมไปที่คีย์จำนวนเต็มเสมอเนื่องจากเงื่อนไขการค้นหาแบบง่ายจะเป็นการเปรียบเทียบอาร์เรย์ไบต์ในเอ็นจิ้นฐานข้อมูลซึ่งในกรณีส่วนใหญ่จะช้ากว่าการเปรียบเทียบจำนวนเต็ม
  5. หากคีย์เป็นสตริงหลายภาษาคุณต้องดูการเปรียบเทียบด้วย

ประโยชน์ที่ได้รับ: 1 และ 2

Watchouts: 3, 4 และ 5


ภาพรวมของคีย์เอกลักษณ์ประดิษฐ์:

  1. คุณไม่จำเป็นต้องกังวลเกี่ยวกับการสร้างและการจัดการ (ในกรณีส่วนใหญ่) เนื่องจากคุณสมบัตินี้จัดการโดยกลไกจัดการฐานข้อมูล โดยค่าเริ่มต้นจะไม่ซ้ำกันและไม่ใช้พื้นที่มาก การดำเนินการที่กำหนดเองเช่นON UPDATE CASCADEอาจเป็น ommited เนื่องจากค่าคีย์ไม่เปลี่ยนแปลง

  2. พวกเขา (มัก) เป็นตัวเลือกที่ดีที่สุดสำหรับการย้ายข้อมูลเป็นคีย์ต่างประเทศเนื่องจาก:

    2.1 ประกอบด้วยหนึ่งคอลัมน์

    2.2 ใช้ชนิดที่เรียบง่ายซึ่งมีน้ำหนักเบาและดำเนินการอย่างรวดเร็วสำหรับการเปรียบเทียบ

  3. สำหรับเอนทิตีที่เชื่อมโยงกันซึ่งคีย์ใดไม่ถูกย้ายไปที่ใดก็อาจกลายเป็นโอเวอร์เฮดของข้อมูลได้เนื่องจากสูญเสียประโยชน์ คีย์หลักธรรมชาติที่ซับซ้อน (หากไม่มีคอลัมน์สตริง) จะมีประโยชน์มากกว่า

ประโยชน์ที่ได้รับ: 1 และ 2

Watchouts: 3


สรุป:

แป้น Arificial นั้นสามารถบำรุงรักษาได้เชื่อถือได้และรวดเร็วกว่าเนื่องจากได้รับการออกแบบมาสำหรับคุณสมบัตินี้ แต่ในบางกรณีไม่จำเป็น ยกตัวอย่างเช่นเดียวผู้สมัครคอลัมน์ในส่วนพฤติกรรมกรณีเช่นCHAR(4) INT IDENTITYดังนั้นมีคำถามอื่นที่นี่ด้วย: การบำรุงรักษา + ความมั่นคงหรือความชัดเจน ?

คำถาม"ฉันควรใส่รหัสปลอมหรือไม่" ขึ้นอยู่กับโครงสร้างหลักตามธรรมชาติเสมอ:

  • หากมีสตริงขนาดใหญ่แสดงว่าช้าลงและจะเพิ่มข้อมูลค่าใช้จ่ายหากย้ายข้อมูลเป็นภาษาอื่นไปยังเอนทิตีอื่น
  • หากประกอบด้วยหลายคอลัมน์ก็จะช้าลงและจะเพิ่มข้อมูลค่าใช้จ่ายหากโยกย้ายเป็นต่างประเทศไปยังเอนทิตีอื่น

5
"การดำเนินการที่กำหนดเองเช่น ON CASCADE UPDATE อาจถูกใช้เนื่องจากค่าคีย์ไม่เปลี่ยนแปลง" ผลกระทบของคีย์ตัวแทนคือการทำให้ทุกคีย์ต่างประเทศอ้างอิงเทียบเท่ากับ "ON CASCADE UPDATE" ที่สำคัญไม่มีการเปลี่ยนแปลง แต่ค่าที่แสดงถึงการไม่
Mike Catrill 'Cat Recall'

@ MikeSherrill'Catcall 'ใช่แน่นอน อย่างไรก็ตามON UPDATE CASCADEไม่ได้ใช้ในขณะที่ไม่มีการอัปเดตคีย์ แต่ถ้าเป็นเช่นนั้นอาจเป็นปัญหาหากON UPDATE NO ACTIONมีการกำหนดค่า ฉันหมายถึงว่า DBMS ไม่เคยใช้มันในขณะที่ค่าคอลัมน์คีย์ไม่เปลี่ยนแปลง
BlitZ

4

กุญแจสำคัญคือคุณสมบัติเชิงตรรกะของฐานข้อมูลในขณะที่ประสิทธิภาพถูกกำหนดโดยการนำไปใช้งานจริงในที่เก็บข้อมูลและโดยการดำเนินการทางกายภาพนั้นเทียบกับการนำไปใช้นั้น ดังนั้นจึงเป็นข้อผิดพลาดในการระบุลักษณะการทำงานของคีย์

ในตัวอย่างนี้อย่างไรก็ตามการนำไปใช้งานที่เป็นไปได้สองอย่างของตารางและคิวรีจะถูกนำมาเปรียบเทียบกัน ตัวอย่างไม่ตอบคำถามที่ถูกวางในชื่อที่นี่ การเปรียบเทียบที่เกิดขึ้นเป็นการเชื่อมโดยใช้ประเภทข้อมูลสองแบบ (จำนวนเต็มและตัวอักษร) โดยใช้ดัชนีประเภทเดียว (B-tree) จุด "ชัดเจน" คือว่าถ้าใช้ดัชนีแฮชหรือดัชนีประเภทอื่น ๆ ก็อาจจะไม่มีความแตกต่างของประสิทธิภาพที่วัดได้ระหว่างการใช้งานทั้งสอง มีปัญหาพื้นฐานมากขึ้นกับตัวอย่างอย่างไรก็ตาม

มีการเปรียบเทียบแบบสอบถามสองรายการสำหรับประสิทธิภาพการทำงาน แต่แบบสอบถามทั้งสองนั้นไม่เท่ากันอย่างมีเหตุผลเนื่องจากส่งคืนผลลัพธ์ที่ต่างกัน การทดสอบที่เหมือนจริงมากขึ้นจะเปรียบเทียบข้อความค้นหาสองรายการที่ส่งคืนผลลัพธ์เดียวกันแต่ใช้การใช้งานที่แตกต่างกัน

จุดสำคัญเกี่ยวกับกุญแจตัวแทนคือมันเป็นคุณสมบัติพิเศษในตารางที่ตารางนั้นยังมีคุณลักษณะสำคัญ "ความหมาย" ที่ใช้ในโดเมนธุรกิจ มันเป็นคุณสมบัติที่ไม่ใช่ตัวแทนที่น่าสนใจสำหรับผลลัพธ์แบบสอบถามที่จะเป็นประโยชน์ ดังนั้นการทดสอบจริงจะเปรียบเทียบตารางโดยใช้คีย์ธรรมชาติเท่านั้นกับการใช้งานทางเลือกที่มีทั้งคีย์ธรรมชาติและคีย์ตัวแทนในตารางเดียวกัน โดยทั่วไปแล้วปุ่มตัวแทนจะต้องการที่เก็บข้อมูลและการจัดทำดัชนีเพิ่มเติมและตามคำจำกัดความต้องการข้อ จำกัด เพิ่มเติมที่ไม่ซ้ำ ตัวแทนต้องการการประมวลผลเพิ่มเติมเพื่อทำแผนที่ค่าคีย์ธรรมชาติภายนอกไปยังตัวแทนและในทางกลับกัน

ตอนนี้เปรียบเทียบแบบสอบถามที่เป็นไปได้นี้:

A.

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

เทียบเท่ากับโลจิคัลหากแอ็ตทริบิวต์ NaturalTable1Key ใน Table2 ถูกแทนที่ด้วย IDTable1Key ตัวแทน:

บี

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

ข้อความค้นหา B ต้องเข้าร่วม แบบสอบถาม A ไม่ นี่เป็นสถานการณ์ที่คุ้นเคยในฐานข้อมูลที่ (เกิน) ใช้ตัวแทนเสมือน คำค้นหาที่ซับซ้อนโดยไม่จำเป็นและยากที่จะปรับให้เหมาะสม ตรรกะทางธุรกิจ (โดยเฉพาะอย่างยิ่งข้อ จำกัด ด้านความสมบูรณ์ของข้อมูล) กลายเป็นเรื่องยากที่จะนำไปใช้ทดสอบและตรวจสอบ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.