it-swarm.asia

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

Setelah bertanya ini pertanyaan membandingkan GUID berurutan dan non-berurutan, saya mencoba membandingkan kinerja INSERT pada 1) tabel dengan GUID kunci primer diinisialisasi secara berurutan dengan newsequentialid(), dan 2) sebuah tabel dengan kunci primer INT diinisialisasi secara berurutan dengan 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 sekuensial GUID.

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

PEMBARUAN: Memodifikasi skrip untuk melakukan penyisipan berdasarkan tabel TEMP, seperti dalam contoh oleh Phil Sandler, Mitch Wheat dan Martin di bawah, saya juga menemukan bahwa IDENTITAS lebih cepat dari yang seharusnya. Tapi itu bukan cara konvensional memasukkan baris, dan saya masih tidak mengerti mengapa percobaan 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 dengan menyiapkan baris untuk dimasukkan dalam tabel sementara dan melakukan banyak penyisipan sebagai batch-insert menggunakan temp table ini. Secara keseluruhan, saya tidak berpikir kami telah menemukan penjelasan untuk fenomena tersebut, dan IDENTITY () tampaknya masih lebih lambat untuk sebagian besar penggunaan praktis. Adakah yang bisa menjelaskan ini?

39
someName

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

19
Mitch Wheat

Pada basis data 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 individu 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 nilai Id yang bertambah tetapi ini dihitung sendiri dengan menambahkan 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 proses pembuatan identity yang bertanggung jawab atas hasilnya. Untuk bilangan bulat peningkatan yang dihitung sendiri maka hasilnya jauh lebih sesuai dengan apa yang diharapkan dilihat ketika hanya mempertimbangkan biaya IO.

Ketika saya memasukkan kode sisipan yang dijelaskan di atas ke dalam prosedur tersimpan dan meninjau sys.dm_exec_procedure_stats Itu memberikan 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 tersebut total_worker_time Adalah 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 nilai IDENTITY lebih intensif CPU daripada yang menghasilkan NEWSEQUENTIALID() (Perbedaan antara 2 angka adalah 10231308 yang rata-rata sekitar 5μs per masukkan.) dan bahwa untuk definisi tabel ini, biaya CPU tetap ini cukup tinggi untuk lebih besar daripada pembacaan dan penulisan logis tambahan yang terjadi karena lebar kunci yang lebih besar. (NB: Itzik Ben Gan melakukannya pengujian serupa di sini dan menemukan penalti 2μs per sisipan)

Jadi mengapa IDENTITY lebih intensif CPU daripada UuidCreateSequential?

Saya percaya ini dijelaskan dalam artikel ini . Untuk setiap nilai kesepuluh identity 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 perbedaannya menghilang dengan masih mungkin sedikit bermanfaat bagi kasus GUID 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 yang saya gunakan untuk melakukan memasukkan multi baris menggunakan 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 semacam tambahan untuk (seharusnya berurutan!) GUIDs.

GUID Sort

Operasi semacam ini tidak diperlukan dari teks penjelasan dalam BOL .

Membuat GUID yang lebih besar daripada GUID yang sebelumnya dibuat 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 yang hilang bahwa SQL Server tidak mengenali bahwa output dari skalar komputasi akan sudah dipilah sebelumnya seperti yang tampaknya sudah dilakukan untuk kolom identity. ( Edit Saya melaporkan ini dan masalah sortir yang tidak perlu sekarang diperbaiki di Denali)

19
Martin Smith

Cukup sederhana: dengan GUID, lebih murah untuk menghasilkan nomor berikutnya dalam baris daripada untuk IDENTITY (Nilai saat ini dari GUID tidak harus disimpan, IDENTITY 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 M.R., ada beberapa keuntungan utama 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/

8
Thomas Kejser

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

Saya pikir bahkan jika pengujian 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
4
Phil Sandler

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

Pertama saya akan mengatakan bahwa Anda hanya mengukur sekali aspek kinerja tombol - INSERT kecepatan. 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 INSERT kecepatan antara GUID dan IDENTITY (int) sedikit lebih besar dengan GUID daripada dengan IDENTITY - mungkin +/- 10% di antara proses. Batch yang menggunakan IDENTITY bervariasi 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.

3
Yuck

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 -standar-guid

Satu hal yang saya tahu adalah 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 bahwa 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.

1
M.R.