it-swarm-eu.dev

Proč sekvenční GUID klíče fungují rychleji než sekvenční INT klávesy v mém testovacím případě?

Poté, co jsem se zeptal this otázku porovnávající sekvenční a nesekvenční GUID, pokusil jsem se porovnat výkon INSERT na 1) tabulce s GUID primární klíč inicializovaný postupně s newsequentialid() a 2) tabulka s primárním klíčem INT inicializovaná postupně pomocí identity(1,1). Očekával bych, že tento bude nejrychlejší kvůli menší šířce celých čísel, a také se zdá jednodušší vygenerovat sekvenční celé číslo než sekvenční GUID. K mému překvapení však INSERTs na stole s celočíselným klíčem byly výrazně pomalejší než sekvenční GUID tabulka).

To ukazuje průměrnou dobu využití (ms) pro zkušební běhy:

NEWSEQUENTIALID()  1977
IDENTITY()         2223

Může to někdo vysvětlit?

Byl použit následující experiment:

SET NOCOUNT ON

CREATE TABLE TestGuid2 (Id UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID() PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(100))

CREATE TABLE TestInt (Id Int NOT NULL identity(1,1) PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(100))

DECLARE @BatchCounter INT = 1
DECLARE @Numrows INT = 100000


WHILE (@BatchCounter <= 20)
BEGIN 
BEGIN TRAN

DECLARE @LocalCounter INT = 0

    WHILE (@LocalCounter <= @NumRows)
    BEGIN
    INSERT TestGuid2 (SomeDate,batchNumber) VALUES (GETDATE(),@BatchCounter)
    SET @LocalCounter +=1
    END

SET @LocalCounter = 0

    WHILE (@LocalCounter <= @NumRows)
    BEGIN
    INSERT TestInt (SomeDate,batchNumber) VALUES (GETDATE(),@BatchCounter)
    SET @LocalCounter +=1
    END

SET @BatchCounter +=1
COMMIT 
END

DBCC showcontig ('TestGuid2')  WITH tableresults
DBCC showcontig ('TestInt')  WITH tableresults

SELECT batchNumber,DATEDIFF(ms,MIN(SomeDate),MAX(SomeDate)) AS [NEWSEQUENTIALID()]
FROM TestGuid2
GROUP BY batchNumber

SELECT batchNumber,DATEDIFF(ms,MIN(SomeDate),MAX(SomeDate)) AS [IDENTITY()]
FROM TestInt
GROUP BY batchNumber

DROP TABLE TestGuid2
DROP TABLE TestInt

UPDATE: Úpravy skriptu tak, aby se prováděly vkládání na základě tabulky TEMP, stejně jako v příkladech od Phil Sandler, Mitch Wheat a Martin níže, také zjistíte, že IDENTITY je rychlejší, jak má být. Ale nejedná se o konvenční způsob vkládání řádků a stále nechápu, proč se pokus zpočátku pokazil: I když vynechám GETDATE () z mého původního příkladu, IDENTITY () je stále ještě pomalejší. Zdá se tedy, že jediným způsobem, jak IDENTITY () překonat NEWSEQUENTIALID (), je připravit řádky k vložení do dočasné tabulky a pomocí této dočasné tabulky provést mnoho vložení jako dávkové vložení. Celkově vzato si nemyslím, že jsme našli vysvětlení tohoto jevu, a IDENTITY () se stále zdá být pomalejší pro většinu praktických použití. Může to někdo vysvětlit?

39
someName

Upravil jsem kód @ Phila Sandlera, aby se odstranil účinek volání GETDATE () (mohou se vyskytovat hardwarové efekty/přerušení?) A řádky jsem vytvořil stejně dlouho.

[Od SQL Server 2000 existuje několik článků týkajících se časování a časovačů s vysokým rozlišením, takže jsem chtěl tento efekt minimalizovat.]

V jednoduchém modelu obnovy s daty a souborem protokolu, které jsou stejně velké, než je požadováno, jsou načasování (v sekundách): (Aktualizováno o nové výsledky na základě přesného kódu níže)

       Identity(s)  Guid(s)
       ---------    -----
       2.876        4.060    
       2.570        4.116    
       2.513        3.786   
       2.517        4.173    
       2.410        3.610    
       2.566        3.726
       2.376        3.740
       2.333        3.833
       2.416        3.700
       2.413        3.603
       2.910        4.126
       2.403        3.973
       2.423        3.653
    -----------------------
Avg    2.650        3.857
StdDev 0.227        0.204

Použitý kód:

SET NOCOUNT ON

CREATE TABLE TestGuid2 (Id UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID() PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(88))

CREATE TABLE TestInt (Id Int NOT NULL identity(1,1) PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(100))

DECLARE @Numrows INT = 1000000

CREATE TABLE #temp (Id int NOT NULL Identity(1,1) PRIMARY KEY, rowNum int, adate datetime)

DECLARE @LocalCounter INT = 0

--put rows into temp table
WHILE (@LocalCounter < @NumRows)
BEGIN
    INSERT INTO #temp(rowNum, adate) VALUES (@LocalCounter, GETDATE())
    SET @LocalCounter += 1
END

--Do inserts using GUIDs
DECLARE @GUIDTimeStart DateTime = GETDATE()
INSERT INTO TestGuid2 (SomeDate, batchNumber) 
SELECT adate, rowNum FROM #temp
DECLARE @GUIDTimeEnd  DateTime = GETDATE()

--Do inserts using IDENTITY
DECLARE @IdTimeStart DateTime = GETDATE()
INSERT INTO TestInt (SomeDate, batchNumber) 
SELECT adate, rowNum FROM #temp
DECLARE @IdTimeEnd DateTime = GETDATE()

SELECT DATEDIFF(ms, @IdTimeStart, @IdTimeEnd) AS IdTime, DATEDIFF(ms, @GUIDTimeStart, @GUIDTimeEnd) AS GuidTime

DROP TABLE TestGuid2
DROP TABLE TestInt
DROP TABLE #temp
GO

Po přečtení @ Martinova šetření jsem v obou případech znovu spustil navrhovanou TOP (@num), tj.

...
--Do inserts using GUIDs
DECLARE @num INT = 2147483647; 
DECLARE @GUIDTimeStart DATETIME = GETDATE(); 
INSERT INTO TestGuid2 (SomeDate, batchNumber) 
SELECT TOP(@num) adate, rowNum FROM #temp; 
DECLARE @GUIDTimeEnd DATETIME = GETDATE();

--Do inserts using IDENTITY
DECLARE @IdTimeStart DateTime = GETDATE()
INSERT INTO TestInt (SomeDate, batchNumber) 
SELECT TOP(@num) adate, rowNum FROM #temp;
DECLARE @IdTimeEnd DateTime = GETDATE()
...

a zde jsou výsledky načasování:

       Identity(s)  Guid(s)
       ---------    -----
       2.436        2.656
       2.940        2.716
       2.506        2.633
       2.380        2.643
       2.476        2.656
       2.846        2.670
       2.940        2.913
       2.453        2.653
       2.446        2.616
       2.986        2.683
       2.406        2.640
       2.460        2.650
       2.416        2.720

    -----------------------
Avg    2.426        2.688
StdDev 0.010        0.032

Nebyl jsem schopen získat skutečný plán provedení, protože dotaz se nikdy nevrátil! Zdá se, že je pravděpodobná chyba. (Běh Microsoft SQL Server 2008 R2 (RTM) - 10.50.1600.1 (X64))

19
Mitch Wheat

Na nové databázi v jednoduchém modelu obnovy s datovým souborem o velikosti 1 GB a souborem protokolu o 3 GB (přenosný počítač, oba soubory na stejné jednotce) a intervalem obnovy nastaveným na 100 minut (aby se zabránilo skreslení výsledků kontrolním bodem) podobné výsledky jako u jednoho řádku inserts.

Testoval jsem tři případy: Pro každý případ jsem udělal 20 šarží vložením 100 000 řádků jednotlivě do následujících tabulek. Úplné skripty najdete v historii revizí této odpovědi .

CREATE TABLE TestGuid
  (
     Id          UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID() PRIMARY KEY,
     SomeDate    DATETIME, batchNumber BIGINT, FILLER CHAR(100)
  )

CREATE TABLE TestId
  (
     Id          Int NOT NULL identity(1, 1) PRIMARY KEY,
     SomeDate    DATETIME, batchNumber BIGINT, FILLER CHAR(100)
  )

CREATE TABLE TestInt
  (
     Id          Int NOT NULL PRIMARY KEY,
     SomeDate    DATETIME, batchNumber BIGINT, FILLER  CHAR(100)
  )  

Pro třetí tabulku test vložil řádky s inkrementující hodnotou Id, ale toto bylo samo vypočteno zvýšením hodnoty proměnné ve smyčce.

Zprůměrování času během 20 šarží poskytlo následující výsledky.

NEWSEQUENTIALID() IDENTITY()  INT
----------------- ----------- -----------
1999              2633        1878

Závěr

Takže se rozhodně zdá, že to je režie procesu vytváření identity, který je zodpovědný za výsledky. Pro samostatně vypočítané inkrementující celé číslo jsou pak výsledky mnohem více v souladu s tím, co by se očekávalo, když uvidíme pouze náklady IO=).

Když vložím výše popsaný kód vložení do uložených procedur a zkontroluji sys.dm_exec_procedure_stats, Získá to následující výsledky

proc_name      execution_count      total_worker_time    last_worker_time     min_worker_time      max_worker_time      total_elapsed_time   last_elapsed_time    min_elapsed_time     max_elapsed_time     total_physical_reads last_physical_reads  min_physical_reads   max_physical_reads   total_logical_writes last_logical_writes  min_logical_writes   max_logical_writes   total_logical_reads  last_logical_reads   min_logical_reads    max_logical_reads
-------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- --------------------
IdentityInsert 20                   45060360             2231067              2094063              2645079              45119362             2234067              2094063              2660080              0                    0                    0                    0                    32505                1626                 1621                 1626                 6268917              315377               276833               315381
GuidInsert     20                   34829052             1742052              1696051              1833055              34900053             1744052              1698051              1838055              0                    0                    0                    0                    35408                1771                 1768                 1772                 6316837              316766               298386               316774

Takže v těchto výsledcích je total_worker_time Asi o 30% vyšší. To představuje

Celkové množství času procesoru v mikrosekundách, které bylo spotřebováno vykonáním této uložené procedury od jejího kompilace.

Takže to prostě vypadá, jako by kód, který generuje hodnotu IDENTITY, byl náročnější na CPU než ten, který generuje NEWSEQUENTIALID() (Rozdíl mezi dvěma čísly je 10231308, což je průměrně kolem 5 µs na insert.) a že pro tuto definici tabulky byly tyto fixní náklady na CPU dostatečně vysoké, aby vyvážily další logická čtení a zápisy vzniklé v důsledku větší šířky klíče. (Pozn .: Itzik Ben Gan udělal podobné testování zde a zjistil penalizaci 2 µs za vložku)

Proč je tedy IDENTITY náročnější na CPU než UuidCreateSequential?

Věřím, že je to vysvětleno v tomto článk . Pro každou generovanou desátou hodnotu identity musí SQL Server zapsat změnu do systémových tabulek na disku

A co MultiRow vložky?

Když je 100 000 řádků vloženo do jediného příkazu, zjistil jsem, že rozdíl zmizel s možným nepatrným přínosem pro případ GUID, ale nikde se nepodařilo dosáhnout jasných výsledků. Průměr pro 20 šarží v mém testu byl

NEWSEQUENTIALID() IDENTITY()
----------------- -----------
1016              1088

Důvod, proč to nemá sankci zjevnou v Philově kódu a Mitchově první sadě výsledků, je ten, že se stalo, že kód, který jsem použil pro vložení více řádků, používal SELECT TOP (@NumRows). To zabránilo optimalizátoru správně odhadnout počet řádků, které budou vloženy.

Zdá se, že je to výhodné, protože existuje určitý bod překlopení, ve kterém přidá další operaci řazení pro (údajně sekvenční!) GUIDs.

GUID Sort

Tato operace řazení není vyžadována od vysvětlující text v BOL .

Vytvoří GUID, které je větší než jakékoli GUID dříve generované touto funkcí na určeném počítači od spuštění systému Windows. Po restartování systému Windows GUID může začít znovu od nižšího rozsahu, ale je stále globálně jedinečný.

Zdálo se mi tedy, že chyba nebo chybějící optimalizace SQL Server neuznává, že výstup z výpočetního skaláru bude již předem roztříděn, jak to zřejmě již dělá pro sloupec identity. ( Upravit Nahlásil jsem to a zbytečný problém se řazení je nyní opraven v Denali)

19
Martin Smith

Zcela jednoduché: s GUID je levnější vygenerovat další číslo v řádku než pro IDENTITY (Aktuální hodnota GUID nemusí být uložena, IDENTITY musí být To platí i pro NEWSEQUENTIALGUID.

Mohli byste udělat test spravedlivějším a použít SEQUENCER s velkou CACHE - což je levnější než IDENTITY.

Jak však říká M.R., GUID mají některé hlavní výhody. Ve skutečnosti jsou MUCH více škálovatelné než sloupce IDENTITY (ale pouze pokud NEJSOU sekvenční).

Viz: http://blog.kejser.org/2011/10/05/boosting-insert-speed-by-generating-scalable-keys/

8
Thomas Kejser

Tento typ otázky mě fascinuje. Proč jsi to musel zveřejnit v pátek večer? :)

Myslím, že i když je váš test určen POUZE k měření výkonu INSERT, jste (možná) zavedli řadu faktorů, které by mohly být zavádějící (opakování, dlouhodobá transakce atd.)

Nejsem úplně přesvědčen, že moje verze něco dokazuje, ale identita má lepší výkon než GUID v něm (3,2 sekundy vs. 6,8 sekundy na domácím PC):

SET NOCOUNT ON

CREATE TABLE TestGuid2 (Id UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID() PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(100))

CREATE TABLE TestInt (Id Int NOT NULL identity(1,1) PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(100))

DECLARE @Numrows INT = 1000000

CREATE TABLE #temp (Id int NOT NULL Identity(1,1) PRIMARY KEY, rowNum int)

DECLARE @LocalCounter INT = 0

--put rows into temp table
WHILE (@LocalCounter < @NumRows)
BEGIN
    INSERT INTO #temp(rowNum) VALUES (@LocalCounter)
    SET @LocalCounter += 1
END

--Do inserts using GUIDs
DECLARE @GUIDTimeStart DateTime = GETDATE()
INSERT INTO TestGuid2 (SomeDate, batchNumber) 
SELECT GETDATE(), rowNum FROM #temp
DECLARE @GUIDTimeEnd  DateTime = GETDATE()

--Do inserts using IDENTITY
DECLARE @IdTimeStart DateTime = GETDATE()
INSERT INTO TestInt (SomeDate, batchNumber) 
SELECT GETDATE(), rowNum FROM #temp
DECLARE @IdTimeEnd DateTime = GETDATE()

SELECT DATEDIFF(ms, @IdTimeStart, @IdTimeEnd) AS IdTime
SELECT DATEDIFF(ms, @GUIDTimeStart, @GUIDTimeEnd) AS GuidTime

DROP TABLE TestGuid2
DROP TABLE TestInt
DROP TABLE #temp
4
Phil Sandler

Několikrát jsem spustil váš ukázkový skript a provedl několik vylepšení v počtu a velikosti šarží (a moc vám děkuji za poskytnutí).

Nejprve řeknu, že měříte pouze jednou stránku výkonu kláves - rychlost INSERT. Takže pokud se nezajímáte pouze o co nejrychlejší získání dat do tabulek, je pro toto zvíře mnohem více.

Moje zjištění byla obecně podobná těm vašim. Chtěl bych však zmínit, že rozptyl rychlosti INSERT mezi GUID a IDENTITY (int) je mírně větší s GUID než s IDENTITY - možná +/- 10% mezi běhy. Šarže, které používaly IDENTITY, se pokaždé lišily o méně než 2 - 3%.

Také je třeba poznamenat, že moje testovací box je zjevně méně výkonný než ten váš, takže jsem musel použít menší počet řádků.

3
Yuck

Budu se vracet zpět k další konverzaci na stackoverflow pro toto stejné téma - https://stackoverflow.com/questions/170346/what-are-the-performance-improvement-of-sequential-guid-over -standard-guid

Jedna věc, kterou vím, je, že mít sekvenční identifikátory GUID je, že použití indexu je lepší díky velmi malému pohybu listů, a proto snižuje vyhledávání HD. Z tohoto důvodu bych si myslel, že by i vložky byly rychlejší, protože to nemusí distribuovat klíče na velké množství stránek.

Moje osobní zkušenost je taková, že při implementaci velké databáze s vysokým provozem je lepší používat identifikátory GUID, protože je mnohem více škálovatelná pro integraci s jinými systémy. To platí pro replikaci, konkrétně a pro limity int/bigint .... ne že by vám došly velké body, ale nakonec budete a cyklicky zpět.

1
M.R.