从您的原始查询开始:
declare
@lower numeric(18,0) = 1000,
@upper numeric(18,0) = 1005;
select * from [messages]
where msg_id between @lower+1 and @upper;
你添加的1默认具有整数数据类型。当将一个整数值添加到一个numeric(18,0)值时,SQL Server会应用data type precedence的规则。int的优先级较低,因此它会被转换为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;
对涉及@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) ║
╚═══════════╩═════════════════════════════════════╩════════════════╝
对于您的表达式,结果的精度为:
max(0, 0) + max(18 - 0, 1 - 0) + 1 = 0 + 18 + 1 = 19
并且结果的比例为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;
这意味着你的原始查询等同于以下内容:
declare
@lower numeric(19,0) = 1000 + 1,
@upper numeric(18,0) = 1005;
select * from [messages]
where msg_id between @lower and @upper;
如果值能够隐式转换为NUMERIC(18, 0),SQL Server只能使用@lower进行聚集索引搜索。将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;
该查询可以同时处理过滤器和搜索谓词:

我的建议是,如果可能的话,将表中的数据类型更改为BIGINT。 BIGINT比NUMERIC(18,0)少占用一个字节,并且可以享受到NUMERIC(18,0)无法获得的性能优化,包括更好的位图过滤支持。