この質問にはいくつかの課題があります。SQL Serverのインデックスは、それぞれ数個の論理読み取りで次のことを非常に効率的に実行できます。
- 行が存在することを確認してください
- 行が存在しないことを確認してください
- ある時点で始まる次の行を見つける
- ある時点で始まる前の行を見つける
ただし、これらを使用してインデックスのN番目の行を見つけることはできません。そのためには、テーブルとして格納されている独自のインデックスをロールするか、インデックスの最初のN行をスキャンする必要があります。C#コードは、配列のN番目の要素を効率的に見つけることができるという事実に大きく依存していますが、ここではそれを行うことはできません。データモデルを変更しないと、アルゴリズムはT-SQLには使用できないと思います。
2番目の課題は、BINARY
データ型の制限に関するものです。私の知る限り、通常の方法で加算、減算、または除算を実行することはできません。をに変換できますBINARY(64)
が、BIGINT
変換エラーはスローされませんが、動作は定義されていません。
データ型とバイナリデータ型の間の変換は、SQL Serverのバージョン間で同じであるとは限りません。
さらに、変換エラーがないことは、ここでは多少問題になります。可能な最大BIGINT
値よりも大きいものはすべて変換できますが、間違った結果が得られます。
現在、9223372036854775807より大きい値があることは事実です。ただし、常に1から始めて最小の最小値を検索している場合、テーブルに9223372036854775807を超える行がない限り、それらの大きな値は関係ありません。その時点でのテーブルが約2000エクサバイトになるため、これはありそうにありません。質問に答えるために、非常に大きな値を検索する必要がないと仮定します。また、データ型の変換は避けられないようです。
テストデータについては、5,000万個の連続した整数に相当する整数を、20個の値ごとに1つの値のギャップを持つさらに5,000万個の整数とともにテーブルに挿入しました。また、signedに適切に適合しない単一の値を挿入しました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));
このコードを私のマシンで実行するには数分かかりました。表の前半には、パフォーマンスの悪い例を表すギャップがないようにしました。問題を解決するために使用したコードは、インデックスを順番にスキャンするため、最初のギャップがテーブルの早い段階であると、非常に速く終了します。その前に、データが正しいことを確認しましょう。
SELECT TOP (2) KeyColBigInt
FROM
(
SELECT KeyCol
, CAST(KeyCol AS BIGINT) KeyColBigInt
FROM dbo.BINARY_PROBLEMS
) t
ORDER By KeyCol DESC;
結果は、変換後の最大値BIGINT
が102500672であることを示唆しています。
╔══════════════════════╗
║ KeyColBigInt ║
╠══════════════════════╣
║ -9223372036854775808 ║
║ 102500672 ║
╚══════════════════════╝
予想どおりBIGINTに適合する値を持つ1億行があります。
SELECT COUNT(*)
FROM dbo.BINARY_PROBLEMS
WHERE KeyCol < 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007FFFFFFFFFFFFFFF;
この問題への1つのアプローチは、インデックスを順番にスキャンし、行の値が期待ROW_NUMBER()
値と一致しなくなったらすぐに終了することです。最初の行を取得するためにテーブル全体をスキャンする必要はありません。最初のギャップまでの行のみをスキャンする必要があります。クエリプランを取得する可能性が高いコードを記述する方法の1つを次に示します。
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;
この回答に収まらない理由により、このクエリはSQL Serverによって逐次実行されることが多く、SQL Serverは最初の一致が見つかるまでにスキャンする必要がある行数を過小評価することがよくあります。私のマシンでは、SQL Serverは最初の一致を見つける前にインデックスから50000022行をスキャンします。クエリの実行には11秒かかります。これはギャップを超えた最初の値を返すことに注意してください。正確にどの行が必要かは明確ではありませんが、多くの問題なく、ニーズに合わせてクエリを変更できるはずです。ここでは何の計画は次のようになります。
私の他の唯一のアイデアは、クエリに並列処理を使用するようにSQL Serverをいじめることでした。CPUが4つあるので、データを4つの範囲に分割し、それらの範囲でシークを行います。各CPUには範囲が割り当てられます。範囲を計算するために、最大値を取得し、データが均等に分散されていると想定しました。よりスマートになりたい場合は、列の値のサンプリングされた統計ヒストグラムを見て、そのように範囲を構築できます。以下のコードは、トレースフラグ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);
並列ネストループパターンは次のようになります。
全体として、クエリはテーブル内のより多くの行をスキャンするため、以前よりも多くの処理を実行します。ただし、デスクトップでは7秒で実行されます。実際のサーバーでの並列化が向上する可能性があります。こちらが実際のプランへのリンクです。
私は本当にこの問題を解決する良い方法を考えることができません。SQLの外部で計算を行うか、データモデルを変更するのが最善の策です。
delete
から、現在利用可能なバイナリを別のテーブル(たとえばcreate table available_for_reuse(id binary64)
)にダンプするトリガーをテーブルに置く可能性はありますか?