性能改进在生产中出错,在测试中运行良好

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)

请按要求查找查询计划:

执行计划 开启统计IO和时间后Reads的次数对比:

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)

问题是 - 如何测试它们,因为它们似乎适用于大多数场景,但仅在少数情况下失败。我不太了解应用程序及其供应商支持的应用程序,因此无法获得很多细节,例如业务逻辑如何为底层程序工作。

Pau*_*ite 7

您的重写与原始查询的语义不同,因此不同的结果并不奇怪。

这是真的,无需担心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)

db<>小提琴演示

然后,执行计划可以直接查找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仍然运行在一个字符串,它可能会产生意想不到的效果。在任何情况下,如果没有(不同的)计算列索引,表达式仍然无法查找。