什么时候计算计算列?

She*_*115 29 sql-server computed-column

计算列的值何时确定?

  • 何时取回值?
  • 值什么时候改变?
  • 其他时间?

我猜这是一个新手问题,因为我在搜索中没有找到任何东西。

Aar*_*and 34

这很容易证明你自己。我们可以创建一个包含使用标量用户定义函数的计算列的表,然后在更新和选择之前和之后检查计划和函数统计信息,并查看何时记录执行。

假设我们有这个功能:

CREATE FUNCTION dbo.mask(@x varchar(32))
RETURNS varchar(32) WITH SCHEMABINDING
AS
BEGIN
  RETURN (SELECT 'XX' + SUBSTRING(@x, 3, LEN(@x)-4) + 'XXXX');
END
GO
Run Code Online (Sandbox Code Playgroud)

还有这张表:

CREATE TABLE dbo.Floobs
(
  FloobID int IDENTITY(1,1),
  Name varchar(32),
  MaskedName AS CONVERT(varchar(32), dbo.mask(Name)),
  CONSTRAINT pk_Floobs PRIMARY KEY(FloobID),
  CONSTRAINT ck_Name CHECK (LEN(Name)>=8)
);
GO
Run Code Online (Sandbox Code Playgroud)

让我们sys.dm_exec_function_stats在插入之前和之后检查(SQL Server 2016 和 Azure SQL 数据库中的新增功能),然后在选择之后:

SELECT o.name, s.execution_count
FROM sys.dm_exec_function_stats AS s
INNER JOIN sys.objects AS o
ON o.[object_id] = s.[object_id]
WHERE s.database_id = DB_ID();

INSERT dbo.Floobs(Name) VALUES('FrankieC');

SELECT o.name, s.execution_count
FROM sys.dm_exec_function_stats AS s
INNER JOIN sys.objects AS o
ON o.[object_id] = s.[object_id]
WHERE s.database_id = DB_ID();

SELECT * FROM dbo.Floobs;

SELECT o.name, s.execution_count
FROM sys.dm_exec_function_stats AS s
INNER JOIN sys.objects AS o
ON o.[object_id] = s.[object_id]
WHERE s.database_id = DB_ID();
Run Code Online (Sandbox Code Playgroud)

我在插入上没有看到函数调用,只有在选择上。

现在,删除表并再次执行,这次将列更改为PERSISTED

DROP TABLE dbo.Floobs;
GO
DROP FUNCTION dbo.mask;
GO

...
  MaskedName AS CONVERT(varchar(32), dbo.mask(Name)) PERSISTED,
...
Run Code Online (Sandbox Code Playgroud)

我看到了相反的情况:我在插入上记录了一个执行,但在选择上没有记录。

没有足够现代的 SQL Server 版本来使用sys.dm_exec_function_stats?不用担心,这也包含在执行计划中

对于非持久化版本,我们只能在select中看到引用的函数:

在此处输入图片说明

在此处输入图片说明

虽然持久版本只显示插入时发生的计算:

在此处输入图片说明

在此处输入图片说明

现在,Martin 在评论中提出了一个重要观点:这并不总是正确的。让我们创建一个不覆盖持久化计算列的索引,并运行使用该索引的查询,看看查找是否从现有的持久化数据中获取数据,或者在运行时计算数据(drop and re-create function和表在这里):

CREATE INDEX x ON dbo.Floobs(Name);
GO

INSERT dbo.Floobs(name) 
  SELECT LEFT(name, 32) 
  FROM sys.all_columns 
  WHERE LEN(name) >= 8;
Run Code Online (Sandbox Code Playgroud)

现在,我们将运行一个使用索引的查询(实际上它在这种特定情况下默认使用索引,即使没有 where 子句):

SELECT * FROM dbo.Floobs WITH (INDEX(x))
  WHERE Name LIKE 'S%';
Run Code Online (Sandbox Code Playgroud)

我在函数统计信息中看到了额外的执行,并且计划没有说谎:

在此处输入图片说明

所以,答案是它取决于。在这种情况下,SQL Server 认为重新计算值比执行查找更便宜。这可能会因多种因素而改变,所以不要依赖它。无论是否使用用户定义的函数,这都可能发生在任一方向;我只在这里使用它是因为它更容易说明。

  • @ArthurD 这是一个优化器决定(主要是)基于每个替代方案的估计成本,请参阅 [我的回答](http://dba.stackexchange.com/a/124127/1192) 此处的另一个问题。 (8认同)

Art*_*r D 19

这取决于您如何定义计算列。将PERSISTED计算一个计算列,然后将其作为数据存储在表中。如果您没有将列定义为PERSISTED,它将在您的查询运行时进行计算。

请参阅Aaron 的回答以获得很好的解释和证明。

Pinal Dave还详细描述了这一点,并在他的系列中展示了存储证明:

SQL SERVER – 计算列 – 持久化和存储

  • 如果它们被持久化,但查询计划使用的索引不包括该列呢?我不确定您是否会进行查找,或者它是否只是即时计算并且目前无法对其进行测试。 (6认同)