3

我正在使用 SqlParameter 将值传递给我的查询

假设我的DOS numeric(3,0)表中有一个字段。

我的查询类似于SELECT * FROM ENT WHERE ENT.DOS = @DOS.

问题是我在运行时不知道 ENT.DOS 字段的类型(我的引擎很复杂......),所以我在创建 SqlParameter 时没有指定任何精度或比例。

例如,如果我想要 DOS = 1,Precision 将设置为 1,Scale 设置为 0。

它有效,这不是重点。问题是:它很慢。

如果我指定真正的 Precision 和 Scale 值,查询会运行得很快。

我可以在 Management Studio 中重现该问题:

在数据库中: DOS = numeric(3,0) TICOD = char(1) PICOD = numeric(8,0) PINO = numeric(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

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

4

3 回答 3

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@PICODasnumeric(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
于 2013-11-02T19:45:02.217 回答
0

我不知道你是如何为你的慢例子得到你的声明的,但如果DECLARE @TICOD as varchar(1) = 'C'真的是DECLARE @TICOD as nvarchar(1) = 'C'那么它不是小数点的大小在咬你,实际上它很可能是你TICOD没有被使用的索引。

当您需要比较两种不同的数据类型时,需要将两种类型中的一种转换为另一种数据类型,以便进行比较。Sql Server 使用此顺序来确定要转换的对象,列表中较低的项目将移动到列表中较高的项目。

所以看看我们看到的列表

25. nvarchar (including nvarchar(max) )    
26. nchar
27. varchar (including varchar(max) )
28. char
29. varbinary (including varbinary(max) )

所以在你的慢版本中,当你这样做时,TICOD = @TICOD它会变成TICODanvarchar而不是@TICOD变成char.

因为TICOD已转换,查询将不再使用使用 构建的任何索引TICOD,这就是您的缓慢的来源。

我会将输入类型从更改nvarcharcharvarchar(因为使用 varchar 而不是 nvarchar 也会使用索引)传递SqlDbType.Char或传入您在 .NET 中创建它时使用SqlDbType.VarChar的任何SqlParameter创建者。

于 2013-10-29T16:59:22.200 回答
0

你如何衡量缓慢?在运行这两个查询之前,您是否清理了缓冲池和 proc 缓存?还有什么是确切的执行时间?更快的版本在几秒钟内完成,而较慢的版本需要几分钟/几小时/不完成?

理想情况下,当您在查询的 where 子句中使用局部变量时,与实际列数据类型相比,它们的数据类型应该是准确的或较大的。如果数据类型恰好更小;然后优化器将无法使用索引,即使它存在,因为隐式转换/转换/轮/截断等操作在 Where 条件的左侧。并且您可能知道参与 where 条件的表列,因为它没有应用任何显式/隐式函数。

尝试对所有 NUMERIC 变量使用更大的 Precision 和 Scale,或对本地声明的变量使用SQL_VARIANT 数据类型,然后再分配值并用于查询。这应该有助于避免隐式转换的开销。

还有两个变量完全声明为不同的数据类型。ENT_ID 是 INT,您将其声明为 NUMERIC。TICOD 列是 CHAR(1),您将其声明为 Varchar(1)。我仍然可以使用 TICOD,因为 CHAR 与 VARCHAR 相比,但 INT 与 NUMERIC 绝对不好。这就是为什么我建议使用 SQL_VARIANT 数据类型。

于 2013-10-29T19:06:00.877 回答