我有一个聚集索引主键列,我正在对它进行范围查询。问题是扫描仅使用第一个范围部分作为搜索谓词,而将范围的另一侧作为剩余谓词。这导致读取所有行直到@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)
在这种情况下,实际执行计划显示:
表定义(简化):
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))
,但没有成功
从您的原始查询开始:
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)
。