Lea*_*min 4 performance sql-server sql-server-2014 query-performance
我们有一个由供应商支持的应用程序,并且有一段代码在一个过程中执行非常繁重的逻辑读取和耗时,因此我建议他们稍微调整查询以减少 IO 和时间,它在在数据和性能方面进行测试,但是在生产中失败并返回不同的数据,我们不得不回滚更改。需要您的专家建议。
这是在测试环境中对数据进行了3个多月的测试,我们从未出现过任何数据问题。部署后立即开始在生产中很少失败,并产生不一致的数据。
现有查询:
SELECT @Ref= CAST(MAX(ISNULL(CAST(ref_clnt AS INT),0))+1 AS VARCHAR(10))
FROM table_name WITH(NOLOCK)
WHERE s_mode='value'
Run Code Online (Sandbox Code Playgroud)
建议查询:
SELECT @Ref = ref_clnt+1 FROM table_name WITH(NOLOCK)
WHERE RefNo = (SELECT MAX(RefNo) FROM table_name WHERE s_mode = 'value')
Run Code Online (Sandbox Code Playgroud)
表的DDL如下:
CREATE TABLE [dbo].[table_name](
[RefNo] [dbo].[udt_RefNo] NOT NULL,
[S_Mode] [varchar](10) NOT NULL,
[ref_clnt] [varchar](50) NULL)
CONSTRAINT [PK_table_name] PRIMARY KEY CLUSTERED
(
[RefNo] ASC
)
Run Code Online (Sandbox Code Playgroud)
仅提供查询中使用的定义中的那些列。
Udt_RefNo 是用户定义的数据类型:
CREATE TYPE [dbo].[udt_RefNo] FROM [char](16) NOT NULL
GO
Run Code Online (Sandbox Code Playgroud)
SQL Server 版本:Microsoft SQL Server 2014 (SP3) 版权所有 (c) Microsoft Corporation Enterprise Edition(64 位)。
非聚集索引覆盖列如下图:
CREATE NONCLUSTERED INDEX [ncidx_table_name_1] ON [dbo].[table_name]
(
[S_Mode] ASC,
[S_Status] ASC
)
INCLUDE ( [ref_clnt])
Run Code Online (Sandbox Code Playgroud)
请按要求查找查询计划:
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.
(1 row affected)
Table 'table_name'. Scan count 1, logical reads 2732, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row affected)
SQL Server Execution Times:
CPU time = 157 ms, elapsed time = 161 ms.
(1 row affected)
Table 'table_name'. Scan count 1, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row affected)
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.
Run Code Online (Sandbox Code Playgroud)
供应商已返回建议作为
SELECT MAX(ISNULL(ref_clnt,0))+1 FROM table_name WITH(NOLOCK) WHERE S_Mode='value'
Run Code Online (Sandbox Code Playgroud)
问题是 - 如何测试它们,因为它们似乎适用于大多数场景,但仅在少数情况下失败。我不太了解应用程序及其供应商支持的应用程序,因此无法获得很多细节,例如业务逻辑如何为底层程序工作。
您的重写与原始查询的语义不同,因此不同的结果并不奇怪。
这是真的,无需担心NOLOCK提示,即使(以某种方式)保证最高ref_clnt值与具有最高RefNo值的行相关联。请参阅此 db<>fiddle 示例。
与计算机打交道时精度很重要,因此您需要仔细考虑数据类型和边缘情况。最大RefNo计算将使用字符串排序语义,因此 '999' 排序高于 '1000'。查询之间还有其他几个重要的区别。我不打算列出所有这些,但NULL处理是另一个例子。
两个版本的代码也存在许多错误。如果ref_clnt返回 -1000000000 或更低的任何值,原始值将失败,因为它不适合varchar(10). 符号使长度为 11。
安全改进原始版本代码的最简单方法是在计算列上添加索引:
ALTER TABLE dbo.table_name
ADD ref_cc AS
ISNULL(CAST(ref_clnt AS integer), 0);
CREATE NONCLUSTERED INDEX i
ON dbo.table_name (S_Mode, ref_cc);
Run Code Online (Sandbox Code Playgroud)
然后,执行计划可以直接查找ref_clnt给定S_Mode值的最高行(按整数排序):
供应商的原始 SQL 的质量可能仍然存在争议,但至少它会运行得更快并产生相同的结果。
供应商的新建议:
SELECT MAX(ISNULL(ref_clnt,0))+1 FROM table_name WITH(NOLOCK) WHERE S_Mode='value'
Run Code Online (Sandbox Code Playgroud)
...仍然有问题,至少在理论上,因为ISNULL使用第一个参数的数据类型,所以整数文字0被隐式转换为varchar(50)。
值check_expression如果不是,则返回NULL; 否则,replacement_value之后它被隐式转换为的类型被返回check_expression,如果类型是不同的。replacement_value可以被截断如果replacement_value长于check_expression。
在MAX仍然运行在一个字符串,它可能会产生意想不到的效果。在任何情况下,如果没有(不同的)计算列索引,表达式仍然无法查找。