Ci sono alcune sfide con questa domanda. Gli indici in SQL Server possono eseguire le seguenti operazioni in modo molto efficiente con solo una lettura logica ciascuno:
- controlla che esista una riga
- controlla che non esista una riga
- trova la riga successiva a partire da un certo punto
- trova la riga precedente a partire da un certo punto
Tuttavia, non possono essere utilizzati per trovare l'ennesima riga in un indice. Per fare ciò è necessario ruotare il proprio indice memorizzato come tabella o scansionare le prime N righe nell'indice. Il tuo codice C # si basa fortemente sul fatto che puoi trovare in modo efficiente l'ennesimo elemento dell'array, ma non puoi farlo qui. Penso che l'algoritmo non sia utilizzabile per T-SQL senza una modifica del modello di dati.
La seconda sfida riguarda le restrizioni sui BINARY
tipi di dati. Per quanto ne so, non è possibile eseguire addizioni, sottrazioni o divisioni nei normali modi. Puoi convertire il tuo BINARY(64)
in a BIGINT
e non genererà errori di conversione, ma il comportamento non è definito :
Le conversioni tra qualsiasi tipo di dati e tipi di dati binari non sono garantite uguali tra le versioni di SQL Server.
Inoltre, la mancanza di errori di conversione è un po 'un problema qui. Puoi convertire qualsiasi cosa più grande del BIGINT
valore più grande possibile ma ti darà risultati sbagliati.
È vero che al momento hai valori maggiori di 9223372036854775807. Tuttavia, se inizi sempre da 1 e cerchi il valore minimo più piccolo, quei valori grandi non possono essere rilevanti a meno che la tua tabella non contenga più di 9223372036854775807 righe. Ciò sembra improbabile perché la tua tabella a quel punto sarebbe di circa 2000 exabyte, quindi ai fini della risposta alla tua domanda suppongo che non è necessario cercare i valori molto grandi. Farò anche la conversione del tipo di dati perché sembrano inevitabili.
Per i dati del test, ho inserito l'equivalente di 50 milioni di numeri interi sequenziali in una tabella insieme a 50 milioni di numeri interi in più con un singolo divario di valore ogni 20 valori circa. Ho anche inserito un singolo valore che non si adatta correttamente in un segno 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));
Il codice ha impiegato alcuni minuti per essere eseguito sulla mia macchina. Ho fatto in modo che la prima metà del tavolo non avesse spazi vuoti per rappresentare una specie di caso peggiore per le prestazioni. Il codice che ho usato per risolvere il problema analizza l'indice in modo che finisca molto rapidamente se il primo gap è all'inizio della tabella. Prima di arrivare a questo, verificiamo che i dati siano come dovrebbero essere:
SELECT TOP (2) KeyColBigInt
FROM
(
SELECT KeyCol
, CAST(KeyCol AS BIGINT) KeyColBigInt
FROM dbo.BINARY_PROBLEMS
) t
ORDER By KeyCol DESC;
I risultati suggeriscono che il valore massimo che convertiamo in BIGINT
è 102500672:
╔══════════════════════╗
║ KeyColBigInt ║
╠══════════════════════╣
║ -9223372036854775808 ║
║ 102500672 ║
╚══════════════════════╝
Esistono 100 milioni di righe con valori che si adattano a BIGINT come previsto:
SELECT COUNT(*)
FROM dbo.BINARY_PROBLEMS
WHERE KeyCol < 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007FFFFFFFFFFFFFFF;
Un approccio a questo problema è la scansione dell'indice in ordine e la chiusura non appena il valore di una riga non corrisponde al ROW_NUMBER()
valore previsto . Non è necessario eseguire la scansione dell'intera tabella per ottenere la prima riga: solo le righe fino al primo spazio. Ecco un modo per scrivere codice che probabilmente otterrà quel piano di query:
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;
Per motivi che non rientrano in questa risposta, questa query verrà spesso eseguita in serie da SQL Server e SQL Server spesso sottostimerà il numero di righe che devono essere analizzate prima di trovare la prima corrispondenza. Sul mio computer, SQL Server esegue la scansione di 50000022 righe dall'indice prima di trovare la prima corrispondenza. L'esecuzione della query richiede 11 secondi. Si noti che questo restituisce il primo valore oltre il gap. Non è chiaro quale riga desideri esattamente, ma dovresti essere in grado di modificare la query in base alle tue esigenze senza troppi problemi. Ecco come appare il piano :
La mia unica altra idea era quella di costringere SQL Server a utilizzare il parallelismo per la query. Ho quattro CPU, quindi dividerò i dati in quattro intervalli e cercherò su tali intervalli. A ciascuna CPU verrà assegnato un intervallo. Per calcolare gli intervalli ho appena preso il valore massimo e ho ipotizzato che i dati fossero distribuiti uniformemente. Se vuoi essere più intelligente al riguardo, puoi guardare un istogramma delle statistiche campionato per i valori delle colonne e costruire i tuoi intervalli in quel modo. Il codice seguente si basa su molti trucchi non documentati che non sono sicuri per la produzione, incluso il flag di traccia 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);
Ecco come appare il modello di ciclo nidificato parallelo:
Nel complesso, la query funziona più di prima poiché esegue la scansione di più righe nella tabella. Tuttavia, ora funziona in 7 secondi sul mio desktop. Potrebbe parallelizzarsi meglio su un vero server. Ecco un link al piano reale .
Non riesco davvero a pensare a un buon modo per risolvere questo problema. Fare il calcolo al di fuori di SQL o cambiare il modello di dati può essere la tua migliore scommessa.
delete
trigger sul tavolo che scarichi il binario ora disponibile su una tabella separata (ad es.create table available_for_reuse(id binary64)
), Specialmente alla luce del requisito di fare questa ricerca molto frequentemente ?