同一列上的聚集索引“Seek predicate”和“predicate”

Abi*_*lov 8 index sql-server

我有一个聚集索引主键列,我正在对它进行范围查询。问题是扫描仅使用第一个范围部分作为搜索谓词,而将范围的另一侧作为剩余谓词。这导致读取所有行直到@upper 限制

我正在为范围使用两个参数:

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

select * from messages
where msg_id between @lower+1 and @upper;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,实际执行计划显示:

  • 谓词:messages.msg_id >=lower
  • 寻求谓词:messages.msg_id < @upper
  • 读取的行数:1005

表定义(简化):

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
) 
Run Code Online (Sandbox Code Playgroud)

更多信息

当与常量而不是变量一起使用时,两个谓词都是 'Seek' 尝试过Option (Optimize for (@lower=1000)),但没有成功

Joe*_*ish 9

从您的原始查询开始:

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

select * from [messages]
where msg_id between @lower+1 and @upper;
Run Code Online (Sandbox Code Playgroud)

1您添加了一个数据类型integer的默认值。向integer值添加值时,numeric(18,0)SQL Server 应用数据类型优先级规则。int具有较低的优先级,因此它被转换为 a numeric(1,0)。您的查询等效于以下内容:

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;
Run Code Online (Sandbox Code Playgroud)

应用一组不同的关于精度、小数位数和长度的规则来确定涉及 的表达式的数据类型@lower。仅仅使用是不安全的,NUMERIC(18,0)因为它可能会溢出(以 999,999,999,999,999,999 和 1 为例)。这里适用的规则是:

????????????????????????????????????????????????????????????????????
? Operation ?          Result precision           ? Result scale * ?
????????????????????????????????????????????????????????????????????
? e1 + e2   ? max(s1, s2) + max(p1-s1, p2-s2) + 1 ? max(s1, s2)    ?
????????????????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

对于您的表达式,结果精度为:

max(0, 0) + max(18 - 0, 1 - 0) + 1 = 0 + 18 + 1 = 19
Run Code Online (Sandbox Code Playgroud)

并且结果比例为 0。您可以通过在 SQL Server 中运行以下代码来验证这一点:

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;
Run Code Online (Sandbox Code Playgroud)

这意味着您的原始查询等效于以下内容:

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

select * from [messages]
where msg_id between @lower and @upper;
Run Code Online (Sandbox Code Playgroud)

SQL Server只能用@lower做一个聚集索引时争取值可以隐式转换为NUMERIC(18, 0)。将NUMERIC(19,0)值转换为NUMERIC(18,0). 因此,该值被用作谓词而不是作为搜索谓词。一种解决方法是执行以下操作:

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;
Run Code Online (Sandbox Code Playgroud)

该查询可以将两个过滤器作为搜索谓词处理:

寻找谓词

我的建议是BIGINT尽可能将表中的数据类型更改为。BIGINT需要的字节数比对位图过滤器的更好支持所NUMERIC(18,0)无法实现的性能优化少一个字节并从中受益NUMERIC(18,0)