Ava*_*rkx 8 sql-server sql-server-2008-r2
情景
曾几何时,一家参与 ETL 过程的小公司有一个暂存数据库,充当来自多个第三方来源的各种格式文件的接收目录。E 是通过 DTS 包处理的,几乎没有用于审计或控制的控制结构,但被认为“足够好”,并且出于所有意图和目的,它确实如此。
E 部分提供的数据旨在供单个应用程序使用,由少数年轻有能力的程序员开发和管理。尽管缺乏当时数据仓库技术的经验或知识,但他们从应用程序代码中提出并创建了自己的 T 和 L 流程。这些初出茅庐的软件工程师发明了一种外人可能称之为“不太理想的轮子”的东西,但以“足够好”作为永远存在的服务水平,他们能够提供一个操作框架。
有一段时间,紧耦合领域的一切都很好,Staging 目录以十几个第三方的数据为食,反过来又由应用程序提供。随着应用程序的增长,它的胃口也在增长,但是随着熟练的白骑士开发人员对系统的监视,这些胃口很快得到了解决,在许多情况下,甚至很好。
但当然,黄金时代不可能永远持续下去。随着申请成功,生意兴隆,生意兴隆。随着它的发展,Staging 环境和应用程序被迫随之发展。尽管他们保持警惕,少数英雄开发人员无法跟上维护现在庞大的系统的步伐,而消费者已经有权获得他们的数据。这不再是他们需要甚至想要什么的问题,而是民众觉得他们只是应得的,甚至要求更多。
凭借装满赃物的金库,该企业进入市场,聘请开发人员和管理员来帮助支持不断增长的系统。各种精神的雇佣兵涌入公司,但随着这种增长的突增,几乎没有可用的专家指导。新的开发人员和管理员努力理解自制套件的复杂性,直到挫折导致全面战争。每个部门都开始尝试单独解决每一个问题,更多的是相互对抗而不是相互合作。一个项目或计划将以几种不同的方式实施,每种方式都略有不同。事实证明,对于一些白骑士来说,这一切的压力太大了,随着他们的倒台,帝国崩溃了。很快,系统就陷入了混乱,
尽管这些承诺领域已转变为血腥的意大利面条式代码,但该公司还是忍受了。毕竟,这是“足够好”。
挑战
之后又经历了几次政权更迭和招聘热潮,我发现自己在公司工作。伟大的战争已经过去很多年了,但所造成的破坏仍然非常明显。我设法解决了系统 E 部分的一些弱点并添加了一些控制表,同时以将 DTS 包升级到 SSIS 为幌子,现在一些实际的数据仓库专业人员正在使用它们,因为他们创建了一个正常的并记录 T 和 L 更换。
第一个障碍是以不会截断值或更改本机数据类型的方式从第三方文件导入数据,但还包括一些用于重新加载和清除的控制键。这一切都很好,但应用程序需要能够以无缝、透明的方式访问这些新表。DTS 包可以填充一个表,然后由应用程序直接读取。出于 QA 的原因,SSIS 升级需要并行完成,但这些新包包括各种控制键并利用分区方案,更不用说实际的元数据更改本身就足以保证完全有一个新表,所以新表用于新的 SSIS 包。
随着可靠的数据导入现在工作并被仓储团队使用,真正的挑战在于将新数据提供给直接访问 Staging 环境的应用程序,对应用程序代码的影响最小(也称为“否”)。为此,我选择使用视图,将表重命名为dbo.DailyTransaction
to并重用视图dbo.DailyTranscation_LEGACY
的dbo.DailyTransaction
对象名称,这实际上只是选择了现在的所有内容LEGACY
指定的表。由于重新加载这些表中包含的年份数据从业务的角度来看不是一个选项,随着新的 SSIS 填充和分区表进入生产,旧的 DTS 导入被关闭,应用程序需要能够也可以访问新表中的新数据。此时,视图会更新以在从新表(例如,dbo.DailyTransactionComplete
,例如)当它可用时从旧表中选择当它不可用时。
实际上,正在执行以下操作:
CREATE VIEW dbo.DailyTransaction
AS SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.DailyTransactionComplete
UNION ALL
SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.DailyTransaction_LEGACY l
WHERE NOT EXISTS ( SELECT 1
FROM dbo.DailyTransactionComplete t
WHERE t.FileDate = l.FileDate );
Run Code Online (Sandbox Code Playgroud)
虽然从逻辑上讲,这在许多聚合情况下根本表现不佳,通常会导致执行计划对遗留表中的数据执行完整索引扫描。这对于几千万条记录来说可能没问题,但对于几十亿条记录来说则不然。由于实际上是后者,我不得不求助于……“有创意”,这导致我创建了一个索引视图。
下面是一些测试情况下,我已经设置了包括FileDate
控制键已经被移植到了数据仓库兼容DateCode_FK
端口,用于说明如何彻底一点我关心对新表的查询是优化搜索暂且:
USE tempdb;
GO
SET NOCOUNT ON;
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DailyTransaction_LEGACY'
AND type = 'U' )
BEGIN
--DROP TABLE dbo.DailyTransaction_LEGACY;
CREATE TABLE dbo.DailyTransaction_LEGACY
(
DailyTransaction_PK BIGINT IDENTITY( 1, 1 ) NOT NULL,
FileDate DATETIME NOT NULL,
Foo INT NOT NULL
);
INSERT INTO dbo.DailyTransaction_LEGACY ( FileDate, Foo )
SELECT DATEADD( DAY, ( 1 - ROW_NUMBER()
OVER( ORDER BY so1.object_id ) - 800 ) % 1000,
CONVERT( DATE, GETDATE() ) ),
so1.object_id % 1000 + so2.object_id % 1000
FROM sys.all_objects so1
CROSS JOIN sys.all_objects so2;
ALTER TABLE dbo.DailyTransaction_LEGACY
ADD CONSTRAINT PK__DailyTrainsaction
PRIMARY KEY CLUSTERED ( DailyTransaction_PK )
WITH ( DATA_COMPRESSION = PAGE, FILLFACTOR = 100 );
END;
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DailyTransactionComplete'
AND type = 'U' )
BEGIN
--DROP TABLE dbo.DailyTransactionComplete;
CREATE TABLE dbo.DailyTransactionComplete
(
DailyTransaction_PK BIGINT IDENTITY( 1, 1 ) NOT NULL,
DateCode_FK INTEGER NOT NULL,
Foo INTEGER NOT NULL
);
INSERT INTO dbo.DailyTransactionComplete ( DateCode_FK, Foo )
SELECT TOP 100000
CONVERT( INTEGER, CONVERT( VARCHAR( 8 ), DATEADD( DAY,
( 1 - ROW_NUMBER() OVER( ORDER BY so1.object_id ) ) % 100,
GETDATE() ), 112 ) ),
so1.object_id % 1000
FROM sys.all_objects so1
CROSS JOIN sys.all_objects so2;
ALTER TABLE dbo.DailyTransactionComplete
ADD CONSTRAINT PK__DailyTransaction
PRIMARY KEY CLUSTERED ( DateCode_FK, DailyTransaction_PK )
WITH ( DATA_COMPRESSION = PAGE, FILLFACTOR = 100 );
END;
GO
Run Code Online (Sandbox Code Playgroud)
在我的本地沙箱上,上面的内容为我提供了一个包含大约 440 万行的旧表和一个包含 10 万行的新表,其中DateCode_FK
/FileDate
值有一些重叠。
一MAX( FileDate )
对没有额外的指标遗留表什么我所期望的运行。
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput DATETIME;
SELECT @ConsumeOutput = MAX( FileDate )
FROM dbo.DailyTransaction_LEGACY;
SET STATISTICS IO, TIME OFF;
GO
Run Code Online (Sandbox Code Playgroud)
表“DailyTransaction_LEGACY”。扫描计数1,逻辑读9228,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。
SQL Server 执行时间:CPU 时间 = 889 毫秒,已用时间 = 886 毫秒。
在桌子上扔一个简单的索引会使事情变得更好。仍然是扫描,但扫描的是一条记录,而不是 440 万条记录。我很酷。
CREATE NONCLUSTERED INDEX IX__DailyTransaction__FileDate
ON dbo.DailyTransaction_LEGACY ( FileDate );
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput DATETIME;
SELECT @ConsumeOutput = MAX( FileDate )
FROM dbo.DailyTransaction_LEGACY;
SET STATISTICS IO, TIME OFF;
GO
Run Code Online (Sandbox Code Playgroud)
SQL Server 解析和编译时间:CPU 时间 = 0 毫秒,已用时间 = 1 毫秒。表“DailyTransaction_LEGACY”。扫描计数 1,逻辑读取 3,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。
SQL Server 执行时间:CPU 时间 = 0 毫秒,已用时间 = 0 毫秒。
现在,创建视图以便开发人员不必更改任何代码,因为这显然是我们所知的世界末日。一场大灾变。
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DailyTransaction'
AND type = 'V' )
BEGIN
EXEC( 'CREATE VIEW dbo.DailyTransaction AS SELECT x = 1;' );
END;
GO
ALTER VIEW dbo.DailyTransaction
AS SELECT DailyTransaction_PK, FileDate = CONVERT(
DATETIME, CONVERT( VARCHAR( 8 ), DateCode_FK ), 112 ), Foo
FROM dbo.DailyTransactionComplete
UNION ALL
SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.DailyTransaction_LEGACY l
WHERE NOT EXISTS ( SELECT 1
FROM dbo.DailyTransactionComplete t
WHERE CONVERT( DATETIME, CONVERT( VARCHAR( 8 ),
t.DateCode_FK ), 112 ) = l.FileDate );
GO
Run Code Online (Sandbox Code Playgroud)
是的,子查询很糟糕,但这不是问题,当真正的问题得到解决时,我可能会简单地创建一个持久化的计算列并为此目的抛出一个索引。所以事不宜迟,
问题
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput1 DATETIME;
SELECT @ConsumeOutput1 = MAX( FileDate )
FROM dbo.DailyTransaction;
SET STATISTICS IO, TIME OFF;
GO
Run Code Online (Sandbox Code Playgroud)
SQL Server 解析和编译时间:CPU 时间 = 0 毫秒,经过时间 = 4 毫秒。表“DailyTransaction_LEGACY”。扫描计数 1,逻辑读取 11972,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。表 'Worktable'。扫描计数 0,逻辑读取 0,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。表 'Workfile'。扫描计数 0,逻辑读取 0,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。表 'DailyTransactionComplete'。扫描计数 2,逻辑读取 620,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。
SQL Server 执行时间:CPU 时间 = 983 毫秒,已用时间 = 983 毫秒。
哦,我明白了,Sql Server 试图告诉我我在做什么是愚蠢的。虽然我基本同意,但这并没有改变我的困境。这对于在谓词中包含FileDate
ondbo.DailyTransaction
视图的查询实际上非常有效,但是虽然该MAX
计划已经足够糟糕,但该TOP
计划将整个事情向南运行。真正的南方。
SET STATISTICS IO, TIME ON;
SELECT TOP 10 FileDate
FROM dbo.DailyTransaction
GROUP BY FileDate
ORDER BY FileDate DESC
SET STATISTICS IO, TIME OFF;
GO
Run Code Online (Sandbox Code Playgroud)
表 'DailyTransactionComplete'。扫描计数 2,逻辑读取 1800110,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。表 'DailyTransaction_LEGACY'。扫描计数 1,逻辑读取 1254,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。表 'Worktable'。扫描计数 0,逻辑读取 0,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。表 'Workfile'。扫描计数 0,逻辑读 0,物理读 0,预读 0,lob 逻辑读 0,lob 物理读 0,lob 预读 0。
SQL Server 执行时间:CPU 时间 = 109559 毫秒,已用时间 = 109664 毫秒。
我之前提到过“有创意”,这可能是误导。我想说的是“更傻”,所以我尝试期间集聚操作,使这个观点工作已经到创建的意见dbo.DailyTransactionComplete
和 dbo.DailyTransaction_LEGACY
表,模式绑定和索引后者,然后在另一视图中使用这些观点与NOEXPAND
提示在遗留视图上。虽然它现在或多或少地为它需要做的事情工作,但我发现整个“解决方案”非常令人沮丧,最终结果如下:
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'v_DailyTransactionComplete'
AND type = 'V' )
BEGIN
EXEC( 'CREATE VIEW dbo.v_DailyTransactionComplete AS SELECT x = 1;' );
END;
GO
ALTER VIEW dbo.v_DailyTransactionComplete
AS SELECT DailyTransaction_PK, FileDate = CONVERT( DATETIME,
CONVERT( VARCHAR( 8 ), DateCode_FK ), 112 ),
Foo
FROM dbo.DailyTransactionComplete;
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'v_DailyTransaction_LEGACY'
AND type = 'V' )
BEGIN
EXEC( 'CREATE VIEW dbo.v_DailyTransaction_LEGACY AS SELECT x = 1;' );
END;
GO
ALTER VIEW dbo.v_DailyTransaction_LEGACY
WITH SCHEMABINDING
AS SELECT l.DailyTransaction_PK,
l.FileDate,
l.Foo,
CountBig = COUNT_BIG( * )
FROM dbo.DailyTransaction_LEGACY l
INNER JOIN dbo.DailyTransactionComplete n
ON l.FileDate <> CONVERT( DATETIME, CONVERT( VARCHAR( 8 ),
n.DateCode_FK ), 112 )
GROUP BY l.DailyTransaction_PK,
l.FileDate,
l.Foo;
GO
CREATE UNIQUE CLUSTERED INDEX CI__v_DailyTransaction_LEGACY
ON dbo.v_DailyTransaction_LEGACY ( FileDate, DailyTransaction_PK )
WITH ( DATA_COMPRESSION = PAGE, FILLFACTOR = 80 );
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DailyTransaction'
AND type = 'V' )
BEGIN
EXEC( 'CREATE VIEW dbo.DailyTransaction AS SELECT x = 1;' );
END;
GO
ALTER VIEW dbo.DailyTransaction
AS SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.v_DailyTransactionComplete
UNION ALL
SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.v_DailyTransaction_LEGACY WITH ( NOEXPAND );
GO
Run Code Online (Sandbox Code Playgroud)
强制优化器使用索引视图提供的索引会使MAX
和TOP
问题消失,但必须有更好的方法来实现我在这里尝试做的事情。绝对任何建议/责骂将不胜感激!
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput1 DATETIME;
SELECT @ConsumeOutput1 = MAX( FileDate )
FROM dbo.DailyTransaction;
SET STATISTICS IO, TIME OFF;
GO
Run Code Online (Sandbox Code Playgroud)
表“v_DailyTransaction_LEGACY”。扫描计数 1,逻辑读取 3,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。表 'DailyTransactionComplete'。扫描计数 1,逻辑读取 310,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。
SQL Server 执行时间:CPU 时间 = 31 毫秒,已用时间 = 36 毫秒。
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput1 DATETIME;
SELECT TOP 10 @ConsumeOutput1 = FileDate
FROM dbo.DailyTransaction
GROUP BY FileDate
ORDER BY FileDate DESC
SET STATISTICS IO, TIME OFF;
GO
Run Code Online (Sandbox Code Playgroud)
表“v_DailyTransaction_LEGACY”。扫描计数 1,逻辑读取 101,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。表 'Worktable'。扫描计数 0,逻辑读取 0,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。表 'Workfile'。扫描计数 0,逻辑读取 0,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。表 'DailyTransactionComplete'。扫描计数 1,逻辑读取 310,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。
SQL Server 执行时间:CPU 时间 = 63 毫秒,已用时间 = 66 毫秒。
特尔;博士:
帮助我了解我需要做什么才能对我提到的第一个视图进行聚合查询,并在合理的时间内以合理的 I/O 资源利用率运行。
重写NOT EXISTS
为DISTINCT
不等式连接确实允许对视图进行索引,但有充分的理由不这样做。
在视图上构建索引而生成的执行计划不可避免地很糟糕。这种不等式迫使嵌套循环物理连接,除了一个值之外,它是一种交叉连接。假设连接列不可为空(如示例代码中所示),则使用不同或等效的分组依据折叠乘积将产生正确的结果,但它永远不会有效。随着时间的推移以及涉及的表变得越来越大,这种低效率只会变得更糟。
类似的问题会影响任何影响视图引用的表的 DML 语句的执行计划(因为视图必须始终与 SQL Server 中的基表同步)。查看为在任一表中添加或修改单行而生成的执行计划,以了解我的意思。
从较高的层面来看,您面临的问题是 SQL Server 查询优化器并不总是针对包含UNION ALL
. 我们认为理所当然的许多优化(例如MAX
-> TOP (1)
)根本没有在 union all 中实现。
对于你解决的每个问题,你都会发现另一种情况,正常的和预期的优化没有发生,导致执行计划的性能令人绝望。显而易见的解决方案是避免在视图中使用 union。在您的案例中如何实现这一点取决于细节,尽管问题中有详细信息,但可能只有您知道。
如果有空间,一种解决方案是单独维护表complete
和legacy
基表(包括不存在逻辑)。这确实会导致数据重复,并带来同步问题,但根据我的经验,这些问题比尝试让联合视图在所有(甚至大多数)情况下为各种查询生成良好的执行计划更容易稳健地解决。
SQL Server 提供了许多功能来协助数据同步,我相信您已经知道,包括更改跟踪、更改数据捕获、触发器……等等。实施的具体细节超出了本论坛的范围。重要的一点是向优化器提供基表,而不是联合所有视图。
归档时间: |
|
查看次数: |
132 次 |
最近记录: |