如果十进制参数的精度错误,则查询速度慢

时间:2013-10-29 16:06:25

标签: c# sql-server ado.net

我正在使用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

enter image description here

该计划显示了搜索谓词

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

enter image description here

该计划显示扫描,谓词移动到过滤器中。

不匹配的数据类型(FORCESEEK) https://dl.dropboxusercontent.com/u/14168890/SqlParameter/exec_plan_forceseek.sqlplan

enter image description here

索引GTFENT_G是:DOS,TICOD,PICOD,PINO,ENT_ID

另外,我还测试过声明数值,实际上是整数类型的整数,慢速变快。

3 个答案:

答案 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更改为charvarchar(因为使用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数据类型。