Mengoptimalkan pencarian rentang angka (interval) di SQL Server

18

Pertanyaan ini mirip dengan Mengoptimalkan Pencarian Rentang IP? tapi itu terbatas pada SQL Server 2000.

Misalkan saya memiliki 10 juta rentang untuk sementara disimpan dalam tabel terstruktur dan diisi seperti di bawah ini.

CREATE TABLE MyTable
(
Id        INT IDENTITY PRIMARY KEY,
RangeFrom INT NOT NULL,
RangeTo   INT NOT NULL,
CHECK (RangeTo > RangeFrom),
INDEX IX1 (RangeFrom,RangeTo),
INDEX IX2 (RangeTo,RangeFrom)
);

WITH RandomNumbers
     AS (SELECT TOP 10000000 ABS(CRYPT_GEN_RANDOM(4)%100000000) AS Num
         FROM   sys.all_objects o1,
                sys.all_objects o2,
                sys.all_objects o3,
                sys.all_objects o4)
INSERT INTO MyTable
            (RangeFrom,
             RangeTo)
SELECT Num,
       Num + 1 + CRYPT_GEN_RANDOM(1)
FROM   RandomNumbers 

Saya perlu tahu semua rentang yang berisi nilai 50,000,000. Saya mencoba pertanyaan berikut

SELECT *
FROM MyTable
WHERE 50000000 BETWEEN RangeFrom AND RangeTo

SQL Server menunjukkan bahwa ada 10.951 pembacaan logis dan hampir 5 juta baris dibaca untuk mengembalikan 12 pencocokan.

masukkan deskripsi gambar di sini

Bisakah saya meningkatkan kinerja ini? Setiap restrukturisasi tabel atau indeks tambahan baik-baik saja.

Martin Smith
sumber
Jika saya memahami pengaturan tabel dengan benar, Anda memilih angka acak secara seragam untuk membentuk rentang Anda, tanpa batasan pada "ukuran" dari setiap rentang. Dan probe Anda adalah untuk bagian tengah dari kisaran keseluruhan 1..100M. Dalam hal ini - tidak ada pengelompokan yang jelas karena keacakan seragam - Saya tidak tahu mengapa indeks pada batas bawah atau batas atas akan membantu. Bisakah Anda jelaskan itu?
davidbak
@davidbak indeks konvensional pada tabel ini memang tidak terlalu membantu dalam kasus terburuk karena harus memindai setengah kisaran karenanya meminta perbaikan potensial pada itu. Ada peningkatan yang bagus dalam pertanyaan terkait untuk SQL Server 2000 dengan pengenalan "granula" Saya berharap indeks spasial dapat membantu di sini karena mereka mendukung containspermintaan dan sementara mereka bekerja dengan baik dalam mengurangi jumlah data yang dibaca mereka tampaknya menambahkan lainnya overhead yang mengatasi ini.
Martin Smith
Saya tidak memiliki fasilitas untuk mencobanya - tetapi saya ingin tahu apakah dua indeks - satu di batas bawah, satu di atas - dan kemudian gabungan bagian dalam - akan membiarkan pengoptimal kueri mengerjakan sesuatu.
davidbak
4
Terkait: Pilih semua rentang yang tumpang tindih dari rentang awal
Paul White mengatakan GoFundMonica

Jawaban:

11

Columnstore sangat senang di sini dibandingkan dengan indeks nonclustered yang memindai setengah tabel. Indeks columnstore nonclustered memberikan sebagian besar manfaat tetapi memasukkan data yang dipesan ke dalam indeks columnstore clustered bahkan lebih baik.

DROP TABLE IF EXISTS dbo.MyTableCCI;

CREATE TABLE dbo.MyTableCCI
(
Id        INT PRIMARY KEY,
RangeFrom INT NOT NULL,
RangeTo   INT NOT NULL,
CHECK (RangeTo > RangeFrom),
INDEX CCI CLUSTERED COLUMNSTORE
);

INSERT INTO dbo.MyTableCCI
SELECT TOP (987654321) *
FROM dbo.MyTable
ORDER BY RangeFrom ASC
OPTION (MAXDOP 1);

Dengan desain saya bisa mendapatkan penghapusan rowgroup pada RangeFromkolom yang akan menghilangkan setengah dari rowgroup saya. Tetapi karena sifat data saya juga mendapatkan penghapusan rowgroup pada RangeTokolom juga:

Table 'MyTableCCI'. Segment reads 1, segment skipped 9.

Untuk tabel yang lebih besar dengan lebih banyak data variabel ada berbagai cara untuk memuat data untuk menjamin penghapusan rowgroup terbaik di kedua kolom. Khusus untuk data Anda, kueri membutuhkan waktu 1 ms.

Joe Obbish
sumber
ya pasti mencari pendekatan lain untuk dipertimbangkan tanpa batasan tahun 2000. Tidak terdengar seperti itu akan dipukuli.
Martin Smith
9

Paul White menunjukkan jawaban untuk pertanyaan serupa yang berisi tautan ke artikel menarik oleh Itzik Ben Gan . Ini menjelaskan model "Pohon Interval Relasional Statis" yang memungkinkan ini dilakukan secara efisien.

Singkatnya, pendekatan ini melibatkan penyimpanan nilai yang dihitung ("forknode") berdasarkan nilai interval di baris. Saat mencari rentang yang memotong rentang lain, dimungkinkan untuk menghitung ulang nilai-nilai forknode yang mungkin dimiliki oleh baris-baris yang cocok dan menggunakan ini untuk menemukan hasil dengan maksimum 31 operasi pencarian (di bawah ini mendukung bilangan bulat dalam kisaran 0 hingga maks yang ditandatangani 32) bit int)

Berdasarkan ini saya merestrukturisasi tabel seperti di bawah ini.

CREATE TABLE dbo.MyTable3
(
  Id        INT IDENTITY PRIMARY KEY,
  RangeFrom INT NOT NULL,
  RangeTo   INT NOT NULL,   
  node  AS RangeTo - RangeTo % POWER(2, FLOOR(LOG((RangeFrom - 1) ^ RangeTo, 2))) PERSISTED NOT NULL,
  CHECK (RangeTo > RangeFrom)
);

CREATE INDEX ix1 ON dbo.MyTable3 (node, RangeFrom) INCLUDE (RangeTo);
CREATE INDEX ix2 ON dbo.MyTable3 (node, RangeTo) INCLUDE (RangeFrom);

SET IDENTITY_INSERT MyTable3 ON

INSERT INTO MyTable3
            (Id,
             RangeFrom,
             RangeTo)
SELECT Id,
       RangeFrom,
       RangeTo
FROM   MyTable

SET IDENTITY_INSERT MyTable3 OFF 

Dan kemudian menggunakan kueri berikut (artikel mencari interval berpotongan sehingga menemukan interval yang mengandung titik adalah kasus yang merosot)

DECLARE @value INT = 50000000;

;WITH N AS
(
SELECT 30 AS Level, 
       CASE WHEN @value > POWER(2,30) THEN POWER(2,30) END AS selected_left_node, 
       CASE WHEN @value < POWER(2,30) THEN POWER(2,30) END AS selected_right_node, 
       (SIGN(@value - POWER(2,30)) * POWER(2,29)) + POWER(2,30)  AS node
UNION ALL
SELECT N.Level-1,   
       CASE WHEN @value > node THEN node END AS selected_left_node,  
       CASE WHEN @value < node THEN node END AS selected_right_node,
       (SIGN(@value - node) * POWER(2,N.Level-2)) + node  AS node
FROM N 
WHERE N.Level > 0
)
SELECT I.id, I.RangeFrom, I.RangeTo
FROM dbo.MyTable3 AS I
  JOIN N AS L
    ON I.node = L.selected_left_node
    AND I.RangeTo >= @value
    AND L.selected_left_node IS NOT NULL
UNION ALL
SELECT I.id, I.RangeFrom, I.RangeTo
FROM dbo.MyTable3 AS I
  JOIN N AS R
    ON I.node = R.selected_right_node
    AND I.RangeFrom <= @value
    AND R.selected_right_node IS NOT NULL
UNION ALL
SELECT I.id, I.RangeFrom, I.RangeTo
FROM dbo.MyTable3 AS I
WHERE node = @value;

Ini biasanya dijalankan 1mspada mesin saya ketika semua halaman dalam cache - dengan statistik IO.

Table 'MyTable3'. Scan count 24, logical reads 72, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 4, logical reads 374, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

dan merencanakan

masukkan deskripsi gambar di sini

NB: Sumbernya menggunakan TVF multistatement daripada CTE rekursif untuk mendapatkan node untuk bergabung tetapi untuk kepentingan membuat jawaban saya mandiri saya telah memilih yang terakhir. Untuk penggunaan produksi, saya mungkin akan menggunakan TVF.

Martin Smith
sumber
9

Saya dapat menemukan pendekatan mode baris yang bersaing dengan pendekatan N / CCI, tetapi Anda perlu tahu sesuatu tentang data Anda. Misalkan Anda memiliki kolom yang berisi perbedaan RangeFromdan RangeTodan Anda mengindeksnya bersama dengan RangeFrom:

ALTER TABLE dbo.MyTableWithDiff ADD DiffOfColumns AS RangeTo-RangeFrom;

CREATE INDEX IXDIFF ON dbo.MyTableWithDiff (DiffOfColumns,RangeFrom) INCLUDE (RangeTo);

Jika Anda mengetahui semua nilai berbeda DiffOfColumnsmaka Anda dapat melakukan pencarian untuk setiap nilai DiffOfColumnsdengan filter rentang aktif RangeTountuk mendapatkan semua data yang relevan. Misalnya, jika kita tahu bahwa DiffOfColumns= 2 maka nilai yang diizinkan hanya untuk RangeFrom49999998, 49999999, dan 50000000. Rekursi dapat digunakan untuk mendapatkan semua nilai yang berbeda DiffOfColumnsdan ini bekerja dengan baik untuk kumpulan data Anda karena hanya ada 256 di antaranya. Kueri di bawah ini membutuhkan sekitar 6 ms pada mesin saya:

WITH RecursiveCTE
AS
(
    -- Anchor
    SELECT TOP (1)
        DiffOfColumns
    FROM dbo.MyTableWithDiff AS T
    ORDER BY
        T.DiffOfColumns

    UNION ALL

    -- Recursive
    SELECT R.DiffOfColumns
    FROM
    (
        -- Number the rows
        SELECT 
            T.DiffOfColumns,
            rn = ROW_NUMBER() OVER (
                ORDER BY T.DiffOfColumns)
        FROM dbo.MyTableWithDiff AS T
        JOIN RecursiveCTE AS R
            ON R.DiffOfColumns < T.DiffOfColumns
    ) AS R
    WHERE
        -- Only the row that sorts lowest
        R.rn = 1
)
SELECT ca.*
FROM RecursiveCTE rcte
CROSS APPLY (
    SELECT mt.Id, mt.RangeFrom, mt.RangeTo
    FROM dbo.MyTableWithDiff mt
    WHERE mt.DiffOfColumns = rcte.DiffOfColumns
    AND mt.RangeFrom >= 50000000 - rcte.DiffOfColumns AND mt.RangeFrom <= 50000000
) ca
OPTION (MAXRECURSION 0);

Anda dapat melihat bagian rekursif biasa bersama dengan indeks mencari setiap nilai yang berbeda:

rencana permintaan 1

Kelemahan dari pendekatan ini adalah mulai lambat ketika ada terlalu banyak nilai berbeda untuk DiffOfColumns. Mari kita lakukan tes yang sama, tetapi gunakan CRYPT_GEN_RANDOM(2)sebagai ganti CRYPT_GEN_RANDOM(1).

DROP TABLE IF EXISTS dbo.MyTableBigDiff;

CREATE TABLE dbo.MyTableBigDiff
(
Id        INT IDENTITY PRIMARY KEY,
RangeFrom INT NOT NULL,
RangeTo   INT NOT NULL,
CHECK (RangeTo > RangeFrom)
);

WITH RandomNumbers
     AS (SELECT TOP 10000000 ABS(CRYPT_GEN_RANDOM(4)%100000000) AS Num
         FROM   sys.all_objects o1,
                sys.all_objects o2,
                sys.all_objects o3,
                sys.all_objects o4)
INSERT INTO dbo.MyTableBigDiff
            (RangeFrom,
             RangeTo)
SELECT Num,
       Num + 1 + CRYPT_GEN_RANDOM(2) -- note the 2
FROM   RandomNumbers;


ALTER TABLE dbo.MyTableBigDiff ADD DiffOfColumns AS RangeTo-RangeFrom;

CREATE INDEX IXDIFF ON dbo.MyTableBigDiff (DiffOfColumns,RangeFrom) INCLUDE (RangeTo);

Kueri yang sama sekarang menemukan 65536 baris dari bagian rekursif dan membutuhkan 823 ms CPU pada mesin saya. Ada PAGELATCH_SH menunggu dan hal-hal buruk lainnya sedang terjadi. Saya dapat meningkatkan kinerja dengan menambahkan nilai diff untuk menjaga agar jumlah nilai unik tetap terkendali dan menyesuaikan untuk bucket di CROSS APPLY. Untuk rangkaian data ini saya akan mencoba 256 ember:

ALTER TABLE dbo.MyTableBigDiff ADD DiffOfColumns_bucket256 AS CAST(CEILING((RangeTo-RangeFrom) / 256.) AS INT);

CREATE INDEX [IXDIFF😎] ON dbo.MyTableBigDiff (DiffOfColumns_bucket256, RangeFrom) INCLUDE (RangeTo);

Salah satu cara untuk menghindari mendapatkan baris tambahan (sekarang saya membandingkan dengan nilai bulat bukan nilai sebenarnya) adalah dengan memfilter pada RangeTo:

CROSS APPLY (
    SELECT mt.Id, mt.RangeFrom, mt.RangeTo
    FROM dbo.MyTableBigDiff mt
    WHERE mt.DiffOfColumns_bucket256 = rcte.DiffOfColumns_bucket256
    AND mt.RangeFrom >= 50000000 - (256 * rcte.DiffOfColumns_bucket256)
    AND mt.RangeFrom <= 50000000
    AND mt.RangeTo >= 50000000
) ca

Permintaan lengkap sekarang membutuhkan 6 ms pada mesin saya.

Joe Obbish
sumber
8

Salah satu cara alternatif untuk mewakili suatu rentang adalah sebagai titik pada suatu garis.

Di bawah ini memigrasikan semua data ke tabel baru dengan kisaran yang direpresentasikan sebagai geometrytipe data.

CREATE TABLE MyTable2
(
Id INT IDENTITY PRIMARY KEY,
Range GEOMETRY NOT NULL,
RangeFrom AS Range.STPointN(1).STX,
RangeTo   AS Range.STPointN(2).STX,
CHECK (Range.STNumPoints() = 2 AND Range.STPointN(1).STY = 0 AND Range.STPointN(2).STY = 0)
);

SET IDENTITY_INSERT MyTable2 ON

INSERT INTO MyTable2
            (Id,
             Range)
SELECT ID,
       geometry::STLineFromText(CONCAT('LINESTRING(', RangeFrom, ' 0, ', RangeTo, ' 0)'), 0)
FROM   MyTable

SET IDENTITY_INSERT MyTable2 OFF 


CREATE SPATIAL INDEX index_name   
ON MyTable2 ( Range )  
USING GEOMETRY_GRID  
WITH (  
BOUNDING_BOX = ( xmin=0, ymin=0, xmax=110000000, ymax=1 ),  
GRIDS = (HIGH, HIGH, HIGH, HIGH),  
CELLS_PER_OBJECT = 16); 

Kueri yang setara untuk menemukan rentang yang berisi nilai di 50,000,000bawah.

SELECT Id,
       RangeFrom,
       RangeTo
FROM   MyTable2
WHERE  Range.STContains(geometry::STPointFromText ('POINT (50000000 0)', 0)) = 1 

Bacaan untuk ini menunjukkan peningkatan pada 10,951dari permintaan asli.

Table 'MyTable2'. Scan count 0, logical reads 505, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'extended_index_1797581442_384000'. Scan count 4, logical reads 17, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Namun tidak ada peningkatan yang signifikan dibandingkan yang asli dalam hal waktu yang berlalu . Hasil eksekusi yang umum adalah 250 ms vs 252 ms.

Rencana pelaksanaan lebih kompleks seperti di bawah ini

masukkan deskripsi gambar di sini

Satu-satunya kasus di mana penulisan ulang bekerja andal lebih baik bagi saya adalah dengan cache dingin.

Sangat mengecewakan dalam hal ini dan sulit untuk merekomendasikan penulisan ulang ini tetapi publikasi hasil negatif juga dapat bermanfaat.

Martin Smith
sumber
5

Sebagai penghormatan kepada tuan robot baru kami, saya memutuskan untuk melihat apakah ada fungsi R-dan Python yang baru dapat membantu kami di sini. Jawabannya adalah tidak, setidaknya untuk skrip yang saya dapat bekerja dan mengembalikan hasil yang benar. Jika ada orang dengan pengetahuan yang lebih baik datang, yah, jangan ragu untuk memukul saya. Harga saya masuk akal.

Untuk melakukan ini, saya mengatur VM dengan 4 core dan 16 GB RAM, berpikir ini akan cukup untuk menangani set data ~ 200MB.

Mari kita mulai dengan bahasa yang tidak ada di Boston!

R

EXEC sp_execute_external_script 
@language = N'R', 
@script = N'
tweener = 50000000
MO = data.frame(MartinIn)
MartinOut <- subset(MO, RangeFrom <= tweener & RangeTo >= tweener, select = c("Id","RangeFrom","RangeTo"))
', 
@input_data_1_name = N'MartinIn',
@input_data_1 = N'SELECT Id, RangeFrom, RangeTo FROM dbo.MyTable',
@output_data_1_name = N'MartinOut',
@parallel = 1
WITH RESULT SETS ((ID INT, RangeFrom INT, RangeTo INT));

Ini waktu yang buruk.

Table 'MyTable'. Scan count 1, logical reads 22400

 SQL Server Execution Times:
   CPU time = 3219 ms,  elapsed time = 5349 ms.

The rencana eksekusi yang cukup tidak menarik, meskipun aku tidak tahu mengapa operator tengah memiliki untuk menghubungi kami nama.

GILA

Selanjutnya, koding dengan krayon!

Python

EXEC sp_execute_external_script 
@language = N'Python', 
@script = N'
import pandas as pd
MO = pd.DataFrame(MartinIn)
tweener = 50000000
MartinOut = MO[(MO.RangeFrom <= tweener) & (MO.RangeTo >= tweener)]
', 
@input_data_1_name = N'MartinIn',
@input_data_1 = N'SELECT Id, RangeFrom, RangeTo FROM dbo.MyTable',
@output_data_1_name = N'MartinOut',
@parallel = 1
WITH RESULT SETS ((ID INT, RangeFrom INT, RangeTo INT));

Tepat ketika Anda berpikir itu tidak bisa lebih buruk daripada R:

Table 'MyTable'. Scan count 1, logical reads 22400

 SQL Server Execution Times:
   CPU time = 3797 ms,  elapsed time = 10146 ms.

Rencana eksekusi bermulut kotor lainnya :

GILA

Hmm dan Hmmer

Sejauh ini, saya tidak terkesan. Saya tidak sabar untuk menghapus VM ini.

Erik Darling
sumber
1
Anda dapat mengirimkan parameter juga, misalnya DECLARE @input INT = 50000001; EXEC dbo.sp_execute_external_script @language = N'R', @script = N'OutputDataSet <- InputDataSet[which(x >= InputDataSet$RangeFrom & x <= InputDataSet$RangeTo) , ]', @parallel = 1, @input_data_1 = N'SELECT Id, RangeFrom, RangeTo FROM dbo.MyTable;', @params = N'@x INT', @x = 50000001 WITH RESULT SETS ( ( Id INT NOT NULL, RangeFrom INT NOT NULL, RangeTo INT NOT NULL ));tapi ya kinerjanya tidak bagus. Saya menggunakan R untuk hal-hal yang tidak dapat Anda lakukan dalam SQL, katakan jika Anda ingin memprediksi sesuatu.
wBob
4

Saya menemukan solusi yang cukup bagus menggunakan kolom yang dihitung, namun itu hanya baik untuk nilai tunggal. Yang sedang berkata, jika Anda memiliki nilai sihir, mungkin itu sudah cukup.

Mulai dengan sampel yang Anda berikan, lalu modifikasi tabel:

ALTER TABLE dbo.MyTable
    ADD curtis_jackson 
        AS CONVERT(BIT, CASE 
                            WHEN RangeTo >= 50000000
                            AND RangeFrom < 50000000
                            THEN 1 
                            ELSE 0 
                        END);

CREATE INDEX IX1_redo 
    ON dbo.MyTable (curtis_jackson) 
        INCLUDE (RangeFrom, RangeTo);

Pertanyaannya menjadi:

SELECT *
FROM MyTable
WHERE curtis_jackson = 1;

Yang mengembalikan hasil yang sama dengan permintaan awal Anda. Dengan rencana eksekusi dimatikan, berikut adalah statistik (terpotong karena singkatnya):

Table 'MyTable'. Scan count 1, logical reads 3...

SQL Server Execution Times:
  CPU time = 0 ms,  elapsed time = 0 ms.

Dan inilah rencana permintaannya :

GILA

Erik Darling
sumber
Bisakah Anda mengatasi imitasi kolom / indeks yang difilter dengan indeks yang dihidupkan WHERE (50000000 BETWEEN RangeFrom AND RangeTo) INCLUDE (..)?
ypercubeᵀᴹ
3
@ yper-crazyhat-cubeᵀᴹ - ya. CREATE INDEX IX1_redo ON dbo.MyTable (curtis_jackson) INCLUDE (RangeFrom, RangeTo) WHERE RangeTo >= 50000000 AND RangeFrom <= 50000000akan bekerja. Dan permintaan SELECT * FROM MyTable WHERE RangeTo >= 50000000 AND RangeFrom <= 50000000;itu menggunakannya - jadi tidak banyak kebutuhan untuk Curtis yang malang
Martin Smith
3

Solusi saya didasarkan pada pengamatan bahwa interval memiliki lebar maksimum W yang diketahui . Untuk data sampel ini adalah satu byte atau 256 bilangan bulat. Oleh karena itu untuk pencarian nilai parameter yang diberikan P kita tahu RangeFrom terkecil yang dapat di set hasil P - W . Menambahkan ini ke predikat memberi

declare @P int = 50000000;
declare @W int = 256;

select
    *
from MyTable
where @P between RangeFrom and RangeTo
and RangeFrom >= (@P - @W);

Mengingat pengaturan asli dan permintaan mesin saya (64 bit Windows 10, 4-core hyperthreaded i7, 2.8GHz, 16GB RAM) mengembalikan 13 baris. Kueri itu menggunakan pencarian indeks paralel dari indeks (RangeFrom, RangeTo). Permintaan yang direvisi juga melakukan pencarian indeks paralel pada indeks yang sama.

Pengukuran untuk kueri asli dan revisi adalah

                          Original  Revised
                          --------  -------
Stats IO Scan count              9        6
Stats IO logical reads       11547        6

Estimated number of rows   1643170  1216080
Number of rows read        5109666       29
QueryTimeStats CPU             344        2
QueryTimeStats Elapsed          53        0

Untuk kueri asli, jumlah baris yang dibaca sama dengan jumlah baris yang kurang dari atau sama dengan @P. Pengoptimal kueri (QO) tidak memiliki alternatif selain membacanya semuanya karena tidak dapat menentukan sebelumnya yang jika baris ini akan memenuhi predikat. Indeks multi-kolom pada (RangeFrom, RangeTo) tidak berguna dalam menghilangkan baris yang tidak cocok dengan RangeTo karena tidak ada korelasi antara kunci indeks pertama dan kedua yang dapat diterapkan. Misalnya, baris pertama mungkin memiliki interval kecil dan dihilangkan sedangkan baris kedua memiliki interval besar dan dikembalikan, atau sebaliknya.

Dalam satu upaya yang gagal saya mencoba memberikan kepastian itu melalui kendala pemeriksaan:

alter table MyTable with check
add constraint CK_MyTable_Interval
check
(
    RangeTo <= RangeFrom + 256
);

Tidak ada bedanya.

Dengan memasukkan pengetahuan eksternal saya tentang distribusi data ke dalam predikat, saya dapat menyebabkan QO melewati baris RangeFrom bernilai rendah, yang tidak pernah bisa menjadi bagian dari resultset, dan melintasi kolom utama indeks ke baris yang dapat diterima. Ini menunjukkan dalam pencarian predikat yang berbeda untuk setiap permintaan.

Dalam argumen cermin batas atas RangeTo adalah P + W . Ini tidak berguna, bagaimanapun, karena tidak ada korelasi antara RangeFrom dan RangeTo yang akan memungkinkan kolom tambahan indeks multi-kolom untuk menghilangkan baris. Karenanya tidak ada manfaat dari menambahkan klausa ini ke kueri.

Pendekatan ini mendapatkan sebagian besar manfaatnya dari ukuran interval kecil. Karena ukuran interval yang mungkin meningkatkan jumlah baris bernilai rendah yang dilewati berkurang, meskipun beberapa masih akan dilewati. Dalam kasus yang membatasi, dengan interval sebesar rentang data, pendekatan ini tidak lebih buruk daripada permintaan asli (yang saya akui kenyamanan dingin).

Saya mohon maaf atas kesalahan off-by-one apa pun yang mungkin ada dalam jawaban ini.

Michael Green
sumber