什么时候计算列?


29

何时确定计算列的值?

  • 何时检索值?
  • 值何时更改?
  • 还有一些时间吗?

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

Answers:


19

这取决于您如何定义计算列。甲PERSISTED计算列将被计算,然后被存储为表内的数据。如果您未将列定义为PERSISTED,则将在运行查询时计算该列。

请查看Aaron的答案以获取详细说明和证明。

Pinal Dave还详细描述了这一点,并在其系列文章中显示了存储证明:

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


6
如果它们被保留但查询计划使用的索引不覆盖该列怎么办?我不确定您是否会进行查找,或者只是进行即时计算并且当前无法对其进行测试。
马丁·史密斯

1
@Martin你是对的,在我的测试中,SQL Server选择了通过查找重新计算。
亚伦·伯特兰

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

和这个表:

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

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();

我看不到插入函数调用,只有选择。

现在,放下表格,然后再做一次,这次将列更改为PERSISTED

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

...
  MaskedName AS CONVERT(varchar(32), dbo.mask(Name)) PERSISTED,
...

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

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

对于非持久版本,我们可以看到仅在select中引用的函数:

在此处输入图片说明

在此处输入图片说明

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

在此处输入图片说明

在此处输入图片说明

现在,马丁在评论中提出了一个要点:并非总是如此。让我们创建一个不覆盖持久化计算列的索引,并运行使用该索引的查询,看看查找是否从现有持久化数据中获取数据,或者在运行时计算数据(删除并重新创建函数)和表在这里):

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

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

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

SELECT * FROM dbo.Floobs WITH (INDEX(x))
  WHERE Name LIKE 'S%';

我在函数统计信息中看到了其他执行,该计划没有说谎:

在此处输入图片说明

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


非常感谢,我从不质疑引擎在计算结果中的行为。
亚瑟D

8
@ArthurD这是一个优化程序的决定,(主要)基于每种选择的估计成本,请参见对另一个问题的回答
保罗·怀特说GoFundMonica

-1

这个问题的答案确实是“取决于”。我刚刚遇到了一个示例,其中SQL Server使用持久化计算列上的索引,但它仍在执行该函数,就好像这些值从一开始就没有持久化。它可能与列(nvarchar(37))的数据类型或表的大小(大约700万行)有关,但是persisted在这种特殊情况下,SQL Server决定忽略该关键字。

在这种情况下,表上的主键是TransactionID,它也是一个计算的持久化列。执行计划正在生成索引扫描,并且在只有700万行的表中,此简单查询需要花费2-3分钟的时间才能运行,因为该函数在每行上都再次运行,并且值似乎没有持久化索引。

用持久列创建表 显示执行功能的执行计划

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.