这几天突发想到在ETL中Merge性能的问题。思路的出发点是Merge到目标表需要扫描的数据太多,而现实情况下,假设应该是只有一小部分会被更新,而且这部分数据也应该是比较新的数据,比方说对于想FactOrders这样一张表,一些越日期越久远的订单可能不可能被更新。那么整个思路就是减小每次需要从磁盘加载目标表到内存中跟stage表进行merge操作的数据量。只是我存在着两个疑问,这也是我问题要进行下面实验的原因。

前提条件是:目标表通过日期进行分区。

第一个疑问:在索引的作用下,SQL Server到底能不能聪明到只加载必要的分区,而不是整张表的数据?

第二个疑问:假设SQL Server自己不够聪明,那么我们通过引导SQL Server去加载必要分区即可,这下SQL Server知不知道该怎么做?

为此我要做三个实验:

第一个实验:Merge语句中目标表和源表的Join字段建立索引,看看SQL Server有没有缩小对目标表的数据集加载

第二个实验:用CTE封装目标表数据,并用Where语句缩小数据集,然后Merge语句把CTE作为目标表

第三个实验:这个应该是最有保证的做法,通过语句查找出涉及到的分区,再从目标表中把那些分区切换到另外的一张临时建立的表中,然后对这张临时建立的表进行和stage表的merge操作,再把数据切换回目标表。这个做法做有保证,但是也是最复杂的。首先生成临时建立的表的表结构必须和目标表一致,这就需要写一个过程专门要干这事。

先建好分区函数、Scheme、目标表和stage表

CREATE PARTITION FUNCTION myRangePF1 (DATETIME)
AS RANGE LEFT FOR VALUES ('2015-01-01', '2015-02-01', '2015-03-01', '2015-04-01', '2015-05-01', '2015-06-01', '2015-07-01', '2015-08-01', '2015-09-01', '2015-10-01', '2015-11-01', '2015-12-01', '2016-01-01');
GO CREATE PARTITION SCHEME myRangePS1
AS PARTITION myRangePF1
TO ([PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY],[PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY],[PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY]);
GO CREATE TABLE dbo.Merge_Perf_test_target
(
col1 INT,
col2 DATETIME,
col3 VARCHAR(1000),
col4 FLOAT
) ON myRangePS1(col2)
WITH(DATA_COMPRESSION=PAGE)
GO INSERT dbo.Merge_Perf_test_target
SELECT [ID], DATEADD(SECOND,ABS(CHECKSUM(NEWID()))%(DATEDIFF(SECOND,'2015-01-01','2016-01-01')-1),'2015-01-01'), REPLICATE('A',(CHECKSUM(NEWID())%500)+1), 1.*ABS(CHECKSUM(NEWID()))/ABS(CHECKSUM(NEWID()))
FROM [dbo].[Numbers] --SELECT * FROM sys.partitions WHERE [object_id] = object_id('Merge_Perf_test_target') AND index_id in (0,1)
--SELECT * FROM sys.indexes WHERE [object_id] = object_id('Merge_Perf_test_target') AND index_id in (0,1)
--SELECT * FROM sys.data_spaces WHERE data_space_id = 65601 --SET STATISTICS IO ON
--GO --SELECT COUNT(*)
--FROM dbo.Merge_Perf_test_target
--WHERE (col2 > = '2015-04-01' AND col2 < '2015-05-01') OR
-- (col2 > = '2015-10-01' AND col2 < '2015-11-01')
--GO CREATE TABLE dbo.Merge_Perf_test_src
(
col1 INT,
col2 DATETIME,
col3 VARCHAR(1000),
col4 FLOAT
)
GO --TRUNCATE TABLE dbo.Merge_Perf_test_src INSERT dbo.Merge_Perf_test_src
SELECT col1, DATEADD(SECOND,ABS(CHECKSUM(NEWID()))%(DATEDIFF(SECOND,'2015-03-01','2015-04-01')-1),'2015-01-01'), REPLICATE('A',(CHECKSUM(NEWID())%500)+1), 1.*ABS(CHECKSUM(NEWID()))/ABS(CHECKSUM(NEWID()))
FROM dbo.Merge_Perf_test_target
WHERE (col2 > = '2015-04-01' AND col2 < '2015-05-01'); INSERT dbo.Merge_Perf_test_src
SELECT col1, DATEADD(SECOND,ABS(CHECKSUM(NEWID()))%(DATEDIFF(SECOND,'2015-10-01','2015-11-01')-1),'2015-10-01'), REPLICATE('A',(CHECKSUM(NEWID())%500)+1), 1.*ABS(CHECKSUM(NEWID()))/ABS(CHECKSUM(NEWID()))
FROM dbo.Merge_Perf_test_target
WHERE (col2 > = '2015-10-01' AND col2 < '2015-11-01');
GO

实验一:

创建索引然后运行merge

CREATE INDEX IX_Merge_Perf_test_target_col1 ON dbo.Merge_Perf_test_target(col1)
CREATE INDEX IX_Merge_Perf_test_src_col1 ON dbo.Merge_Perf_test_src(col1)
--SELECT * FROM Merge_Perf_test_src MERGE Merge_Perf_test_target AS tgt
USING dbo.Merge_Perf_test_src AS src
ON tgt.col1 = src.col1
WHEN MATCHED
THEN UPDATE SET tgt.col2 = src.col2, tgt.col3 = src.col3, tgt.col4 = src.col4;

通过图形执行计划可以看到实际行数是一百万,也就是目标表整张表的行数。第一个假设失败。SQL Server自己不会聪明到减小数据范围的缩小。

实验二:

删掉前面建立的索引

drop index IX_Merge_Perf_test_target_col1 on Merge_Perf_test_target
drop index IX_Merge_Perf_test_src_col1 on Merge_Perf_test_src

这一步设计是需要动态SQL来协助完成的。通过下面的SQL输出的结果来拼凑出Where条件语句,也就是以分区列作为删选条件缩小数据集。

SELECT col.name, ty.name as type, prt_fn.type_desc, prt_fn.boundary_value_on_right, curr.value as start_value, nxt.value as next_start_value
FROM sys.index_columns ix_col
INNER JOIN sys.columns col ON col.object_id = ix_col.object_id AND ix_col.column_id = col.column_id
INNER JOIN sys.types ty ON ty.system_type_id = col.system_type_id
INNER JOIN sys.indexes ix ON ix.object_id = ix_col.object_id AND ix.index_id = ix_col.index_id
INNER JOIN sys.partitions prt ON prt.object_id = ix.object_id AND prt.index_id = ix.index_id
INNER JOIN sys.data_spaces ds ON ds.data_space_id = ix.data_space_id
INNER JOIN sys.partition_schemes prt_sch ON ds.data_space_id = prt_sch.data_space_id
INNER JOIN sys.partition_functions prt_fn ON prt_sch.function_id = prt_fn.function_id
INNER JOIN (sys.partition_range_values curr
LEFT JOIN sys.partition_range_values nxt ON curr.function_id = nxt.function_id AND curr.boundary_id = nxt.boundary_id - 1) ON prt_sch.function_id = curr.function_id AND prt.partition_number-1 = curr.boundary_id
WHERE ix_col.partition_ordinal = 1 AND prt.partition_number IN (SELECT $PARTITION.myRangePF1(col2) prt_no FROM dbo.Merge_Perf_test_src) and prt.object_id = object_id('Merge_Perf_test_target')

上面的语句输出下面的结果

上面输出的结果为了拼凑出像(col2 >= '2015-01-01 00:00:00.000' AND col2 < '2015-02-01 00:00:00.000') OR (col2 >= '2015-10-01 00:00:00.000' AND col2 < '2015-11-01 00:00:00.000'))这样的Where语句。那type输出字段再这里的作用就是为了却分是整型字段还是时间字段,因为时间字段需要添加两个单括号。而type_desc在这里是为了区分到底是范围还是值,值得肯定是要拼凑出值列表字符串给IN字句,范围就是像上面那样用大小括号。boundary_value_on_right是决定等于号到底最后放在大于号还是小于号上。这里不实现这个逻辑。只说明设计做法。

通过CTE加入Where条件,运行后再观察

WITH T AS (SELECT * FROM dbo.Merge_Perf_test_target
WHERE (col2 >= '2015-01-01 00:00:00.000' AND col2 < '2015-02-01 00:00:00.000') OR
(col2 >= '2015-10-01 00:00:00.000' AND col2 < '2015-11-01 00:00:00.000')) MERGE Merge_Perf_test_target AS tgt
USING dbo.Merge_Perf_test_src AS src
ON tgt.col1 = src.col1
WHEN MATCHED
THEN UPDATE SET tgt.col2 = src.col2, tgt.col3 = src.col3, tgt.col4 = src.col4;

图形执行计划可以看到使用了range scan,起码从的Seek Predicates可以看出

相比实验一减少了一半的时间

实验三:

这个设计的核心是找出涉及到的分区号,然后用SWITCH PARTITION切换到另外一个临时建立的表,再MERGE,再SWITCH PARTITION回去。按道理来讲和实验二比应该区别不大。这里也是需要用到动态语句协助。把下面输出的分区号码拼成一条字符串放到SWITCH PARTITION()中。

SELECT distinct $PARTITION.myRangePF1(col2) prt_no FROM dbo.Merge_Perf_test_src

这里最重要的一步是要运行时动态建立起一张和目标表架构相同的表,包括压缩级别和分区方法。实现其实不难,只是需要写代码查找各张系统视图或者系统表。这里略过。

ALTER TABLE dbo.Merge_Perf_test_target
SWITCH PARTITION 2 TO dbo.Merge_Perf_test_target_tmp PARTITION 2 ALTER TABLE dbo.Merge_Perf_test_target
SWITCH PARTITION 11 TO dbo.Merge_Perf_test_target_tmp PARTITION 11

从上面看到,时间上其实和第二种方法也只是快了5秒,但是实际上IO上是一样的。但是第二种方法比第三种方法有一点可以说可能是优点的同时也是缺点,就是第二种方法有时会遇到锁阻塞的问题。因为一旦改动数据范围大就意味着SQL Server可能多目标表加表级别锁,这样所有在同一时间点对目标表的访问都会被阻塞住。但是并不意味着这点就是好的。因为数据被移除目标表,意味着虽然没有阻塞,但是没有数据其实从另一个层面来讲是错的,这个时候就好像数据被删除了一样,甚至比阻塞还严重。

SQL Server ->> 尝试优化ETL中优化Merge性能的更多相关文章

  1. SQL优化工具 - SQL Server Profiler与数据库引擎优化顾问

    最近项目做到几千个学生分别去人脸识别记录(目前约630000行)中查询最后一次记录,可想而知性能这块是个麻烦.于是乎,GET到了SQL Server Profiler和数据库引擎优化顾问这俩工SHEN ...

  2. SQL Server数据库的存储过程中定义的临时表,真的有必要显式删除临时表(drop table #tableName)吗?

    本文出处:http://www.cnblogs.com/wy123/p/6704619.html 问题背景 在写SQL Server存储过程中,如果存储过程中定义了临时表,有些人习惯在存储过程结束的时 ...

  3. sql server 内置ETL工具学习(一) BCP篇

    sql server 内置ETL工具学习 常用的导入方式:bcp, BULK INSERT,OPENROWSET和 SSIS. BCP BCP全称BULK COPY PROGRAM 有以下特点: 命令 ...

  4. SQL Server 2008 安装过程中遇到“性能计数器注册表配置单元一致性”检查失败 问题的解决方法

    操作步骤: 1. 在 Microsoft Windows 2003 或 Windows XP 桌面上,依次单击"开始"."运行",然后在"打开&quo ...

  5. SQL点滴12—SQL Server备份还原数据库中的小把戏

    原文:SQL点滴12-SQL Server备份还原数据库中的小把戏 备份数据库时出现一个不太了解的错误 ,错误信息“is formatted to support  1 media families, ...

  6. Sql Server与.Net(C#)中星期值对比

    最近发现Sql Server与.Net(C#)中星期值居然不匹配,倒不知道依哪一个了. 1.Sql Server declare @date datetime; set @date = '2017-0 ...

  7. 如何在SQL Server查询语句(Select)中检索存储过程(Store Procedure)的结果集?

    如何在SQL Server查询语句(Select)中检索存储过程(Store Procedure)的结果集?(2006-12-14 09:25:36) 与这个问题具有相同性质的其他描述还包括:如何 ...

  8. 解决VS2010在新建实体数据模型出现“在 .NET Framework Data Provider for Microsoft SQL Server Compact 3.5 中发生错误。请与提供程序供应商联系以解决此问题。”的问题

    最近想试着学习ASP.NET MVC,在点击 添加--新建项--Visual C#下的数据中的ADO.NET 实体数据模型,到"选择您的数据连接"时,出现错误,"在 .N ...

  9. SQL Server 可更新订阅中有行筛选的同步复制移除项目而不重新初始化所有订阅!

    原文:SQL Server 可更新订阅中有行筛选的同步复制移除项目而不重新初始化所有订阅! 在可更新订阅的同步复制中,有行筛选的项目表,移除的时候会提示重新初始化所有的快照并且应用此快照,这将导致所有 ...

随机推荐

  1. iphone手机拍照学习笔记

    大纲: 功能 理论 技巧 实战 一.功能 设置-相机可以打开网格. 短按屏幕.画面曝光切换. 长按调节曝光和聚焦,曝光有范围,取决于点选的地方. live photo可以拍出会动的照片,上划编辑,高速 ...

  2. Ubuntu 16.04 compare 软件安装

    1软件官网 http://www.scootersoftware.com/download.php?zz=kb_linux_install 2.打开上述网址可看到安装信息 终端安装方式: wget h ...

  3. Android Studio的串口通讯开发

    基于android-serialport-api实现 前言RS232标准接口UARTRS232与UART转接下载 NDK 和构建工具创建支持 C/C++ 的新项目编译C/C++代码串口通讯原理关于校验 ...

  4. 【debian】解决debian中文安装后出现乱码的问题

    由于安装debian选择语言时选择了简体中文安装,但内核没有中文字库,导致某些字符显示为乱码(菱形,方块). 解决办法: 普通用户如果没有设置sudo权限,首先切换到root权限.然后: apt-ge ...

  5. 关于游标嵌套时@@FETCH_STATUS的值

    游标嵌套使用时,@@FETCH_STATUS的值有时会从内部游标影响到外部的游标,使外部的游标只循环一次.这时要检查游标的使用方法.要先移动游标,然后就开始判断,为真进行进行业务逻辑处理,然后移动游标 ...

  6. unity接入安卓sdk (unity调用安卓工程)

    1.安装jdk 并且配置环境,这个网上资料很多,这里不说了 2.安卓开发软件eclipse集成环境版 下载地址 http://tools.android-studio.org/index.php/ad ...

  7. 使用webgl(three.js)创建3D机房(升级版)-普通机房

    序: 目前市面上的数据中心主要分两大类,一类属于普通数据中心,机柜按照XY轴 有序排放,一类属于微模块集合的数据中心,多个机柜组合而成的微模块.  本节课主要详细讲解普通数据中心的可视化展示,浏览器直 ...

  8. no jpeg in java.library.path;java.lang.NoClassDefFoundError: Could not initialize class sun.awt.image.codec.JPEGImageEncoderImpl

    no jpeg in java.library.path;java.lang.NoClassDefFoundError: Could not initialize class sun.awt.imag ...

  9. java 配置在.properties文件中的常量

    不让用常量类,那就用.properties文件配置,放在根目录. import java.util.HashMap; import java.util.Iterator; import java.ut ...

  10. 问题集录01--java对list列表进行排序

    用Collections.sort方法对list排序有两种方法 第一种是list中的对象实现Comparable接口,如下: /** * 根据order对User排序 */ public class  ...