场景
曾几何时,一家小公司中有一个Staging数据库,该数据库正在参与ETL流程,充当来自多个第三方来源的各种格式文件的接收目录。E是通过DTS程序包处理的,几乎没有用于审计或控制的控制结构,但是被认为“足够好”,并且从所有意图和目的来看都是如此。
E部分提供的数据旨在供单个应用程序使用,并由少数年轻有能力的程序员开发和管理。尽管他们缺乏当时的数据仓库技术经验或知识,但他们还是根据应用程序代码创建并创建了自己的T和L流程。这些崭露头角的软件工程师大胆创新,发明了局外人所谓的“低于理想的轮子”,但是凭借“足够好”作为永远存在的服务水平,他们能够提供一个操作框架。
一段时间以来,在紧密耦合的领域中一切都很好,暂存目录以12个第三方的数据为美食,反过来又由应用程序提供。随着应用程序的增长,它的胃口也随之增加,但是随着熟练的白骑士开发人员对系统的观察,这些胃口很快得到了解决,在很多情况下甚至都得到了很好的解决。
但是,黄金时代不可能永远持续下去。随着成功应用程序的繁荣,业务不断发展。随着它的增长,登台环境和应用程序也随之增长。尽管他们保持了警惕,但只有少数英雄开发人员无法跟上现在扩展的系统的维护,并且消费者已经有权使用其数据。不再是他们需要甚至想要的事了,但是民众感到他们只是应得的,甚至要求更多。
有了很多钱,武装起来的公司就进入了市场,雇用了开发人员和管理员来帮助支持不断发展的系统。各式各样的雇佣兵蜂拥而至,但是随着这种增长突飞猛进,可用的专家指导却很少。新的开发人员和管理人员都在努力理解自制套件的复杂性,直到挫折导致全面的战争。每个部门都开始尝试单独解决每个问题,相互之间的工作比相互之间的工作更多。一个项目或计划将以几种不同的方式实施,每种方式与下一个略有不同。对于某些白骑士来说,这一切的压力实在太大了,当他们沦陷时,帝国崩溃了。不久,系统陷入混乱,
尽管这些承诺可以转换成意大利面条代码的领域已经发生了变化,但该公司还是坚持了下来。毕竟是“足够好”。
挑战
后来发生了一些政权更迭和招聘热潮,我发现自己从事公司的雇用。自大战以来已经有很多年了,但是所造成的损失仍然非常明显。我设法解决了系统E部分中的一些弱点,并以将DTS软件包升级到SSIS为幌子添加了一些控制表,现在一些实际的数据仓库专业人员正在使用它们创建正常的并记录了T和L替换。
第一个障碍是从第三方文件中导入数据,这种方式不会截断值或更改本机数据类型,但还包括一些用于重新加载和清除的控制键。这一切都很好,但是应用程序需要能够以无缝,透明的方式访问这些新表。DTS包可以填充一个表,然后由应用程序直接读取该表。由于QA的原因,SSIS升级需要并行进行,但是这些新软件包包括各种控制键,并且还利用了分区方案,更不用说实际的元数据更改足以显着保证一个新表的总和,因此新表用于新的SSIS包。
随着可靠的数据导入现已开始运作,并且由仓库团队使用,真正的挑战在于将新数据提供给直接访问暂存环境的应用程序,而对应用程序代码的影响最小(也称为“否”)。为此,我选择使用视图,重命名表(例如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 );
虽然从逻辑上讲是合理的,但在许多聚合情况下根本无法很好地执行,通常会导致执行计划对遗留表中的数据执行完整索引扫描。对于几十亿条记录来说这可能很好,但对于几十亿条记录则不是那么好。由于实际上是后者,因此我不得不求助于...“创意”,从而引导我创建索引视图。
下面是一些测试情况下,我已经设置了包括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
在我的本地沙箱上,以上内容使我得到了一个具有约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
表“ 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
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
是的,子查询很糟糕,但这不是问题,我可能会简单地创建一个持久化的计算列,并在解决实际问题时为此目的在其上添加索引。因此,事不宜迟,
问题
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput1 DATETIME;
SELECT @ConsumeOutput1 = MAX( FileDate )
FROM dbo.DailyTransaction;
SET STATISTICS IO, TIME OFF;
GO
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
的dbo.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
表“ 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
强迫优化器使用索引视图提供的索引可以使MAX
和TOP
问题消失,但是必须有一种更好的方法来实现我在这里试图做的事情。绝对任何建议/责骂将不胜感激!
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput1 DATETIME;
SELECT @ConsumeOutput1 = MAX( FileDate )
FROM dbo.DailyTransaction;
SET STATISTICS IO, TIME OFF;
GO
表'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
表'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毫秒。
TL; DR:
帮助我了解在我提到的第一个视图上进行聚合查询所需的操作,该视图在合理的时间内运行且具有合理的I / O资源利用率。