Pav*_*dov 4 sql-server execution-plan
最近偶然发现以下问题:更改元数据会更改查询输出。
\n\n以下是如何获取它。
\n\nCREATE TABLE [dbo].[prod]\n(\n[ID] [tinyint] NOT NULL,\n[values] [tinyint] NOT NULL\n) \nGO\nRun Code Online (Sandbox Code Playgroud)\n\n以下查询始终返回 \xe2\x80\x98This is the end\xe2\x80\x99 (始终引发错误)。此外,过滤器应该是某种随机生成器。
\n\nBEGIN TRY\nSELECT [id] FROM \n (\n SELECT [id] as [id]\n FROM dbo.prod\n WHERE ([id]=1/0)\n ) t1\nWHERE\n-- Random generator 1 always returns false\n(FLOOR(RAND()*(1))=1)\n-- Random generator 2 does not always return false\n/*\nSUBSTRING( cast(NEWID() as varchar(max)), 1, 1)\nIN ('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', 'F')\n*/\n-- Random generator 3 does not always return false\n/*\nSUBSTRING(cast(cast( DATEPART(MILLISECOND,GETDATE()) as char(3)) as varchar(3)), 2, 1)\nIN ('1', '2', '3', '4', '5', '6', '7', '8', '9', '0')\n*/\nEND TRY\nBEGIN CATCH\nSELECT 'This is the end'\nEND CATCH;\nRun Code Online (Sandbox Code Playgroud)\n\n现在添加主键约束。注:无数据变化。
\n\nALTER TABLE dbo.prod\nADD CONSTRAINT PK_1 PRIMARY KEY NONCLUSTERED(id);\nRun Code Online (Sandbox Code Playgroud)\n\n现在同样的查询
\n\nBEGIN TRY\nSELECT [id] FROM \n (\n SELECT [id] as [id]\n FROM dbo.prod\n WHERE ([id]=1/0)\n ) t1\nWHERE\n(FLOOR(RAND()*(1))=1)\nEND TRY\nBEGIN CATCH\nSELECT 'This is the end'\nEND CATCH;\nRun Code Online (Sandbox Code Playgroud)\n\n永远不会返回错误。
\n\n可以理解的是,更改元数据可能会更改执行计划,但意外的是它会更改 query\xe2\x80\x99s 的输出。这种行为正确吗?
\n这种行为正确吗?
这是有意(有意)的行为;它是否“正确”更多的是见仁见智的问题。
一般要点是:SQL Server 不保证表达式求值的时间或次数。这种行为的存在是为了让查询优化器能够自由地找到良好的执行计划。
因此,如果表达式(或标量函数)的计算时间或次数存在依赖性,则查询优化可能会更改查询的输出或运行时行为。
在问题的示例中,运行时行为取决于查询优化器是否将每个表达式标识为运行时常量。执行引擎可能决定在查询执行开始之前评估(并缓存)运行时常量表达式。
如果优化器将表达式识别1/0为运行时常量,则显示计划输出的 XML 形式会将其显示为标记为ConstExprxxxx:

像这样的运行时常量可以在查询执行开始之前计算一次,并缓存结果,如前面提到的。如果发生这种情况,则在此预查询常量表达式求值期间将返回被零除错误。
将索引添加到表中恰好采取了通过查询优化的路线,这不会导致1/0被标记为运行时常量(ConstExprxxxx下面没有):

在这种特殊情况下,唯一性约束的存在意味着优化器认为缓存仅计算一次的表达式的结果没有任何好处。启动过滤器意味着永远不会评估错误的表达式。
这些表达评估行为并未ConstExpr在所有情况下都由标签的存在记录或完全确定。答案是不要编写依赖于执行引擎对表达式求值的时间或次数的代码。