我正在使用SqlParameter将值传递给我的查询
假设我的表格中有一个字段DOS numeric(3,0)
。
我的查询类似于SELECT * FROM ENT WHERE ENT.DOS = @DOS
。
问题是我不知道运行时字段ENT.DOS的类型(我的引擎非常复杂......),所以在创建SqlParameter时我没有指定任何精度或比例。< / p>
例如,如果我想要DOS = 1,则Precision将设置为1,Scale将设置为0。
它有效,这不重要。问题是:它很慢。
如果我指定了真实的精度和比例值,则查询运行得很快。
我可以在Management Studio中重现该问题:
在数据库中: DOS =数字(3,0) TICOD = char(1) PICOD =数字(8,0) PINO =数字(8,0) ENT_ID = int
慢版:
DECLARE @DOS as numeric(1,0) = 1
DECLARE @TICOD as varchar(1) = 'C'
DECLARE @PICOD as numeric(1,0) = 3
DECLARE @PINO as numeric(8,0) = 99999999
DECLARE @ENT_ID as numeric(8,0) = 99999999
SELECT TOP 1 *
FROM ENT AS ENT WITH(NOLOCK)
WHERE
(CE4 = '1') and (
( DOS = @DOS and TICOD = @TICOD and PICOD = @PICOD and PINO < @PINO ) or
( DOS = @DOS and TICOD = @TICOD and PICOD < @PICOD ) or
( DOS = @DOS and TICOD < @TICOD ) or
( DOS< @DOS )
)
order by DOS desc,TICOD desc,PICOD desc,PINO desc,ENT_ID desc
OPTION (FAST 1)
快速版(具有适当的参数类型):
declare @DOS as numeric(3,0) = 1
declare @TICOD as char(1) = 'C'
declare @PICOD as numeric(5,0) = 3
declare @PINO as numeric(8,0) = 99999999
declare @ENT_ID as int = 99999999
SELECT TOP 1 *
FROM ENT AS ENT WITH(NOLOCK)
WHERE
(CE4 = '1') and (
(DOS = @DOS and TICOD = @TICOD and PICOD = @PICOD and PINO = @PINO and ENT_ID < @ENT_ID ) or
( DOS = @DOS and TICOD = @TICOD and PICOD = @PICOD and PINO < @PINO ) or
( DOS = @DOS and TICOD = @TICOD and PICOD < @PICOD ) or
( DOS = @DOS and TICOD < @TICOD ) or
( DOS< @DOS )
)
order by DOS desc,TICOD desc,PICOD desc,PINO desc,ENT_ID desc
OPTION (FAST 1)
所以我的问题是:有没有办法为十进制类型声明SqlParameter而不指定Precision和Scale并且有一个快速运行的查询?
更新1:
执行计划:
更正数据类型 https://dl.dropboxusercontent.com/u/14168890/SqlParameter/exec_plan_fast.sqlplan
该计划显示了搜索谓词
1 Seek Keys1: End: DOS < @DOS,
2 Seek Keys1: Prefix: DOS = @DOS, End: TICOD < @TICOD,
3 Seek Keys1: Prefix: DOS, TICOD = @DOS, @TICOD, End: PICOD < @PICOD,
4 Seek Keys1: Prefix: DOS, TICOD, PICOD = @DOS, @TICOD, @PICOD, End: PINO < @PINO,
5 Seek Keys1: Prefix: DOS, TICOD, PICOD, PINO = @DOS, @TICOD, @PICOD, @PINO, End: ENT_ID < @ENT_ID
不匹配的数据类型: https://dl.dropboxusercontent.com/u/14168890/SqlParameter/exec_plan_slow.sqlplan
该计划显示扫描,谓词移动到过滤器中。
不匹配的数据类型(FORCESEEK)
: https://dl.dropboxusercontent.com/u/14168890/SqlParameter/exec_plan_forceseek.sqlplan
索引GTFENT_G是:DOS,TICOD,PICOD,PINO,ENT_ID
另外,我还测试过声明数值,实际上是整数类型的整数,慢速变快。
答案 0 :(得分:2)
在更快的版本中,五个搜索操作都会合并到计划中的一个搜索运算符中。
索引已订购DOS asc,TICOD asc,PICOD asc,PINO asc,ENT_ID asc
。
因为你想要TOP 1 ... ORDER BY DOS desc,TICOD desc,PICOD desc,PINO desc,ENT_ID desc
它只需要按降序键顺序寻找5个不同的索引范围,并在找到第一个匹配的行时立即停止。
在数据类型不匹配的较慢版本中,虽然所有谓词都可以单独查找,但似乎无法将它们组合到此单个搜索运算符中。具有FORCESEEK
提示的计划显示了由不同的搜索运算符执行的所有单独范围搜索。
此外,只要找到TOP 1
,它就不再利用索引顺序来避免排序或短路。搜索运算符都在阻塞排序运算符下,这意味着它们都是完整的评估。
显然最好只根据列定义使用正确的数据类型。如果真的是不可能的,那么一个解决方法似乎就是将它们声明为numeric(38,S)
。
S
是什么似乎并不重要。所以这可以基于数据中的小数位数。关键似乎是确保变量的数据类型至少与列的精度相等。
将@DOS
和@PICOD
声明为numeric(38,37)
或numeric(38,0)
两者都可以针对下表结构提供更快的计划形式。
CREATE TABLE [dbo].[ENT](
[FOO] [int] NOT NULL PRIMARY KEY,
[CE4] INT,
[DOS] [numeric](3,0) NOT NULL,
[TICOD] [char](1) NOT NULL,
[PICOD] [numeric](8, 0) NOT NULL,
[PINO] [numeric](8, 0) NOT NULL,
[ENT_ID] [int] NOT NULL,
UNIQUE NONCLUSTERED ([DOS] ASC, [TICOD] ASC, [PICOD] ASC, [PINO] ASC, [ENT_ID] ASC)
)
查询
DECLARE @DOS as numeric(38,37) = 1
DECLARE @TICOD as varchar(1) = 'C'
DECLARE @PICOD as numeric(38,37) = 3
DECLARE @PINO as numeric(8,0) = 99999999
DECLARE @ENT_ID as numeric(8,0) = 99999999
SELECT TOP 1 *
FROM ENT AS ENT WITH(NOLOCK, FORCESEEK)
WHERE
(
DOS = @DOS and TICOD = @TICOD and PICOD = @PICOD and PINO = @PINO and ENT_ID < @ENT_ID ) or
( DOS = @DOS and TICOD = @TICOD and PICOD = @PICOD and PINO < @PINO ) or
( DOS = @DOS and TICOD = @TICOD and PICOD < @PICOD ) or
( DOS = @DOS and TICOD < @TICOD ) or
( DOS< @DOS )
order by DOS desc,TICOD desc,PICOD desc,PINO desc,ENT_ID desc
答案 1 :(得分:0)
我不知道你是如何得到缓慢的例子,但如果DECLARE @TICOD as varchar(1) = 'C'
真的是DECLARE @TICOD as nvarchar(1) = 'C'
那么它不是咬你的小数的大小,实际上很可能您的TICOD
索引未被使用。
当您需要比较两种不同的数据类型时,需要将这两种类型中的一种转换为另一种数据类型,以便进行比较。 Sql Server使用this order来确定要转换的对象,列表中的较低项目会移动到列表中的较高项目。
所以看一下我们看到的列表
25. nvarchar (including nvarchar(max) )
26. nchar
27. varchar (including varchar(max) )
28. char
29. varbinary (including varbinary(max) )
因此,当您执行TICOD = @TICOD
时,在慢速版本中,它会将TICOD
转换为nvarchar
,而不是将@TICOD
转换为char
。< / p>
由于TICOD
已转换为,因此查询将不再使用使用TICOD
构建的任何索引,这是您的缓慢来源。
我会将输入类型从nvarchar
更改为char
或varchar
(因为使用varchar而不是nvarchar也会使用索引)传递SqlDbType.Char
或{{1在.NET中创建它时使用的任何SqlParameter
creator。
答案 2 :(得分:0)
你如何衡量缓慢?你在运行这两个查询之前清理了缓冲池和proc缓存吗?还有什么是确切的执行时间?更快的版本在几秒内完成,而较慢的版本需要几分钟/小时/未完成?
理想情况下,当您在查询的where子句中使用局部变量时,与实际的列数据类型相比,它们在数据类型中应该准确或大。如果数据类型恰好更小;然后优化器将无法使用索引,即使它存在因为隐式转换/转换/ round / trunc等等操作在Where条件的左侧。并且您可能知道参与条件应该使用的表列,因为它不应用任何显式/隐式函数。
尝试对所有NUMERIC变量使用更大的Precision和Scale,或者在获取值并在查询中使用之前,将 SQL_VARIANT数据类型用于本地声明的变量。这应该有助于避免隐式转换的开销。
另外两个变量完全声明为不同的数据类型。 ENT_ID是INT,您将其声明为NUMERIC。列TICOD是CHAR(1),您将其声明为Varchar(1)。我仍然可以使用TICOD,因为CHAR与VARCHAR相比,但INT Vs NUMERIC绝对不是很好。这就是为什么我建议SQL_VARIANT数据类型。