不可查找的持久计算列上的索引

15 index sql-server optimization sql-server-2012 computed-column

我有Address一个名为 的表,它有一个名为 的持久计算列Hashkey。该列是确定性的,但不精确。它有一个不可查找的唯一索引。如果我运行这个查询,返回主键:

SELECT @ADDRESSID= ISNULL(AddressId,0)
FROM dbo.[Address]
WHERE HashKey = @HashKey
Run Code Online (Sandbox Code Playgroud)

我得到这个计划:

基本计划

如果我强制索引,我会得到更糟糕的计划:

力指数

如果我尝试同时强制索引和查找,则会出现错误:

由于此查询中定义的提示,查询处理器无法生成查询计划。在不指定任何提示且不使用的情况下重新提交查询SET FORCEPLAN

这仅仅是因为它不精确吗?我以为坚持就无所谓了?

有没有办法在不使其成为非计算列的情况下使该索引可查找?

有没有人有任何有关此信息的链接?

我无法发布实际的表创建,但这里有一个具有相同问题的测试表:

drop TABLE [dbo].[Test]

CREATE TABLE [dbo].[Test]
  (
     [test]        [VARCHAR](100) NULL,
     [TestGeocode] [geography] NULL,
     [Hashkey] AS CAST(
                        ( hashbytes
                            ('SHA', 
                                ( RIGHT(REPLICATE(' ', (100)) + isnull([test], ''), ( 100 )) ) 
                                + RIGHT(REPLICATE(' ', (100)) + isnull([TestGeocode].[ToString](), ''), ( 100 ))
                            ) 
                        ) AS BINARY(20)                                                                                                        
                      ) PERSISTED
    CONSTRAINT [UK_Test_HashKey] UNIQUE NONCLUSTERED([Hashkey])
  )    
GO    
DECLARE @Hashkey BINARY(20)

SELECT [Hashkey]
FROM   [dbo].[Test] WITH (FORCESEEK) /*Query processor could not produce a query plan*/
WHERE  [Hashkey] = @Hashkey 
Run Code Online (Sandbox Code Playgroud)

Mar*_*ith 12

问题似乎与[TestGeocode].[ToString]()返回max数据类型 ( nvarchar(max))的事实有关。

我也遇到了这个更简单版本的问题(更改c1tovarchar(8000)或 usingCOALESCE而不是ISNULL解决它的定义)

DROP TABLE dbo.Test

CREATE TABLE dbo.Test
  (
     c1        VARCHAR(
                          MAX    --Fails
                        --  8000 --Works fine
                          ) NULL,
     comp1 AS CAST(ISNULL(c1, 'ABC') AS VARCHAR(100))
    CONSTRAINT UK_Test_comp1 UNIQUE NONCLUSTERED(comp1)
  )

GO

DECLARE @comp1 VARCHAR(100)

SELECT comp1
FROM   dbo.Test WITH (FORCESEEK)
WHERE  comp1 = @comp1 
OPTION (QUERYTRACEON 3604, QUERYTRACEON 8606); 
Run Code Online (Sandbox Code Playgroud)

计算出的列引用会扩展到基础定义,然后再匹配回该列。这允许匹配计算列而根本不按名称引用它们,并且还允许简化对底层定义的操作。

ISNULL返回第一个参数的数据类型(VARCHAR(MAX)在我的示例中)。的返回类型也COALESCE将在VARCHAR(MAX)这里,但它似乎以一种避免问题的方式进行了不同的评估。

在查询成功的情况下,跟踪标志输出包括以下内容

ScaOp_Convert varchar(max) collate 49160,Null,Var,Trim,ML=65535

    ScaOp_Const TI(varchar collate 49160,Var,Trim,ML=3) 
                      XVAR(varchar,Owned,Value=Len,Data = (3,ABC))
Run Code Online (Sandbox Code Playgroud)

如果失败,则替换为

ScaOp_Identifier COL: ConstExpr1003 
Run Code Online (Sandbox Code Playgroud)

推测在它失败的情况下(隐式)CAST('ABC' AS VARCHAR(MAX))只完成一次,这被评估为运行时常量(更多信息)。然而,对这个运行时常量标签的引用,而不是实际的字符串文字值本身,会阻止它与计算列定义匹配。

此重写避免了查询中的问题

CREATE TABLE [dbo].[Test]
  (
     [test]        [VARCHAR](100) NULL,
     [TestGeocode] [geography] NULL,
     [Hashkey] AS CAST(
                        ( hashbytes
                            ('SHA', 
                                ( RIGHT(SPACE(100) + isnull([test], ''), 100) ) 
                                + RIGHT(SPACE(100) + isnull(CAST(RIGHT([TestGeocode].[ToString](),100) AS VARCHAR(100)), ''),100)
                            ) 
                        ) AS BINARY(20)                                                                                                        
                      ) PERSISTED
    CONSTRAINT [UK_Test_HashKey] UNIQUE NONCLUSTERED([Hashkey])
  )
Run Code Online (Sandbox Code Playgroud)


Dan*_*man 0

如果 的数据类型与@HashKey索引列的数据类型不匹配,您将因不可控制的表达式而出现这些症状。您可能需要在计算列表达式中显式地CAST强制所需的数据类型。

根据您的重现,我怀疑这是一个错误。我还提交了一个 Connect bug,即“未使用计算列索引”,以及 Martin 的解决方法的一个版本。请随意投票。