Saya harus dapat menemukan elemen yang hilang dari tabel dengan puluhan-juta baris, dan memiliki kunci utama BINARY(64)
kolom (yang merupakan nilai input untuk menghitung dari). Nilai-nilai ini sebagian besar dimasukkan secara berurutan, tetapi kadang-kadang saya ingin menggunakan kembali nilai sebelumnya yang telah dihapus. Sangat tidak layak untuk memodifikasi catatan yang dihapus dengan IsDeleted
kolom, karena kadang-kadang sebuah baris disisipkan dengan jutaan nilai di depan baris yang ada saat ini. Ini berarti data sampel akan terlihat seperti:
KeyCol : BINARY(64)
0x..000000000001
0x..000000000002
0x..FFFFFFFFFFFF
Jadi, memasukkan semua nilai yang hilang di antara 0x000000000002
dan 0xFFFFFFFFFFFF
tidak layak, jumlah waktu dan ruang yang digunakan tidak diinginkan. Pada dasarnya, ketika saya menjalankan algoritme, saya berharap untuk kembali 0x000000000003
, yang merupakan pembukaan pertama.
Saya telah datang dengan algoritma pencarian biner dalam C #, yang akan query database untuk setiap nilai pada posisi i
, dan menguji apakah nilai itu diharapkan. Untuk konteks, algoritma mengerikan saya: /codereview/174498/binary-search-for-a-missing-or-default-value-by-a-given-formula
Algoritma ini akan berjalan, misalnya, 26-27 SQL-queries di atas meja dengan 100.000.000 item. (Kelihatannya tidak banyak, tapi itu akan terjadi sangat sering.) Saat ini, tabel ini memiliki sekitar 50.000.000 baris di dalamnya, dan kinerja menjadi nyata .
Pemikiran alternatif pertama saya adalah menerjemahkan ini ke prosedur tersimpan, tetapi itu memiliki rintangan sendiri. (Saya harus menulis BINARY(64) + BINARY(64)
algoritma, serta membunuh hal-hal lain.) Ini akan menyakitkan, tetapi tidak mustahil. Saya juga mempertimbangkan menerapkan algoritma terjemahan berdasarkan ROW_NUMBER
, tapi saya punya firasat yang sangat buruk tentang hal ini. (A BIGINT
hampir tidak cukup besar untuk nilai-nilai ini.)
Saya mendukung saran lain , karena saya benar - benar membutuhkan ini secepat mungkin. Untuk apa nilainya satu - satunya kolom yang dipilih oleh permintaan C # adalah KeyCol
, yang lain tidak relevan untuk bagian ini.
Selain itu, sesuai nilainya, kueri saat ini yang mengambil catatan yang sesuai ada di sepanjang baris:
SELECT [KeyCol]
FROM [Table]
ORDER BY [KeyCol] ASC
OFFSET <VALUE> ROWS FETCH FIRST 1 ROWS ONLY
Di mana <VALUE>
indeks dipasok oleh algoritma. Saya juga belum memiliki BIGINT
masalah OFFSET
, tetapi saya akan melakukannya. (Hanya memiliki 50.000.000 baris sekarang berarti bahwa itu tidak pernah meminta indeks di atas nilai itu, tetapi pada beberapa titik itu akan mendapatkan di atas BIGINT
kisaran.)
Beberapa data tambahan:
- Dari penghapusan,
gap:sequential
rasionya adalah tentang1:20
; - 35.000 baris terakhir dalam tabel memiliki nilai>
BIGINT
maksimum;
sumber
delete
pemicu di atas meja yang akan membuang biner yang sekarang tersedia ke meja terpisah (misalnya,create table available_for_reuse(id binary64)
), terutama mengingat persyaratan untuk melakukan pencarian ini sangat sering ?mynameisebrown
yang berarti Anda akan dapatkanmynameisebrowo
, yang Anda dapatkan tidak ingin jikaabc
tersedia.select t1.keycol+1 as aa from t as t1 where not exists (select 1 from t as t2 where t2.keycol = t1.keycol+1) order by keycol fetch first 1 rows only
diberikan oleh kueri ?SELECT TOP 1 ([T1].[KeyCol] + 1) AS [AA] FROM [SearchTestTableProper] AS [T1] WHERE NOT EXISTS (SELECT 1 FROM [SearchTestTableProper] AS [T2] WHERE [T2].[KeyCol] = [T1].[KeyCol] + 1) ORDER BY [KeyCol]
, yang selalu kembali1
.Jawaban:
Joe sudah mengenai sebagian besar poin yang saya hanya menghabiskan satu jam mengetik, dalam ringkasan:
KeyCol
nilai <bigint
maks (9.2e18), jadi konversi (jika perlu) ke / daribigint
seharusnya tidak menjadi masalah selama Anda membatasi pencarian hinggaKeyCol <= 0x00..007FFFFFFFFFFFFFFF
Jadi, apa yang harus dilakukan?
Mari kita tunggu sebentar gagasan pencarian (diulang, intensif cpu, brute force) dan lihat gambar yang lebih besar.
Yang ingin saya usulkan adalah beberapa tambahan pada model data ...
KeyCol
, misalnya:available_for_use(KeyCol binary(64) not null primary key)
KeyCol
nilai baru (mungkin membuat proc tersimpan 'top off'?) [misalnya, perbaruiselect/top/row_number()
kueri Joe untuk melakukantop 100000
]available_for_use
kalau-kalau Anda mulai kehabisan nilaiKeyCol
nilai yang dihapus ke dalam tabel baru kamiavailable_for_use
setiap kali sebuah baris dihapus dari tabel utamaKeyCol
kolom maka pemicu UPDATE yang baru / dimodifikasi pada> main_table <juga menjaga agar tabel baru kamiavailable_for_use
diperbaruiKeyCol
nilai baru Andaselect min(KeyCol) from available_for_use
(jelas ada sedikit lebih dari ini karena a) Anda harus kode untuk masalah konkurensi - tidak ingin 2 salinan dari proses Anda meraih hal yang samamin(KeyCol)
dan b) Anda Anda harus menghapusmin(KeyCol)
dari tabel; ini harus relatif mudah dikodekan, mungkin sebagai proc yang disimpan, dan dapat dialamatkan dalam tanya jawab lain jika perlu)select min(KeyCol)
proses Anda tidak menemukan baris yang tersedia, Anda bisa memulai proc 'top off' Anda untuk menghasilkan batch baris baruDengan perubahan yang diajukan pada model data ini:
available_for_use
tabel untuk memastikan Anda tidak pernah kehabisan nilai baruYa,
available_for_use
tabel yang diusulkan hanyalah tabel nilai pre-generate 'next key'; dan ya, ada potensi untuk beberapa pertikaian ketika meraih nilai 'berikutnya', tetapi pertikaian a) mudah ditangani melalui desain tabel / indeks / permintaan yang tepat dan b) akan menjadi minor / berumur pendek dibandingkan dengan overhead / keterlambatan dengan gagasan saat ini pencarian berulang, kasar, indeks.sumber
n
kunci (mungkin 10 atau 20, untuk memaksanya mencari apa yang mungkin lebih rendah, nilai yang lebih diinginkan). Sangat menghargai jawabannya di sini, Anda menuliskannya! :)KeyCol
nilai yang tersedia ... ya, itu akan bekerja juga :-) dan jelas menghilangkan kebutuhan untuk perubahan model data ehKeyCol
manajer, dan kebutuhan untuk kode untuk potensi pelanggaran PK jika 2 (atau lebih) contoh bersamaan dari mencoba aplikasi untuk menggunakan yang samaKeyCol
nilai ... yuck ... pasti lebih mudah dengan server middleware tunggal atau solusi db-centricAda beberapa tantangan dengan pertanyaan ini. Indeks dalam SQL Server dapat melakukan hal berikut dengan sangat efisien hanya dengan beberapa pembacaan logis:
Namun, mereka tidak dapat digunakan untuk menemukan baris ke-N dalam indeks. Melakukan hal itu mengharuskan Anda menggulung indeks Anda sendiri yang disimpan sebagai sebuah tabel atau untuk memindai baris N pertama dalam indeks. Kode C # Anda sangat bergantung pada fakta bahwa Anda dapat secara efisien menemukan elemen Nth dari array, tetapi Anda tidak dapat melakukannya di sini. Saya pikir algoritma itu tidak dapat digunakan untuk T-SQL tanpa perubahan model data.
Tantangan kedua terkait dengan pembatasan pada
BINARY
tipe data. Sejauh yang saya tahu Anda tidak dapat melakukan penambahan, pengurangan, atau pembagian dengan cara biasa. Anda dapat mengonversikan AndaBINARY(64)
ke aBIGINT
dan itu tidak akan menghasilkan kesalahan konversi, tetapi perilaku tidak didefinisikan :Selain itu, kurangnya kesalahan konversi agak menjadi masalah di sini. Anda dapat mengonversi apa pun yang lebih besar dari nilai terbesar yang mungkin,
BIGINT
tetapi itu akan memberi Anda hasil yang salah.Memang benar bahwa Anda memiliki nilai sekarang yang lebih besar dari 9223372036854775807. Namun, jika Anda selalu mulai dari 1 dan mencari nilai minimum terkecil maka nilai-nilai besar itu tidak dapat relevan kecuali tabel Anda memiliki lebih dari 9223372036854775807 baris. Ini sepertinya tidak mungkin karena meja Anda pada saat itu akan menjadi sekitar 2000 exabytes, jadi untuk keperluan menjawab pertanyaan Anda, saya akan mengasumsikan bahwa nilai yang sangat besar tidak perlu dicari. Saya juga akan melakukan konversi tipe data karena tampaknya tidak dapat dihindari.
Untuk data pengujian, saya memasukkan setara 50 juta integer berurutan ke dalam tabel bersama dengan 50 juta integer lainnya dengan celah nilai tunggal tentang setiap 20 nilai. Saya juga memasukkan satu nilai yang tidak akan cocok dengan yang ditandatangani
BIGINT
:Kode itu membutuhkan beberapa menit untuk berjalan di mesin saya. Saya membuat paruh pertama tabel tidak memiliki celah untuk mewakili semacam kasus yang lebih buruk untuk kinerja. Kode yang saya gunakan untuk menyelesaikan masalah memindai indeks sehingga akan selesai dengan sangat cepat jika celah pertama di awal dalam tabel. Sebelum kita sampai di sana, mari kita verifikasi bahwa data sudah sebagaimana mestinya:
Hasilnya menunjukkan bahwa nilai maksimum yang kami konversi
BIGINT
adalah 102500672:Ada 100 juta baris dengan nilai yang sesuai dengan BIGINT seperti yang diharapkan:
Salah satu pendekatan untuk masalah ini adalah memindai indeks secara berurutan dan berhenti segera setelah nilai baris tidak sesuai dengan
ROW_NUMBER()
nilai yang diharapkan . Seluruh tabel tidak perlu dipindai untuk mendapatkan baris pertama: hanya baris ke atas sampai celah pertama. Inilah salah satu cara untuk menulis kode yang kemungkinan akan mendapatkan paket kueri itu:Untuk alasan yang tidak sesuai dengan jawaban ini, kueri ini akan sering dijalankan secara serial oleh SQL Server dan SQL Server akan sering meremehkan jumlah baris yang perlu dipindai sebelum pertandingan pertama ditemukan. Di mesin saya, SQL Server memindai 50000022 baris dari indeks sebelum menemukan kecocokan pertama. Permintaan membutuhkan waktu 11 detik untuk berjalan. Perhatikan bahwa ini mengembalikan nilai pertama melewati celah. Tidak jelas baris mana yang Anda inginkan dengan tepat, tetapi Anda harus dapat mengubah kueri agar sesuai dengan kebutuhan Anda tanpa banyak masalah. Seperti apa rencananya :
Satu-satunya ide saya adalah menggertak SQL Server menggunakan paralelisme untuk permintaan. Saya memiliki empat CPU, jadi saya akan membagi data menjadi empat rentang dan mencari rentang tersebut. Setiap CPU akan diberi kisaran. Untuk menghitung rentang, saya hanya meraih nilai maks dan mengasumsikan bahwa data didistribusikan secara merata. Jika Anda ingin lebih pintar tentang itu, Anda bisa melihat histogram statistik sampel untuk nilai kolom dan membangun rentang Anda dengan cara itu. Kode di bawah ini bergantung pada banyak trik tidak berdokumen yang tidak aman untuk diproduksi, termasuk jejak bendera 8649 :
Berikut adalah pola paralel nested loop:
Secara keseluruhan, kueri lebih berfungsi daripada sebelumnya karena akan memindai lebih banyak baris dalam tabel. Namun, sekarang berjalan dalam 7 detik di desktop saya. Mungkin paralel dengan lebih baik pada server nyata. Berikut ini tautan ke paket yang sebenarnya .
Saya benar-benar tidak bisa memikirkan cara yang baik untuk menyelesaikan masalah ini. Melakukan perhitungan di luar SQL atau mengubah model data mungkin merupakan taruhan terbaik Anda.
sumber
Inilah jawaban yang mungkin tidak akan berhasil untuk Anda, tetapi saya akan menambahkannya.
Meskipun BINARY (64) dapat dihitung, ada dukungan yang buruk untuk menentukan penggantinya. Karena BIGINT tampaknya terlalu kecil untuk domain Anda, Anda mungkin mempertimbangkan untuk menggunakan DECIMAL (38,0), yang tampaknya merupakan jenis ANGKA terbesar di SQL-server.
Menemukan celah pertama itu mudah karena kita dapat membuat nomor yang kita cari:
Nested loop yang tergabung dalam indeks pk harus cukup untuk menemukan item pertama yang tersedia.
sumber