Mengapa kunci GUID berurutan berkinerja lebih cepat daripada kunci INT berurutan dalam kasus pengujian saya?

39

Setelah mengajukan pertanyaan ini membandingkan GUID berurutan dan non-berurutan, saya mencoba membandingkan kinerja INSERT pada 1) tabel dengan kunci primer GUID yang diinisialisasi secara berurutan newsequentialid(), dan 2) tabel dengan kunci primer INT yang diinisialisasi secara berurutan identity(1,1). Saya berharap yang terakhir menjadi yang tercepat karena lebar integer yang lebih kecil, dan juga tampaknya lebih mudah untuk menghasilkan integer berurutan daripada GUID berurutan. Tapi yang mengejutkan saya, INSERT di atas meja dengan kunci integer secara signifikan lebih lambat daripada tabel GUID berurutan.

Ini menunjukkan penggunaan waktu rata-rata (ms) untuk pengujian berjalan:

NEWSEQUENTIALID()  1977
IDENTITY()         2223

Adakah yang bisa menjelaskan ini?

Eksperimen berikut digunakan:

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: Memodifikasi skrip untuk melakukan penyisipan berdasarkan tabel TEMP, seperti dalam contoh oleh Phil Sandler, Mitch Wheat dan Martin di bawah ini, saya juga menemukan bahwa IDENTITAS lebih cepat sebagaimana mestinya. Tapi itu bukan cara konvensional memasukkan baris, dan saya masih tidak mengerti mengapa percobaan itu salah pada awalnya: bahkan jika saya menghilangkan GETDATE () dari contoh asli saya, IDENTITY () masih jauh lebih lambat. Jadi sepertinya satu-satunya cara untuk membuat IDENTITY () mengungguli NEWSEQUENTIALID () adalah menyiapkan baris untuk dimasukkan dalam tabel sementara dan melakukan banyak penyisipan sebagai penyisipan batch menggunakan tabel temp ini. Secara keseluruhan, saya tidak berpikir kami telah menemukan penjelasan tentang fenomena tersebut, dan IDENTITY () tampaknya masih lebih lambat untuk sebagian besar penggunaan praktis. Adakah yang bisa menjelaskan ini?

someName
sumber
4
Pikiran saja: Mungkinkah menghasilkan GUID baru dapat dilakukan tanpa melibatkan meja sama sekali, sedangkan mendapatkan nilai identitas yang tersedia berikutnya memperkenalkan semacam kunci sementara untuk memastikan dua utas / koneksi tidak akan mendapatkan nilai yang sama? Saya hanya menebak-nebak. Pertanyaan menarik!
orang yang marah
4
Siapa bilang mereka lakukan ?? Ada banyak bukti yang tidak mereka miliki - lihat ruang Disk Kimberly Tripp murah - bukan itu intinya! posting blog - dia mengerjakan review yang cukup luas, dan GUID selalu kalah dengan jelasINT IDENTITY
marc_s
2
Nah, percobaan di atas menunjukkan yang sebaliknya, dan hasilnya berulang.
someName
2
Menggunakan IDENTITYtidak memerlukan kunci tabel. Secara konseptual saya bisa melihat Anda mungkin mengharapkannya mengambil MAX (id) +1, tetapi pada kenyataannya nilai selanjutnya disimpan. Ini sebenarnya harus lebih cepat daripada menemukan GUID berikutnya.
4
Juga, mungkin kolom pengisi untuk tabel TestGuid2 harus CHAR (88) untuk membuat baris ukuran yang sama
Mitch Wheat

Jawaban:

19

Saya memodifikasi kode @Phil Sandler untuk menghapus efek memanggil GETDATE () (mungkin ada efek perangkat keras / interupsi yang terlibat ??), dan membuat baris dengan panjang yang sama.

[Sudah ada beberapa artikel sejak SQL Server 2000 yang berkaitan dengan masalah waktu dan timer resolusi tinggi, jadi saya ingin meminimalkan efek itu.]

Dalam model pemulihan sederhana dengan data dan file log yang berukuran sama dengan apa yang diperlukan, berikut adalah timing (dalam detik): (Diperbarui dengan hasil baru berdasarkan kode persis di bawah ini)

       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

Kode yang digunakan:

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

Setelah membaca investigasi @ Martin, saya menjalankan kembali dengan TOP yang disarankan (@num) dalam kedua kasus, yaitu

...
--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()
...

dan inilah hasil waktunya:

       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

Saya tidak bisa mendapatkan rencana eksekusi yang sebenarnya, karena permintaan tidak pernah kembali! Tampaknya ada bug. (Menjalankan Microsoft SQL Server 2008 R2 (RTM) - 10.50.1600.1 (X64))

Mitch Wheat
sumber
7
Dengan rapi menggambarkan elemen penting dari pembandingan yang baik: Pastikan Anda hanya mengukur satu hal pada satu waktu.
Aaronaught
Apa rencanamu di sini? Apakah ada SORToperator untuk GUID?
Martin Smith
@ Martin: Hai, saya tidak memeriksa rencana (melakukan beberapa hal sekaligus :)). Saya akan melihat sedikit nanti ...
Mitch Wheat
@Itch - Ada tanggapan tentang ini? Saya agak curiga hal utama yang Anda ukur di sini adalah waktu yang dibutuhkan untuk menyortir pedoman untuk sisipan besar yang sementara menarik tidak menjawab pertanyaan awal OP yang tentang memberikan penjelasan tentang mengapa urutan berkinerja lebih baik daripada kolom identitas pada satu sisipan baris dalam pengujian OP.
Martin Smith
2
@Itch - Meskipun semakin saya memikirkannya, semakin sedikit saya mengerti mengapa ada orang yang mau menggunakannya NEWSEQUENTIALID. Ini akan membuat indeks lebih dalam, menggunakan halaman data 20% lebih banyak dalam kasus OP dan hanya dijamin akan semakin meningkat sampai mesin di-reboot sehingga memiliki banyak kelemahan lebih dari satu identity. Tampaknya dalam kasus ini bahwa Rencana Kueri menambahkan yang tidak perlu lebih lanjut!
Martin Smith
19

Pada database baru dalam model pemulihan sederhana dengan file data berukuran 1GB dan file log pada 3GB (mesin laptop, kedua file pada drive yang sama) dan interval pemulihan diatur ke 100 menit (untuk menghindari pos pemeriksaan yang memiringkan hasil) Saya melihat hasil yang mirip dengan Anda dengan satu baris inserts.

Saya menguji tiga kasus: Untuk setiap kasus saya melakukan 20 batch memasukkan 100.000 baris secara individual ke dalam tabel berikut. Skrip lengkap dapat ditemukan di riwayat revisi jawaban ini .

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)
  )  

Untuk tabel ketiga tes memasukkan baris dengan Idnilai tambah tetapi ini dihitung sendiri dengan menambah nilai variabel dalam satu lingkaran.

Rata-rata waktu yang diambil di 20 batch memberi hasil berikut.

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

Kesimpulan

Jadi sepertinya merupakan identityproses penciptaan yang bertanggung jawab atas hasilnya. Untuk bilangan bulat peningkatan yang dihitung sendiri maka hasilnya jauh lebih sesuai dengan apa yang diharapkan untuk dilihat ketika hanya mempertimbangkan biaya IO.

Ketika saya memasukkan kode sisipan yang dijelaskan di atas ke dalam prosedur tersimpan dan mengulasnya sys.dm_exec_procedure_statsmemberikan hasil sebagai berikut

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

Jadi dalam hasil total_worker_timetersebut sekitar 30% lebih tinggi. Ini mewakili

Jumlah total waktu CPU, dalam mikrodetik, yang dikonsumsi oleh eksekusi prosedur yang tersimpan ini sejak dikompilasi.

Jadi hanya muncul seolah-olah kode yang menghasilkan IDENTITYnilai lebih banyak CPU intensif daripada yang menghasilkan NEWSEQUENTIALID()(Perbedaan antara 2 angka adalah 10231308 yang rata-rata keluar sekitar 5μs per sisipan.) Dan untuk definisi tabel ini biaya CPU tetap ini cukup tinggi untuk melebihi pembacaan dan penulisan logis tambahan yang terjadi karena lebarnya kunci. (NB: Itzik Ben Gan melakukan pengujian serupa di sini dan menemukan penalti 2μs per sisipan)

Jadi mengapa IDENTITYCPU lebih intensif daripada UuidCreateSequential?

Saya yakin ini dijelaskan dalam artikel ini . Untuk setiap identitynilai kesepuluh yang dihasilkan, SQL Server harus menulis perubahan ke tabel sistem pada disk

Bagaimana dengan MultiRow Sisipan?

Ketika 100.000 baris dimasukkan dalam satu pernyataan, saya menemukan perbedaan menghilang dengan mungkin masih sedikit manfaat untuk GUIDkasus ini tetapi tidak mendekati hasil pemotongan yang jelas. Rata-rata untuk 20 batch dalam pengujian saya adalah

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

Alasan bahwa itu tidak memiliki penalti jelas dalam kode Phil dan hasil set pertama Mitch adalah karena kebetulan bahwa kode saya digunakan untuk melakukan memasukkan multi baris yang digunakan SELECT TOP (@NumRows). Ini mencegah pengoptimal memperkirakan jumlah baris yang akan dimasukkan dengan benar.

Ini tampaknya bermanfaat karena ada titik kritis tertentu di mana ia akan menambahkan operasi pengurutan tambahan untuk (seharusnya berurutan!) GUIDS.

Sortir GUID

Operasi semacam ini tidak diperlukan dari teks penjelasan dalam BOL .

Membuat GUID yang lebih besar dari GUID apa pun yang sebelumnya dihasilkan oleh fungsi ini pada komputer tertentu sejak Windows dimulai. Setelah memulai ulang Windows, GUID dapat memulai lagi dari kisaran yang lebih rendah, tetapi masih unik secara global.

Jadi sepertinya saya bug atau optimasi hilang bahwa SQL Server tidak mengenali bahwa output dari skalar komputasi akan sudah dipilah sebelumnya seperti yang tampaknya sudah dilakukan untuk identitykolom. ( Sunting Saya melaporkan ini dan masalah sortir yang tidak perlu sekarang diperbaiki di Denali )

Martin Smith
sumber
Bukan berarti ia memiliki banyak dampak tetapi hanya demi kejelasan angka yang dikutip Denny, 20 nilai identitas yang di-cache, tidak benar - seharusnya 10.
Aaron Bertrand
@AaronBertrand - Terima kasih. Artikel yang Anda tautkan paling informatif.
Martin Smith
8

Cukup sederhana: dengan GUID, lebih murah untuk menghasilkan nomor berikutnya dalam baris daripada untuk IDENTITAS (Nilai saat ini dari GUID tidak harus disimpan, IDENTITAS harus). Ini berlaku bahkan untuk NEWSEQUENTIALGUID.

Anda bisa membuat tes lebih adil dan menggunakan SEQUENCER dengan CACHE besar - yang lebih murah daripada IDENTITAS.

Tetapi seperti yang dikatakan MR, ada beberapa keuntungan besar bagi GUID. Faktanya, mereka JAUH lebih scalable daripada kolom IDENTITAS (tetapi hanya jika mereka TIDAK berurutan).

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

Thomas Kejser
sumber
Saya pikir Anda tidak mengetahui bahwa mereka menggunakan panduan berurutan.
Martin Smith
Martin: argumennya juga berlaku untuk GUID berurutan. IDENTITY harus disimpan (untuk kembali ke nilai lama setelah restart), GUID berurutan tidak memiliki batasan ini.
Thomas Kejser
2
Ya menyadari setelah komentar saya, Anda berbicara tentang menyimpan terus-menerus daripada menyimpan dalam memori. 2012 memang menggunakan cache IDENTITYjuga. karenanya keluhan di sini
Martin Smith
4

Saya terpesona dengan jenis pertanyaan ini. Mengapa Anda harus mempostingnya pada Jumat malam? :)

Saya pikir bahkan jika tes Anda HANYA dimaksudkan untuk mengukur kinerja INSERT, Anda (mungkin) telah memperkenalkan sejumlah faktor yang dapat menyesatkan (pengulangan, transaksi jangka panjang, dll.)

Saya tidak sepenuhnya yakin versi saya membuktikan apa pun, tetapi identitas memang berkinerja lebih baik daripada GUID di dalamnya (3,2 detik vs 6,8 detik pada PC di rumah):

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
Phil Sandler
sumber
Faktor lain yang tidak ada yang disebutkan adalah model pemulihan basis data, dan pertumbuhan file log ...
Mitch Wheat
@ Nyalakan pada basis data baru dalam model pemulihan sederhana dengan data dan file log baik ukuran cara apa yang diperlukan saya mendapatkan hasil yang mirip dengan OP.
Martin Smith
Saya baru saja mendapatkan timing 2,560 detik untuk Identity, dan 3,666 detik untuk Guid (dalam model pemulihan sederhana dengan data dan file log keduanya berukuran jauh di atas apa yang diperlukan)
Mitch Wheat
@Itch - Pada kode OP dengan semuanya dalam transaksi yang sama atau pada kode Phil?
Martin Smith
pada kode poster ini, itu sebabnya saya berkomentar di sini. Saya juga memposting kode yang saya gunakan ...
Mitch Wheat
3

Saya menjalankan skrip sampel Anda beberapa kali membuat beberapa penyesuaian untuk menghitung jumlah dan ukuran (dan terima kasih banyak telah menyediakannya).

Pertama saya akan mengatakan bahwa Anda hanya mengukur sekali aspek kinerja tombol - INSERTkecepatan. Jadi kecuali Anda secara khusus hanya peduli dengan memasukkan data ke dalam tabel secepat mungkin, ada lebih banyak lagi untuk hewan ini.

Temuan saya secara umum mirip dengan Anda. Namun, saya akan menyebutkan bahwa varians dalam INSERTkecepatan antara GUIDdan IDENTITY(int) sedikit lebih besar dengan GUIDdibandingkan dengan IDENTITY- mungkin +/- 10% antara berjalan. Batch yang digunakan IDENTITYbervariasi kurang dari 2 - 3% setiap kali.

Juga untuk dicatat, kotak pengujian saya jelas kurang kuat dari milik Anda sehingga saya harus menggunakan jumlah baris yang lebih kecil.

Yuck
sumber
Ketika PK adalah GUID, mungkinkah mesin tidak menggunakan indeks tetapi algoritma hashing untuk menentukan lokasi fisik dari catatan yang sesuai? Menyisipkan ke dalam tabel jarang dengan kunci primer hash selalu lebih cepat daripada menyisipkan ke dalam tabel dengan indeks pada kunci primer karena tidak adanya overhead indeks. Itu hanya pertanyaan - jangan pilih saya jika jawabannya tidak. Berikan saja tautan ke otoritas.
1

Saya akan merujuk kembali ke konv lain di stackoverflow untuk topik yang sama ini - https://stackoverflow.com/questions/170346/what-are-the-performance-improvement-of- berikutnyaential - guid - over - standard - guid

Satu hal yang saya tahu adalah bahwa memiliki GUID berurutan adalah bahwa penggunaan indeks lebih baik karena pergerakan daun yang sangat sedikit, dan karenanya mengurangi pencarian HD. Saya akan berpikir karena ini, sisipan akan lebih cepat juga, karena tidak harus mendistribusikan kunci ke sejumlah besar halaman.

Pengalaman pribadi saya adalah ketika Anda menerapkan DB lalu lintas tinggi yang besar, lebih baik menggunakan GUID, karena itu membuatnya jauh lebih skalabel untuk integrasi dengan sistem lain. Itu berlaku untuk replikasi, khususnya, dan batas int / bigint .... bukan berarti Anda akan kehabisan bigints, tetapi akhirnya Anda akan, dan siklus kembali.

BAPAK
sumber
1
Anda tidak kehabisan BIGINTs, tidak pernah ... Lihat ini: sqlmag.com/blog/it-possible-run-out-bigint-values
Thomas Kejser