Clustered Index 'Prädikat suchen' und 'Prädikat' in derselben Spalte

7

Ich habe eine Clustered-Index-Primärschlüsselspalte und führe eine Bereichsabfrage durch. Das Problem ist, dass der Scan nur den ersten Bereichsteil für das Suchprädikat verwendet und die andere Seite des Bereichs als Restprädikat belässt. Dies führt dazu, dass alle Zeilen bis zur @ Obergrenze gelesen werden

Ich verwende zwei Parameter für den Bereich:

declare
    @lower numeric(18,0) = 1000,
    @upper numeric(18,0) = 1005;

select * from messages
where msg_id between @lower+1 and @upper;

In diesem Fall zeigt der tatsächliche Ausführungsplan:

  • Prädikat: messages.msg_id> = niedriger
  • Prädikat suchen: messages.msg_id <@upper
  • Zeilen gelesen: 1005

Tabellendefinition (vereinfacht):

CREATE TABLE [dbo].[messages](
    [msg_id] [numeric](18, 0) IDENTITY(0,1) NOT NULL,
    [col2] [varchar](32) NOT NULL,
 CONSTRAINT [PK_Message] PRIMARY KEY CLUSTERED 
(
    [msg_id] ASC
) 

Mehr Informationen

Bei Verwendung mit Konstanten anstelle von Variablen sind beide Prädikate "Suchen". Haben es versucht Option (Optimize for (@lower=1000)), ohne Erfolg

Abir Stolov
quelle

Antworten:

8

Beginnend mit Ihrer ursprünglichen Abfrage:

declare
    @lower numeric(18,0) = 1000,
    @upper numeric(18,0) = 1005;

select * from [messages]
where msg_id between @lower+1 and @upper;

Das von 1Ihnen hinzugefügte hat integerstandardmäßig den Datentyp . Beim Hinzufügen eines integerWerts zu einem numeric(18,0)Wert wendet SQL Server die Regeln mit der Priorität des Datentyps an . inthat eine niedrigere Priorität, so dass es in a konvertiert wird numeric(1,0). Ihre Anfrage entspricht der folgenden:

declare
    @lower numeric(18,0) = 1000,
    @upper numeric(18,0) = 1005;

select * from [messages]
where msg_id between @lower+CAST(1 AS NUMERIC(1, 0)) and @upper;

Ein anderer Satz von Regeln für Präzision, Skalierung und Länge wird angewendet, um den Datentyp des beteiligten Ausdrucks zu bestimmen @lower. Es ist nicht sicher, nur zu verwenden, NUMERIC(18,0)da dies überlaufen könnte (betrachten Sie 999.999.999.999.999.999 und 1 als Beispiel). Die hier geltende Regel lautet:

╔═══════════╦═════════════════════════════════════╦════════════════╗
 Operation           Result precision            Result scale * 
╠═══════════╬═════════════════════════════════════╬════════════════╣
 e1 + e2    max(s1, s2) + max(p1-s1, p2-s2) + 1  max(s1, s2)    
╚═══════════╩═════════════════════════════════════╩════════════════╝

Für Ihren Ausdruck lautet die resultierende Genauigkeit:

max(0, 0) + max(18 - 0, 1 - 0) + 1 = 0 + 18 + 1 = 19

und die resultierende Skala ist 0. Sie können dies überprüfen, indem Sie den folgenden Code in SQL Server ausführen:

declare
@lower numeric(18,0) = 1000,
@upper numeric(18,0) = 1005;

SELECT 
  SQL_VARIANT_PROPERTY(@lower+1, 'BaseType') lower_exp_BaseType
, SQL_VARIANT_PROPERTY(@lower+1, 'Precision') lower_exp_Precision
, SQL_VARIANT_PROPERTY(@lower+1, 'Scale') lower_exp_Scale;

Dies bedeutet, dass Ihre ursprüngliche Abfrage der folgenden entspricht:

declare
    @lower numeric(19,0) = 1000 + 1,
    @upper numeric(18,0) = 1005;

select * from [messages]
where msg_id between @lower and @upper;

SQL Server kann nur @lowerdann eine Clustered-Index-Suche durchführen, wenn der Wert implizit in konvertiert werden kann NUMERIC(18, 0). Es ist nicht sicher, einen NUMERIC(19,0)Wert in zu konvertieren NUMERIC(18,0). Infolgedessen wird der Wert als Prädikat anstatt als Suchprädikat angewendet. Eine Problemumgehung besteht darin, Folgendes zu tun:

declare
    @lower numeric(18,0) = 1000,
    @upper numeric(18,0) = 1005;

select * from [messages]
where msg_id between TRY_CAST(@lower+1 AS NUMERIC(18,0)) and @upper;

Diese Abfrage kann beide Filter als Suchprädikate verarbeiten:

Prädikate suchen

Mein Rat ist, den Datentyp in der Tabelle nach BIGINTMöglichkeit auf zu ändern . BIGINTerfordert ein Byte weniger als NUMERIC(18,0)und profitiert von nicht verfügbaren Leistungsoptimierungen, um eine NUMERIC(18,0)bessere Unterstützung für Bitmap-Filter zu ermöglichen.

Joe Obbish
quelle
4

Auf einem Ihrer Filter ( @lower+1) befindet sich ein Ausdruck , der die Engine dazu veranlasst, ein reguläres Prädikat anstelle eines Suchprädikats auszuführen (macht es nicht SARG-fähig).

Wenn Sie versuchen, den Wert des Filters vor der SELECTAnweisung zu ändern, werden Sie feststellen, dass beide Enden korrekt als Suchgrenzen verwendet werden.

declare
    @lower numeric(18,0) = 1000 + 1,
    @upper numeric(18,0) = 1005;

select * from messages
where msg_id between @lower and @upper;

Geben Sie hier die Bildbeschreibung ein

EzLo
quelle