Mengindeks GUID PK di SQL Server 2012

13

Pengembang saya telah menyiapkan aplikasi mereka untuk menggunakan GUID sebagai PK untuk hampir semua tabel mereka dan secara default SQL Server telah mengatur indeks berkerumun di PK ini.

Sistem ini relatif muda dan tabel terbesar kami hanya lebih dari satu juta baris, tetapi kami melihat pengindeksan kami dan ingin dapat skala dengan cepat karena mungkin diperlukan dalam waktu dekat.

Jadi, kecenderungan pertama saya adalah memindahkan indeks berkerumun ke bidang yang dibuat yang merupakan representasi bigint dari DateTime. Namun, satu-satunya cara saya dapat membuat CX unik adalah dengan memasukkan kolom GUID dalam CX ini tetapi pesan dengan dibuat terlebih dahulu.

Apakah ini akan membuat kunci pengelompokan terlalu luas dan akan meningkatkan kinerja untuk menulis? Membaca juga penting, tetapi menulis mungkin menjadi perhatian yang lebih besar saat ini.

njkroes
sumber
1
Bagaimana cara GUID dihasilkan? NEWID atau NEWSEQUENTIALID?
swasheck
6
Performa Guiding dan Insert Clustered seharusnya hanya dalam kalimat jika kata yang mendahului "performance" diperkecil
billinkc
2
Keluarkan pengembang tersebut untuk makan siang dan jelaskan kepada mereka bahwa jika mereka menggunakan NEWID () lagi sebagai kunci utama Anda akan menyalahkan kinerja yang buruk pada mereka. Mereka akan dengan cepat bertanya kepada Anda apa yang harus dilakukan untuk mencegah hal itu. Pada titik mana, Anda mengatakan menggunakan IDENTITY (1,1) sebagai gantinya. (mungkin sedikit penyederhanaan tetapi 9 kali dari 10 yang akan berhasil).
Max Vernon
3
Alasan kebencian kami pada panduan adalah bahwa mereka lebar (16 byte) dan ketika tidak dibuat dengan newsequentialidacak. Kunci yang dikelompokkan adalah yang terbaik saat sempit dan meningkat. GUID adalah kebalikannya: gemuk dan acak. Bayangkan sebuah rak buku hampir penuh buku. Dalam datang OED dan karena keacakan guids, itu menyisipkan di tengah rak. Untuk menjaga agar barang-barang tetap tertata, separuh buku yang tepat harus dimasukkan ke lokasi baru yang merupakan tugas intensif waktu. Itulah yang dilakukan GUID ke basis data Anda dan mematikan kinerja.
billinkc
7
Cara untuk memperbaiki masalah menggunakan pengidentifikasi unik adalah kembali ke papan gambar dan tidak menggunakan pengidentifikasi unik . Mereka tidak mengerikan jika sistemnya kecil, tetapi jika Anda memiliki setidaknya beberapa juta + baris tabel (atau tabel apa pun yang lebih besar dari itu), Anda akan dihancurkan menggunakan pengidentifikasi unik untuk kunci.
Jon Seigel

Jawaban:

20

Masalah utama dengan GUID, terutama yang tidak berurutan, adalah:

  • Ukuran kunci (16 byte vs 4 byte untuk INT): Ini berarti Anda menyimpan 4 kali jumlah data di kunci Anda bersama dengan ruang tambahan untuk indeks apa pun jika ini adalah indeks berkerumun Anda.
  • Fragmentasi indeks: Hampir tidak mungkin untuk mempertahankan kolom GUID non-sekuensial didefragmentasi karena sifat acak dari nilai-nilai kunci.

Jadi apa artinya ini bagi situasi Anda? Itu datang ke desain Anda. Jika sistem Anda hanya tentang menulis dan Anda tidak khawatir tentang pengambilan data, maka pendekatan yang diuraikan oleh Thomas K akurat. Namun, Anda harus ingat bahwa dengan mengejar strategi ini, Anda menciptakan banyak masalah potensial untuk membaca data itu dan menyimpannya. Seperti yang ditunjukkan Jon Seigel , Anda juga akan menempati lebih banyak ruang dan pada dasarnya memiliki daya ingat.

Pertanyaan utama seputar GUID adalah seberapa perlunya mereka. Pengembang menyukai mereka karena mereka memastikan keunikan global, tetapi jarang terjadi bahwa keunikan seperti ini diperlukan. Tetapi pertimbangkan bahwa jika jumlah nilai maksimum Anda kurang dari 2.147.483.647 (nilai maksimum bilangan bulat bertanda 4 byte), maka Anda mungkin tidak menggunakan tipe data yang sesuai untuk kunci Anda. Bahkan dengan menggunakan BIGINT (8 byte), nilai maksimal Anda adalah 9.223.372.036.854.775.807. Ini biasanya cukup untuk setiap basis data non-global (dan banyak yang global) jika Anda memerlukan beberapa nilai peningkatan otomatis untuk kunci unik.

Akhirnya, sejauh menggunakan heap versus indeks berkerumun, jika Anda murni menulis data heap akan paling efisien karena Anda meminimalkan overhead untuk memasukkan. Namun, tumpukan di SQL Server sangat tidak efisien untuk pengambilan data. Pengalaman saya adalah bahwa indeks berkerumun selalu diinginkan jika Anda memiliki kesempatan untuk mendeklarasikannya. Saya telah melihat penambahan indeks berkerumun ke tabel (4 miliar + catatan) meningkatkan kinerja pilih secara keseluruhan dengan faktor 6.

Informasi tambahan:

Mike Fal
sumber
13

Tidak ada yang salah dengan GUID sebagai kunci dan cluster dalam sistem OLTP (kecuali Anda memiliki BANYAK indeks di atas meja yang menderita karena peningkatan ukuran cluster). Faktanya, mereka jauh lebih dapat diukur daripada kolom IDENTITAS.

Ada kepercayaan luas bahwa GUID adalah masalah besar dalam SQL Server - sebagian besar, ini cukup salah. Faktanya, GUID dapat secara signifikan lebih terukur pada kotak dengan lebih dari sekitar 8 core:

Maaf, tetapi pengembang Anda benar. Khawatir tentang hal-hal lain sebelum Anda khawatir tentang GUID.

Oh, dan akhirnya: mengapa Anda ingin indeks cluster di tempat pertama? Jika masalah Anda adalah sistem OLTP dengan banyak indeks kecil, Anda cenderung lebih baik dengan tumpukan.

Sekarang mari kita pertimbangkan fragmentasi (yang akan diperkenalkan oleh GUID) terhadap bacaan Anda. Ada tiga masalah utama dengan fragmentasi:

  1. Halaman membagi disk biaya I / O
  2. Setengah halaman penuh tidak seefisien memori seperti halaman penuh
  3. Ini menyebabkan halaman disimpan tidak berurutan, yang membuat kemungkinan I / O berurutan lebih kecil

Karena kekhawatiran Anda dalam pertanyaan adalah tentang skalabilitas, yang dapat kami definisikan sebagai "Menambahkan lebih banyak perangkat keras membuat sistem berjalan lebih cepat" ini adalah masalah Anda yang paling kecil. Untuk mengatasi masing-masing secara bergantian

Iklan 1) Jika Anda ingin skala, maka Anda mampu membeli I / O. Bahkan SSD Samsung / Intel 512GB yang murah (dengan beberapa USD / GB) akan memberi Anda lebih dari 100K IOPS. Anda tidak akan memakannya dalam waktu dekat pada sistem 2 socket. Dan jika Anda harus mengalami itu, beli satu lagi dan Anda siap

Iklan 2) Jika Anda menghapus tabel Anda, Anda akan memiliki setengah halaman penuh. Dan bahkan jika Anda tidak melakukannya, memori itu murah dan untuk semua kecuali sistem OLTP terbesar - data panas harus sesuai di sana. Mencari untuk mengemas lebih banyak data ke halaman adalah sub-optimalisasi ketika Anda mencari skala.

Iklan 3) Tabel yang dibangun dari data halaman yang sering terpecah-pecah, melakukan I / O acak dengan kecepatan yang persis sama dengan tabel yang diisi secara berurutan

Sehubungan dengan bergabung, ada dua jenis bergabung utama yang Anda mungkin akan melihat dalam beban kerja seperti OLTP: Hash dan loop. Mari kita lihat satu per satu:

Hash join: Gabung hash mengasumsikan bahwa tabel kecil dipindai dan yang lebih besar biasanya dicari. Meja kecil sangat mungkin berada dalam memori, jadi I / O bukan urusan Anda di sini. Kami sudah menyentuh fakta bahwa mencari adalah biaya yang sama dalam indeks terfragmentasi seperti dalam indeks non terfragmentasi

Loop bergabung: Tabel luar akan dicari. Biaya yang sama

Anda juga mungkin memiliki banyak pemindaian tabel yang buruk terjadi - tetapi kemudian GUID sekali lagi bukan urusan Anda, pengindeksan yang tepat adalah.

Sekarang, Anda mungkin memiliki beberapa pemindaian rentang yang sah terjadi (terutama ketika bergabung dengan kunci asing) dan dalam kasus ini, data terfragmentasi kurang "dikemas" dibandingkan dengan data yang tidak terfragmentasi. Tapi mari kita pertimbangkan apa yang akan Anda lihat dalam data 3NF yang diindeks dengan baik:

  1. Gabung dari tabel yang memiliki referensi kunci asing ke kunci utama dari tabel yang dirujuk

  2. Sebaliknya

Iklan 1) Dalam hal ini, Anda akan mencari tunggal untuk kunci utama - bergabung dengan n ke 1. Fragmentasi atau tidak, biaya yang sama (satu pencarian)

Iklan 2) Dalam hal ini, Anda bergabung dengan kunci yang sama, tetapi dapat mengambil lebih dari satu baris (rentang pencarian). Gabung dalam hal ini adalah 1 ke n. Namun, tabel asing yang Anda cari, Anda mencari kunci SAMA, yang kemungkinan besar berada pada halaman yang sama dalam indeks terfragmentasi seperti pada yang tidak terfragmentasi.

Pertimbangkan kunci asing itu sejenak. Bahkan jika Anda memiliki sekuensial "sempurna" meletakkan kunci utama kami - apa pun yang menunjuk ke kunci itu akan tetap non berurutan.

Tentu saja, Anda mungkin menjalankan mesin virtual di beberapa SAN di beberapa bank yang harganya murah dan prosesnya tinggi. Maka semua saran ini akan hilang. Tetapi jika itu adalah dunia Anda, skalabilitas mungkin bukan yang Anda cari - Anda mencari kinerja dan kecepatan / biaya tinggi - yang merupakan dua hal yang berbeda.

Thomas Kejser
sumber
1
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Paul White 9
5

Thomas: beberapa poin Anda sepenuhnya masuk akal dan saya setuju dengan itu semua. Jika Anda menggunakan SSD, keseimbangan apa yang Anda optimalkan akan berubah. Acak vs sekuensial tidak diskusi yang sama dengan disk berputar.

Saya terutama setuju bahwa mengambil tampilan DB murni sangatlah salah. Membuat aplikasi Anda lambat dan tidak dapat ditingkatkan hanya untuk meningkatkan kinerja DB bisa sangat keliru.

Masalah besar dengan IDENTITY (atau urutan, atau apa pun yang dihasilkan dalam DB) adalah bahwa itu sangat lambat karena memerlukan perjalanan pulang-pergi ke DB untuk membuat kunci, dan ini secara otomatis membuat kemacetan di DB Anda, itu menegaskan bahwa aplikasi harus melakukan panggilan DB untuk mulai menggunakan kunci. Membuat GUID memecahkan masalah ini dengan menggunakan aplikasi untuk membuat kunci, dijamin unik secara global (menurut definisi), dan lapisan aplikasi dapat menggunakannya untuk meneruskan catatan sekitar SEBELUM melakukan perjalanan pulang-pergi DB.

Tapi saya cenderung menggunakan alternatif untuk GUID Pilihan pribadi saya untuk tipe data di sini adalah BIGINT unik secara global yang dihasilkan oleh aplikasi. Bagaimana cara seseorang melakukan ini? Pada contoh paling sepele, Anda menambahkan fungsi kecil, SANGAT ringan ke aplikasi Anda untuk hash GUID. Dengan asumsi fungsi hash Anda cepat dan relatif cepat (lihat CityHash dari Google untuk satu contoh: http://google-opensource.blogspot.in/2011/04/introducing-cityhash.html - pastikan Anda mendapatkan semua langkah kompilasi dengan benar, atau varian FNV1a dari http://tools.ietf.org/html/draft-eastlake-fnv-03 untuk kode sederhana) ini memberi Anda manfaat dari kedua aplikasi yang menghasilkan pengidentifikasi unik dan nilai kunci 64 bit yang bekerja lebih baik dengan CPU .

Ada beberapa cara lain untuk menghasilkan BIGINT, dan di kedua algo ini ada kemungkinan tabrakan hash - baca dan buat keputusan yang sadar.

Mark Stacey
sumber
2
Saya sarankan Anda mengedit jawaban Anda sebagai jawaban untuk pertanyaan OP dan bukan (seperti sekarang) sebagai jawaban untuk jawaban Thomas. Anda masih dapat menyoroti perbedaan antara Thomas (, MikeFal) dan saran Anda.
ypercubeᵀᴹ
2
Harap jawab jawaban Anda untuk pertanyaan itu. Jika tidak, kami akan menghapusnya untuk Anda.
JNK
2
Terima kasih atas komentarnya Mark. Ketika Anda mengedit jawaban Anda (yang menurut saya menyediakan konteks yang sangat baik) saya akan mengubah satu hal: IDENTITAS tidak memerlukan perjalanan pulang-pergi tambahan ke server jika Anda berhati-hati dengan INSERT. Anda selalu dapat mengembalikan SCOPE_IDENTITY () dalam kumpulan yang memanggil INSERT ..
Thomas Kejser
1
Mengenai "ini sangat lambat karena membutuhkan perjalanan pulang pergi ke DB untuk membuat kunci" - Anda dapat mengambil sebanyak yang Anda butuhkan dalam satu perjalanan pulang pergi.
AK
Mengenai "Anda dapat mengambil sebanyak yang Anda butuhkan dalam satu kali perjalanan" - Anda tidak dapat melakukan ini dengan kolom IDENTITAS atau metode lain di mana Anda pada dasarnya menggunakan DEFAULT pada tingkat basis data.
Avi Cherry