Ada beberapa tantangan dengan pertanyaan ini. Indeks dalam SQL Server dapat melakukan hal berikut dengan sangat efisien hanya dengan beberapa pembacaan logis:
- periksa apakah ada baris
- periksa apakah tidak ada baris
- temukan baris berikutnya mulai dari beberapa titik
- temukan baris sebelumnya yang dimulai di beberapa titik
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 Anda BINARY(64)
ke a BIGINT
dan itu tidak akan menghasilkan kesalahan konversi, tetapi perilaku tidak didefinisikan :
Konversi antara tipe data apa pun dan tipe data biner tidak dijamin sama antara versi SQL Server.
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
:
CREATE TABLE dbo.BINARY_PROBLEMS (
KeyCol BINARY(64) NOT NULL
);
INSERT INTO dbo.BINARY_PROBLEMS WITH (TABLOCK)
SELECT CAST(SUM(OFFSET) OVER (ORDER BY (SELECT NULL) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS BINARY(64))
FROM
(
SELECT 1 + CASE WHEN t.RN > 50000000 THEN
CASE WHEN ABS(CHECKSUM(NewId()) % 20) = 10 THEN 1 ELSE 0 END
ELSE 0 END OFFSET
FROM
(
SELECT TOP (100000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
CROSS JOIN master..spt_values t3
) t
) tt
OPTION (MAXDOP 1);
CREATE UNIQUE CLUSTERED INDEX CI_BINARY_PROBLEMS ON dbo.BINARY_PROBLEMS (KeyCol);
-- add a value too large for BIGINT
INSERT INTO dbo.BINARY_PROBLEMS
SELECT CAST(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000 AS BINARY(64));
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:
SELECT TOP (2) KeyColBigInt
FROM
(
SELECT KeyCol
, CAST(KeyCol AS BIGINT) KeyColBigInt
FROM dbo.BINARY_PROBLEMS
) t
ORDER By KeyCol DESC;
Hasilnya menunjukkan bahwa nilai maksimum yang kami konversi BIGINT
adalah 102500672:
╔══════════════════════╗
║ KeyColBigInt ║
╠══════════════════════╣
║ -9223372036854775808 ║
║ 102500672 ║
╚══════════════════════╝
Ada 100 juta baris dengan nilai yang sesuai dengan BIGINT seperti yang diharapkan:
SELECT COUNT(*)
FROM dbo.BINARY_PROBLEMS
WHERE KeyCol < 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007FFFFFFFFFFFFFFF;
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:
SELECT TOP (1) KeyCol
FROM
(
SELECT KeyCol
, CAST(KeyCol AS BIGINT) KeyColBigInt
, ROW_NUMBER() OVER (ORDER BY KeyCol) RN
FROM dbo.BINARY_PROBLEMS
WHERE KeyCol < 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007FFFFFFFFFFFFFFF
) t
WHERE KeyColBigInt <> RN
ORDER BY KeyCol;
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 :
SELECT TOP 1 ca.KeyCol
FROM (
SELECT 1 bucket_min_value, 25625168 bucket_max_value
UNION ALL
SELECT 25625169, 51250336
UNION ALL
SELECT 51250337, 76875504
UNION ALL
SELECT 76875505, 102500672
) buckets
CROSS APPLY (
SELECT TOP 1 t.KeyCol
FROM
(
SELECT KeyCol
, CAST(KeyCol AS BIGINT) KeyColBigInt
, buckets.bucket_min_value - 1 + ROW_NUMBER() OVER (ORDER BY KeyCol) RN
FROM dbo.BINARY_PROBLEMS
WHERE KeyCol >= CAST(buckets.bucket_min_value AS BINARY(64)) AND KeyCol <= CAST(buckets.bucket_max_value AS BINARY(64))
) t
WHERE t.KeyColBigInt <> t.RN
ORDER BY t.KeyCol
) ca
ORDER BY ca.KeyCol
OPTION (QUERYTRACEON 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.
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 ?