More servicesWindows Live
HomeHotmailSpacesOneCare
 
MSN
Sign in
 
 
Spaces home  Ryan's SpacePhotosProfileFriendsBlog Tools Explore the Spaces community

Blog

June 15

SQL Server 2008 RC0中的Report Designer呢?

微软在在6月5日发布了SQL Server 2008 RC0,首先在MSDN和Technet订阅区放开下载,不过最近在Downloads站点也看到了下载。

这个周末得空装了一个RC0的环境,不过安装完了之后却发现原来在以前各个版本的CTP中的Report Designer不见了。百般搜索无果后突然在微软的下载站点找到了答案:原来CTP中的Report Designer已经被更名为Report Builder 2.0,作为SQL Server 2008 RC0 Feature Pack中的一部分,这个Report Builder 2.0必须从网上单独下载。

不过有人可能会有疑问,Report Builder不是利用Report Model设计报表的么,而且是在Report Manager中下载的么?

是的,不过那是Report Builder 1.0,Report Builder 1.0仍然可以在SQL Server 2008 Reporting Service内置的Report Manager站点上下载。SQL Server 2008提供单独下载的是Report Builder 2.0。

现在看界面可以初步确定的是Report Builder 1.0仍然用于设计基于Report Model的报表,而Report Builder 2.0应该是设计基于常规数据源的报表。

Report Builder 2.0

June 11

万恶的缩写

今天在看技术资料的时候发现了一个缩写——UBO,思前想后才明白UBO就是Usage Based Optimizer的缩写,因此突然感慨用缩写来发明新单词的办法不知道是谁起的头。顺便总结几个SQL Server 2005时代里面的缩写:

SQL Server 工具类所写:

  • SSMS:SQL Server Management Studio
  • BIDS:Business Intelligence Development Studio
  • DTA:Database Engine Tuning Advisor
  • SSAC:SQL Server Surface Area Configuration

SQL Server服务和产品的缩写:

  • SSAS:SQL Server Analysis Service
  • SSRS:SQL Server Reporting Service
  • SSIS:SQL Server Integration Service
  • FTS:FullText Search
  • SSCE:SQL Server Compact Edition
  • SSEE:SQL Server Express Edition
  • SSME:SQL Server Mobile Edition

大家要是想到什么缩写就跟上来补一下吧。

June 08

SQL Server 2008 RC0新的Logo

SQL Server 2008 RC0已经在6月5日发布啦,最先可以下载的是MSDN和Technet订阅用户,不过现在Download站点里面也可以下载到了。

感谢我的老婆第一时间帮我PS了SQL Server 2008的桌面,这个桌面在SQL Server开发组成员的Blog上面看到过,不过像素太小。

March 27

SQL Server 2008中的TDE

在SQL Server 2005中,我们得到了一个新的功能:数据加密,在SQL Server 2008中,这一功能得到了进一步的增强。除了SQL Server 2005中支持的Cell级加密外,现在SQL Server 2008加入了整库加密,微软称其为数据库透明加密Transparent Data Encryption(我们后面简称TDE)。相比SQL Server 2005的加密,TDE工作在SQL Server的IO层,因此开销更小,同时对应用程序的影响也降低到零。

因此在Windows Server 2008平台上,我们现在对数据库的数据加密保护手段已经有了4种,分别是:

  • Windows 2000开始加入的EFS功能
  • Windows Vista和Windows Server 2008开始的BitLockers
  • SQL Server 2005开始引入的Cell级加密
  • SQL Server 2008加入的TDE

关于这4项技术的对比会在稍后的帖子中讨论.

SQL Server的数据加密框架

先看一下SQL Server 2005的加密体系:

image

然后再看一下SQL Server 2008中的数据加密体系:

 

相比之下,SQL Server 2008的加密体系多出了TDE这条路径,就是上图中虚线部分,SQL Server 2008会通过DMK(Database Master Key数据库主密钥)直接加密数据文件,也可以通过EKM(Extensible Key Management外部密钥管理)直接加密数据文件。

SQL Server 2005与SQL Server 2008相同之处在于两者都是用Windows操作系统提供的DPAPI(Data Protection API数据保护应用开发接口)来保护SMK(Service Master Key服务主密钥),每个SQL Server的实例都拥有自己的SMK

EFS对SQL Server的性能影响主要原因是SQL Server在EFS加密文件上执行IO的时候IO操作是同步操作,也就是说如果SQL Server的工作线程需要一次IO就必须等到当前的文件IO结束,因此SQL Server中的Read Ahead机制和CheckPoint机制都会受到影响。关于这个问题的具体讨论可以参考微软支持网站的KB922121

SQL Server 2008基于策略的管理

SQL Server 2008通过引入可扩展的基于策略的管理功能降低了数据平台的管理工作量。其中最引人注意的莫过于提供策略化管理的Declarative Management Framework (简称DMF),不过这个名称已经被启用了,现在正是名字是基于策略的管理(Policy Based Management),我们姑且简称为PBM。PBM使DBA们可以制定管理策略,并将这些策略应用到服务器、数据库以及数据环境中的其他对象上去。经过精心设计的管理策略可以帮助DBA们对数据环境进行前摄性的管理。

PBM的优势体现在:

  • 按需管理:PBM提供了系统配置的逻辑视图,因此DBA们可以预先定义各自所需要的数据服务配置,而不用等到这些需要实际发生的时候再去配置。
  • 智能监控:PBM可以持续监控系统的配置变化,并阻止那些违反了策略的配置变化操作。
  • 虚拟管理:通过PBM,DBA们可以对多台服务器进行规模化管理,在企业内部统一实施某些强制性配置会变得更加方便。

基于策略管理的框架

PBM的框架有三部分组成:

  • 策略管理:管理员制定各种策略。
  • 显式管理:管理员通过对指定的目标或目标群应用策略来检查目标对策略的依从性,或者更严格的是禁止这些目标上违反策略的行为发生。
  • 执行模式:SQL Server 2008的PBM支持4种执行模式,这4种模式决定了策略对目标的影响程度。这四种模式分别是:
    • 按需(On Demand):这种模式下的策略可以有管理员自由的选择是否应用,例如管理员可以手动调用这些策略来检查目标的依从性,或者通过DDL Trigger来订阅这些策略。
    • 更新时阻止(On Change - Prevent):这是最严格的一种,SQL Server 2008通过DDL Trigger的方式在订阅该策略的目标上发生操作时实施检查操作对策略的符合性,如果违反策略则回滚该操作,以达到强制策略的效果。
    • 更新时记录(On Change - Log Only):SQL Server 2008通过Event Notification的机制在在订阅该策略的目标上发生操作时实施检查操作对策略的符合性,如果违反策略则发送消息,就将该违反操作通过Service Broker的队列发送进行记录。
    • 按计划(On Schedule):通过SQL Agent的作业来调用策略对目标对象进行检查。

虽然PBM有以上四种执行模式,但是归总起来其实是两大种,一种是基于SQL Agent作业方式的On Schedule模式,而另外一种是基于Event机制的On Change模式。因此并非所有Facet都支持On Change模式,要支持On Change模式,那么Facet的状态改变必须可以通过事件捕获或者事务性的DDL操作,当然On Schedule和On Demand就没有这些机制,因为这两种模式无需参与到Facet状态更新的事务中去。

基于策略管理的架构图

image

基于策略管理的术语

  • 对象(Facets):包括策略管理中某个方面的相关配置属性。例如在Surface Area中包括了像Database Mail Enabled以及CLR Integration Enabled之类的SQL Server功能的属性。
  • 条件(Conditions):表示一个方面的状态。条件是基于单个方面的,并且可以被一个或多个策略使用。例如,DBA可以建立一个名为Minimal Surface Area的条件,在这个条件中将Surface Area Facet中的所有属性都设置为False。
  • 策略(Policies):包括了用于约束单个或多个目标的条件。例如DBA可以创建一个名为Locked Down Server的策略,在这一策略中将Minimal Surface Area条件指派给某台服务器。
  • 类别(Categories):包含一条或多条策略。数据库拥有者可以将一个或多个分类绑定到数据库上。例如,DBA可以创建一个名为Corporate DB Policies的分类,其中包含一条强制数据库对象命名规则的策略和一条强制数据库兼容级别的策略,并将该分类绑定到业务数据库上。通常所有数据库都绑定到默认分类,但是可以在服务器或数据库级别上将分类设置为激活(Active)或暂停(Inactive),这样管理员就可以灵活控制策略的强制性。
  • 目标(Targets):目标代表像服务器、数据库、登录、表以及其他数据库对象各种被指派策略的实体。在一个SQL Server实例中的所有目标组成了一个目标层级。对于某个策略,DBA可以通过对目标层级进行筛选来定义一个目标集合。例如,DBA可以定义一个包含Production架构拥有的所有索引的目标集合。

为策略检查配置警报

如果某项策略被违反,SQL Server 2008会生成相应的警报,因此可以通过在SQL Agent中配置警报来监控这些事件,

执行模式 事件号

On Change - Prevent
(if automatic)

34050

On Change - Prevent
(if On Demand)

34051
On schedule 34052
On change

34053

策略管理的安全性

属于PolicyAdministratorRole的成员才可以制定和修改策略定义,这个角色的成员是必须要小心控制的,因为恶意用户可以通过制定苛刻的策略来达到类似于拒绝服务攻击的效果。

基于策略管理的配置

基于策略管理的常规配置步骤为:

  1. 根据Facet创建Condition,Condition可以作为Policy的检查条件,也可以是用于确定策略应用范围的筛选条件。
  2. 引用已经创建好的策略来定义策略,同时可以在策略检查条件可以应用的对象集上附加筛选条件,例如一个检查Multiple Part Name对象状态的策略就可以有表、存储过程、视图、同义词等一系列的对象可以进行选择并附加筛选条件,默认的筛选条件是Every,也就是这个对象集中所有的对象。当然不是所有条件都可以充当筛选条件的,在二月CTP的测试中就发现包含LIKE运算符的表达式的条件就不能充当筛选条件。
  3. 如果需要可以在Server Restriction中定义需要应用策略的SQL实例条件。
  4. 如果需要还可以在Policy Management节点上定义策略组,并在策略的定义中将策略归入某个类别,然后由服务器管理员或数据库拥有者订阅某个策略组。不过要注意看看我的前一个帖子,默认策略组都是强制订阅的,要启用自选订阅要在策略组管理中设置。
  5. 然后就等着策略帮你自动管理SQL Server了,这就看策略定义时选择的执行模式了。
March 25

关于SQL Server 2008是否是SQL Server 2005的一个Service Pack

随着SQL Server 2008越来越多地进入到大家的视线中,也有越来越多的声音说SQL Server 2008 (Kaitmai)仅仅是SQL Server 2005的一个补丁包。当初我也是这个想法,因为我们把太多的目光放到了例如Geo-Spatial支持、Hierarchical ID、File Stream上,确实这些功能都是建立在SQL Server 2005搭建的CLR -Integrated平台上的,但是我们也需要看到SQL Server 2008中的其他功能,例如Resource Governor、Declarative Management Framework、Change Data Capture、Reporting Service IIS agnostic report deployment等等。SteveB在美国Launch中更将Policy-base Management列在SQL Server 2008 Top 10新功能的首位,还有美国Launch上关于Activity Monitor的改进、Object Search以及T-SQL Debugger被用户爱称为三项Surprise Features。当然还是会有人说这些都是在DMV和DDL Triggers的技术基础或技术路线上发展起来的,不过哪个产品不是从一条技术路线或某项基础性改革上发展起来的呢?

因此微软把SQL Server 6.0/6.5称为第一代数据库产品,SQL Server 7.0/2000称为第二代数据库,而SQL Server 2005/2008称为第三代数据库产品。以此类推,那么SQL Server vNext是不是会变成第四代数据库产品呢,那又会有什么革命性的改变呢?Grid?...哦,跑题了

因此我们还是应该宽容地看待SQL Server 2008,还是应该升级到SQL Server 2008的,升级有问题可以咨询EMC MSP么,哈哈哈~~~~!

附上SQL Server 2008现在的新特性以及改进列表:

  • Policy-Based Management
  • Management/performance data collection
  • extended events
  • database mirroring enhancements
  • Query plan freezing
  • resource governor
  • transparent data encryption
  • external key management
  • Hot add CPU
  • partition aligned index views
  • backup compression
  • Table valued parameters
  • DDL trigger enhacements
  • T-SQL programming enhancements
  • T-SQL Intellisense
  • Entity data model
  • LINQ
  • Visual entity designer
  • Service Broker enhacements
  • SQLCLR enhacements
  • Multi-server query
  • Configuration servers
  • Hierarchy ID
  • Large UDTs
  • Date/Time data type
  • Improved XML support
  • Geography data type
  • Geometry data type
  • Filestream
  • Analysis Services scalable backup
  • Analysis Services dimension design
  • Star join query optimization
  • MERGE SQL statement
  • Change data capture
  • Analysis Services MDX enhacements
  • Analysis Services Personalization extensions
  • Analysis Services aggregation designer
  • Analysis Services Data Mining enhanced validation
  • Analysis Services Data Mining and enhanced structures
  • Analysis Services MOLAP writeback
  • Analysis Services dynamic management views
  • Integration Services C# scripting
  • Integration Services persistent look-ups
  • Integration Services data profiling
  • Integration Services import/export wizard enhancements
  • Integration Services Improved connectivity
  • Reporting Services IIS agnostic report deployments
  • Reporting Services report builder enhancements
  • Reporting Services core engine enhacements
  • Improved installation
  • March 24

    如何在SQL Server 2008中订阅策略组

    SQL Server 2008中的DMF可以极大程度地降低管理成本,不过今天在学习DMF的配置时偶几乎吐血加厥倒。

    原因是DMF的策略可以提供策略组的功能,用户可以选择订阅某些组的策略,一般策略都是属于Default组,而策略管理员可以创建一些策略组,并将策略放置到组中,以便于DBA选择策略进行应用。不过我屡次尝试都发现创建的策略组是无法订阅的,因为订阅的Checkbox始终是灰色的,在经历了疑惑、焦躁、愤怒和无奈后,最终发现了这个问题的症结。

    为了避免大家也经历这种和MM分手时相类似的心理历程,贴图如下

    1、打开策略组的定义对话框

    Open Policy Category Management Dialog

    2、定义策略组

    Policy Category Origin

    3、千万记得横向滑动列表框到底,天晓得这个后面还有一列,只有取消了Checkbox,这个策略组才是可以手动订阅的,默认有点类似于组策略中的强制策略一样。诶,微软的Dev为什么不把这列放到前面呢?!看来易用性的细节是无处不在阿。

    Policy Category Rolled toEnd

    为纪念这一经历,我特将MSN签名改为“SQL Server 2008 Feb CTP最新心得:拖动条很重要!!!”三天,以此警醒。

    March 18

    怎样让Windows Server 2008象Vista

    调出Vista的Aero

    第一步当然是要安装Desktop Experience功能,启用Theme服务,然后在Personalize Appearance and Sounds的Theme中选择Windows Vista Theme,接着在Windows Color and Experience中选择Windows Aero的Color Scheme。

    使用Live

    Live Writer没有什么问题,主要是Live Messenger。

    首先是把Vista下安装好的目录原样拷贝过来,但是要在system32目录下再拷贝一个sirenacm.dll文件。将sirenacm.dll拷贝到Windows Server 2008的system32目录即可。不过这个时候Live Messenger的窗口中可能找不到启用Live Today、Space、Sharing Folder和Email的四个按钮,需要到注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Live\Messenger键中添加一条值:

    Name Type Value
    Primary Service REGSZ .Net Messenger Service

    使用Sidebar

    Sidebar是最头疼的,费了我大把时间,最后还是王博给了个链接,终于解决。这里是地址http://www.reggieburnett.com/post/Sidebar-working-nicely-on-Windows-Server-2008.aspx

    主要过程就是把Windows Sidebar整个目录都拷贝过来,然后执行下面的命令:

  • regsvr32 sbdrop.dll
  • regsvr32 wlsrvc.dll
  • sidebar /RegServer

    前面两条我都执行了,唯独不知道最后一条,因此老是在加载Gadget的时候爆Library not registered的错误。感谢王博,感谢这位reggie。

    使用Notebook Presenter 8000的蓝牙棒连接手机

    Vista下面安装IntelliPoint 6.1后自动会有驱动安装,蓝牙棒就能连手机了,不过在Server 2008下面就不行了,说是有个驱动找不到,这个还在找解决方案。有晓得的兄弟一定要共享。

  • March 11

    SQL Server 2008 二月CTP中的CDC小改动

    上一周在微软MSDN上Webcast的时候,突然发现一段在CTP5,也就是去年的十一月CTP中测试过的CDC演示代码不能用了,提示是系统无法找到sp_cdc_enable_table_change_data_capture,sp_cdc_enable_db_change_data_capture等一系列存储过程,原以为SQL Server 2008安装的有问题,是不是那些个系统存储过程没有生成。不过后来一看系统对象列表才晓得在二月CTP中CDC的部分存储过程名字已经被改变了。现在所有存储过程后面都删除了change_data_capture后缀。

    想想也对,这些个存储过程已经有cdc前缀了,何苦再加个功能全名的后缀呢?不过再一看,二月CTP的联机丛书里面可没有提到这个改变,而且联机丛书中的CDC配置说明用的还是老的存储过程名称,也就是带change_data_capture后缀的。

    诶,看来版本管理还是很重要的阿,相信如果有人直接从二月CTP开始评估Kaitmai的话,一定会被这个小改动混淆一下的了。

    March 02

    SQL Server 2008中的数据压缩功能

    SQL Server 2005 SP2为我们带来了vardecimal功能,这项功能使得原来定长的decimal数据在数据文件中以可变长的格式存储,据称这项功能可以为典型的数据仓库节省30%的空间,而SQL Server 2008在这一基础上又进一步增强了数据压缩功能。SQL Server 2008现在支持行压缩和页面压缩两种选项,数据压缩选项可以在以下对象上启用:

    • 未创建聚簇索引的表
    • 创建聚簇索引的表
    • 非聚簇索引(对表设置压缩选项不会影响到该表上的非聚簇索引,因此聚簇索引的压缩需要单独设置)
    • 索引视图
    • 分区表和分区索引中的单个分区

    为什么需要数据压缩

    首先可能需要讨论的问题就是为什么在存储成本不断降低的今天,微软还要煞费苦心地在SQL Server中实现并且不断改进数据压缩技术呢?

    尽管存储成本已经不再是传统意义上的首要考虑因素,但是这并不代表数据库尺寸不是一个问题,因为数据库尺寸除了会影响到存储成本之外,还极大地关联到管理成本和性能问题。

    首先我们来讨论为什么会有管理成本的问题?因为数据库需要备份,数据库的尺寸越大,那么备份时间就会越长,当然另外一点就是消耗的备份硬件成本也会随之提高(包括需要的备份介质成本和为了满足备份窗口而需要更高级的备份设备带来的采购成本),还有一种管理成本就是数据库的维护成本,例如我们经常需要完成的DBCC任务,数据库尺寸越大,我们就需要更多的时间来完成这些任务。

    接着我们再看看性能问题。SQL Server在扫描磁盘读取数据的时候都是按照数据页为单位进行读取的,因此如果一张数据页中包含的数据行数越多,SQL Server在一次数据页IO中获得的数据就会越多,这样也就带来了性能的提升。

    最后考虑存储的成本,按照原先SQL Server 2005 SP2中vardecimal的压缩数据为例,30%的空间节省也就意味着30%的存储成本,而按照SQL Server 2008当前放出的测试数据,采用新的数据压缩技术可以达到2X-7X的存储率,再加上如果企业要考虑容灾而增加的存储空间,这样节省的存储硬件成本也将是想当可观的。

    如何使用数据压缩

    SQL Server 2008中的压缩选项可以在创建表或索引时通过Option进行设置,例如:
    CREATE TABLE TestTable (col1 int, col2 varchar(200)) WITH (DATA_COMPRESSION = ROW);

    如果需要改变一个分区的压缩选项,则可以用以下语句:

    ALTER TABLE TestTable REBUILD PARTITION = 1 WITH (DATA COMPRESSION = PAGE);

    如果需要为分区表的各个分区设置不同的压缩选项,可以使用以下的语句:(SQL Server 2008可以对不同的分区使用不同的压缩选项,这一点对于数据仓库应用是非常重要的,因为数据仓库的事实表通常都会有一个或数个热分区,这些分区中的数据经常需要更新,为了避免数据压缩给这些分区上的数据更新带来额外的处理载荷,可以对这些分区关闭压缩选项)

    CREATE TABLE PartitionedTable (col1 int, col2 varchar(200))
    ON PS1 (col1)
    WITH (
    DATA_COMPRESSION = ROW ON PARTITIONS(1),
    DATA_COMPRESSION = PAGE ON PARTITION(2 TO 4));

    如果是为某个索引设置压缩选项的话,可以使用:

    CREATE INDEX IX_TestTable_Col1 ON TestTable (Col1) WITH (DATA_COMPRESSION = ROW);

    如果是修改某个索引的压缩选项,可以使用:

    ALTER INDEX IX_TestTable_Col1 ON TestTable REBUILD WITH (DATA_COMPRESSION = ROW);

    SQL Server 2008同时还提供了一个名为sp_estimate_data_compression_savings存储过程帮助DBA估计激活压缩选项后对象尺寸。

    数据压缩是怎样工作的

    对于行压缩,SQL Server 2008采用以下三种方法来节省存储空间:

  • 减少了与记录相关联的元数据开销。此元数据为有关列、列长度和偏移量的信息。在某些情况下,元数据开销可能大于旧的存储格式。
  • 它对于数值类型(例如,integerdecimalfloat)和基于数值的类型(例如,datetimemoney)使用可变长度存储格式。
  • 它通过使用不存储空字符的可变长度格式来存储定长字符串。

    对于页面压缩,SQL Server 2008则是在一张数据页面上依次采用:

    1. 行压缩
    2. 前缀压缩
    3. 字典压缩

    配置数据压缩功能需要注意的

    尽管SQL Server 2008的数据压缩功能非常有价值,但是仍然需要注意一些问题:

    • 数据压缩功能仅在企业版和开发版中可用
    • 数据压缩可以让一张数据页存储更多的数据行,但是并不能改变单行数据最长8060字节这一限制
    • 在一张已经设置了数据压缩的表上创建聚簇索引时,聚簇索引默认继承原表上的压缩选项
    • 在未设置聚簇索引的表上设置页面压缩时,只有以下情况才会获得页面压缩的实际效果:
      • 数据使用BULK INSERT语法添加到表中
      • 数据使用INSERT INTO ... WITH (TABLOCK)语法添加到表中
      • 执行带有页面压缩选项的ALTER TABLE ... REBUILD命令
    • 在未设置聚簇索引的表上更改压缩选项,会导致该表上所有非聚簇索引都需要重建,因为这些非聚簇索引指向的数据行地址已经都发生了改变。
    • 在改变压缩选项时所需要的临时空间大小与创建索引是所需要的空间是一样的,因此对于分区表,我们可以逐个分区设置压缩选项来减少临时空间的需求压力。
    • 由于SQL Server 2008中数据压缩技术其实是SQL Server 2005 SP2中vardecimal技术的一个超集,因此设置了数据压缩后就没有必要保留vardecimal了。当然SQL Server 2008为了保持向后兼容性,在当前版本中仍然保留了vardecimal,但是SQL Server 2008的下一个版本及可能就会弃用vardecimal选项,因此做了这些设置的数据库应该尽早改变到数据压缩设置下。
    • SQL Server 2008的压缩选项是工作在存储引擎层的,对于SQL Server的其他部件来说这一特性是透明的,因此当我们用BULK LOAD的方式将外面的数据导入SQL Server时,会显著的增加CPU的工作载荷,同时将以压缩的数据表导出到外部文件时,可能会消耗比原来多很多的空间。
  • February 01

    初探SQL Server 2008 Change Tracking

    前段时间评估了一下SQL Server 2008的CDC功能,总体发现CDC的开销还是不小的,特别是对日志文件的读。对于那些字段数量较多而且需要跟踪每个字段值更新的用户表,CDC的带来的额外开销则尤其明显。因此继续评估SQL Server 2008对数据更新跟踪的技术方案。

    在SQL Server 2008的联机丛书中提到了,Change Tracking适用于单向或双向的数据同步应用场景。在接下来的一些列试用中,我们就能深刻体会到这一点。

    激活Change Tracking

    要使用Change Tracking,首先需要在数据库级别启用Change Tracking功能,我们可以通过两种途径启用Change Tracking。

    1. 使用T-SQL语句,例如要启用TestCT数据库的Change Tracking功能,可以通过提交如下语句
      ALTER DATABASE TestCT
      SET CHANGE_TRACKING = ON
      (CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON)
    2. 在SQL Server Management Studio中通过设置数据库的属性,Change Tracking功能在数据库属性对话框中拥有一个独立的选项页。

    其中我们会注意到CHANGE_RETENTION参数和AUTO_CLEANUP参数。因为Change Tracking所有的信息都会被添加到一张内部表中,所以这张内部表将会无限制地增长,如果我们不希望这张表的数据量一直增长,Change Tracking功能提供了一个后台的进程自动对内部表进行清理,清理的依据就是CHANGE_RETENTION,每次清理进程运行的时候都会将超过CHANGE_RETENTION参数中设定时间的记录删除掉,而清理进程是否会运行则依赖于AUTO_CLEANUP参数。CHANGE_RETENTION参数的时间单位可以是分钟、小时或天。

    激活数据库级别的Change Tracking功能后,DBA就可以选择哪些需要更新跟踪的表了。我们同样也有两种方法在更新跟踪的表上启用Change Tracking功能:

    1. 使用T-SQL语句,例如要启用TestCT表上的更新跟踪,我们可以提交如下语句
      ALTER TABLE TestCT
      ENABLE CHANGE_TRACKING
      WITH (TRACK_COLUMNS_UPDATED = ON)
    2. 在SQL Server Management Studio中通过设置表的属性,Change Tracking功能在表属性对话框中拥有一个独立的选项页。

    其中我们注意到有个参数是TRACK_COLUMN_UPDATED,当这个参数被设置为ON的时候,UPDATE语句提交后在内部表中将会记录UPDATE语句影响了哪些列,对于INSERT和DELETE语句,则所有列都是被影响到的。

    使用Change Tracking

    Change Tracking提供的信息可不像Change Data Capture那么详细,Change Data Capture可以提供每个事务影响到的数据的前像和后像。Change Tracking功能通过CHANGETABLE系统表来获得更新的版本信息。

    CHANGETABLE有两种用法,一种是CHANGETABLE(CHANGES),一种是CHANGETABLE(VERSION)。前者用于返回某个sync_version后的数据变化情况,后者用于返回某行数据的最新的更改版本号。

    CHANGETABLE(CHANGES)

    这是一个表函数,语法结构为CHANGETABLE(CHANGES table, last_sync_version),table参数为激活Change Tracking功能的表名,last_sync_version参数是希望获取更新的最小版本号。

    table参数的值非常明白,我们希望获得哪张表的更新信息,就是这张表的名字了,而last_sync_version是影响返回结果集的主要因素。
    举个例子,我们用下面这段脚本来详细说明Change Tracking记录下的信息:

    CREATE TABLE TestCT
    (
        ID int PRIMARY KEY,
        Name varchar(50),
        Description varchar(200)
    )
    GO
    ALTER TABLE TestCT
    ENABLE CHANGE_TRACKING
    WITH (TRACK_COLUMNS_UPDATED = ON)
    GO

    这个时候TestCT表的Change Tracking功能就被激活了,这张表上所有的数据更新都会被记录到一张内部表中,接着我们就可以在TestCT表上提交各种DML语句了。
    INSERT INTO TestCT VALUES (1, 'ABC', NULL)

    这个时候我们可以通过CHANGETABLE函数来查看TestCT的更新历史
    SELECT * FROM CHANGETABLE(CHANGES TestCT, 0) CT
    返回结果为

    SYS_CHANGE
    _VERSION
    SYS_CHANGE
    _CREATION_VERSION
    SYS_CHANGE
    _OPERATION
    SYS_CHANGE
    _COLUMNS
    SYS_CHANGE
    _CONTEXT
    ID
    1 1 I NULL NULL 1

    返回结果集中:

    • SYS_CHANGE_VERSION是一个bigint的字段,表示的当前这行数据最新的更新版本号。
    • SYS_CHANGE_CREATION_VERSION代表的是当前数据行被插入数据表的更新版本号。
    • SYS_CHANGE_OPERATION是一个nchar(1)的字段,I代表Insert,U代表Update,D代表Delete。
    • SYS_CHANGE_COLUMNS代表更新操作影响到了哪些数据列,这个字段的结果是个varbinary(4100)。这个字段只有在表上激活Change Tracking时将TRACK_COLUMN_UPDATED选项设置为ON时才会返回有效值,并且对于INSERT和DELETE操作返回的都是NULL,因为DELETE和INSERT其实影响到了所有数据列,只有UPDATE操作才会返回值,这个字段的值可以通过CHANGE_TRACKING_IS_COLUMN_IN_MASK()函数来解析。
    • SYS_CHANGE_CONTEXT是一个varbinary(128)的字段,这个字段可以记录数据更新的上下文环境信息,不过上下文环境信息需要在提交DML语句时显式地通过WITH CHANGE_TRACK_CONTEXT语句提供。
    • ID是TestCT表的主键字段,因此如果TestCT的主键是内容为(ID, Name)的组合主键,则除了ID外,CHANGETABLE返回结果集中还会多一个Name字段。

    我们接着测试,
    UPDATE TestCT SET Name = 'abc' WHERE ID = 1

    提交完这句语句后,其实TestCT表已经有了两次更新,一次是INSERT操作,一次是UPDATE操作,但是这两次操作都是针对ID为1的这一行数据,这个时候我们在CHANGETABLE函数中通过不同的last_sync_version参数会得到不同的返回结果。

    SELECT * FROM CHANGETABLE(CHANGES TestCT, 0) CT
    返回结果为

    SYS_CHANGE
    _VERSION
    SYS_CHANGE
    _CREATION_VERSION
    SYS_CHANGE
    _OPERATION
    SYS_CHANGE
    _COLUMNS
    SYS_CHANGE
    _CONTEXT
    ID
    2 1 I NULL NULL 1

    从返回结果看,主键值为1的这行数据最近的更新版本为2,而插入表时的更新版本为1,代表这行数据在插入数据表后已经有过更新,但是SYS_CHANGE_OPERATION仍然是I。原因是我们选取的last_sync_version参数值是0,代表我们上次同步时数据更新版本是0,而这行数据是在版本0后被插入表的,因此这行数据还没有出现在同步的目的表中,因此在我们进行同步时首先要处理INSERT操作,而被INSERT到源表中的原始记录已经被更新了(在版本2时),因此我们只能将INSERT操作和UPDATE操作进行合并,直接取得UPDATE后的数据行写入目的表。

    但是如果我们提交的语句是
    SELECT * FROM CHANGETABLE(CHANGES TestCT, 1) CT
    返回结果则会变为

    SYS_CHANGE
    _VERSION
    SYS_CHANGE
    _CREATION_VERSION
    SYS_CHANGE
    _OPERATION
    SYS_CHANGE
    _COLUMNS
    SYS_CHANGE
    _CONTEXT
    ID
    2 1 U 0x0000000002000000 NULL 1

    这次SYS_CHANGE_OPERATION字段变成了U。原因是我们选取的last_sync_version参数值是1,代表我们上次同步时数据更新版本是1,也就意味着这行数据已经被同步到目的表中了,因此在我们进行同步时只需要处理UPDATE操作。

    接着我们尝试一下
    UPDATE TestCT SET ID = 5 WHERE ID = 1

    然后我们看看CHANGETABLE会返回什么
    SELECT * FROM CHANGETABLE(CHANGES TestCT, 2) CT
    返回结果则会变为

    SYS_CHANGE
    _VERSION
    SYS_CHANGE
    _CREATION_VERSION
    SYS_CHANGE
    _OPERATION
    SYS_CHANGE
    _COLUMNS
    SYS_CHANGE
    _CONTEXT
    ID
    3 NULL D NULL NULL 1
    3 3 I NULL NULL 5

    返回结果会变成两条,这是因为我们这次UPDATE修改的是主键,所以在表中其实是将原始记录删除,然后增加一行新的数据。在同步的时候我们就知道需要删除先前同步到目的表中的ID为1的记录,然后复制源表中ID为5的记录到目的表中。

    接下来还可以进行一系列的测试,比如说:

    • 在一个事务中修改多条记录,则会注意到这些记录在CHANGETABLE表函数返回的结果中拥有的SYS_CHANGE_VERSION是相同的。
    • 提交DDL语句,表架构的改变不会在CHANGETABLE表函数的结果中体现,也就是说表结构的改动不被Change Tracking记录,甚至新增加一个设置了默认值的字段。不过增加后对新的字段进行修改就可以被Change Tracking记录了。这说明了Change Tracking只跟踪DML,不跟踪DDL。不过这一点应该不是问题,因为SQL Server 2005开始就有DDL Trigger了,相对DDL语句的提交应该不会过于频繁,因此通过触发器的方案来实现对架构的跟踪应该从成本上是可以接受的。
    • 利用WITH CHANGE_TRACKING_CONTEXT (@context)语句在跟踪记录中留下DML语句执行上下文的信息,这一手段通常可用于跟踪最后一次更新是由哪个应用程序做出的、或由哪个个用户作出的,通过判断执行上下文可以在双向复制中解决冲突的问题。
    • 如果执行了TRUNCATE TABLE或者Cleanup进程清理了Change Tracking记录,那么可以通过CHANGE_TRACKING_MIN_VALID_VERSION函数了解可获取的最小更新版本号。如果这个最小版本号比复制目的端记录的最近一次复制成功的最大版本号都高,则意味着源数据库已经丢失了一部份尚未复制的记录,也就代表目的系统需要重新初始化。
    • 对于UPDATE语句,如果在启用表的Change Tracking功能是设置了Track_Column_Updated选项为ON,Change Tracking会记录下UPDATE语句影响到字段信息,这个信息可以通过使用CHANGE_TRACKING_IS_COLUMN_IN_MASK函数解析,比如说要知道TestCT表的Name字段是否在版本2的UPDATE操作中被影响到,可以使用CHANGE_TRACKING_IS_COLUMN_IN_MASK(COLUMNPROPERTY(OBJECT_ID('TestCT'), 'Name', 'ColumnId'), SYS_CHANGE_COLUMNS)函数。
    CHANEGTABLE(VERSION)

    这个表函数的语法结构为CHANGETABLE(VERSION table , { ( column_name [ , ...n ] ) , ( value [ , ...n ] ) },table参数是激活Change Tracking功能的表名,column_name是主键的字段名,如果主键是复合键,则需要列举主键中所有的字段,value是对应前面每个字段的值。例如上面代码,我们可以通过下面语句来得到ID为5的记录最近的更新版本号是多少:

    SELECT * FROM CHANGETABLE(VERSION TestCT, (ID), (5)) CT
    返回结果则会变为

    SYS_CHANGE
    _VERSION
    SYS_CHANGE
    _CONTEXT
    ID
    3 NULL 5

    由返回结果可以了解到ID为5的数据在当前系统中最后一次更新的更新版本号是3,如果在另外一个系统中ID为5的数据版本号不是3的话,那么就意味着需要进行复制了。

    CHANGETABLE(VERSION)函数与CHANGETABLE(CHANGES)函数最大的区别就在于它可以传入表的主键值,因此可以根据用户的需求了解每一行数据的最新版本号,而CHANGETABLE(CHANGES)则是通过某个版本号来获得自从这个版本号之后的更新信息。因此两个函数可以用于两种不同的复制拓扑,前者更加适用于双向复制,而后者则适用于单向复制。

    测试脚本

    下面给出完整的测试脚本:

    /*************************************************************
    **************************************************************
    Section 1: How to enable the change tracking
    **************************************************************
    **************************************************************/

    --Create test database and enable the change tracking feature of the database
    USE master
    GO
    CREATE DATABASE TestCT
    GO
    ALTER DATABASE TestCT
    SET Change_Tracking = ON
    (CHANGE_RETENTION = 2 MINUTES, AUTO_CLEANUP = ON)
    GO

    --Create test table and enable the change tracking feature on the table
    USE TestCT
    GO
    CREATE TABLE TestCT
    (
        ID int PRIMARY KEY,
        Name varchar(50),
        Description varchar(200)
    )
    GO
    ALTER TABLE TestCT
    ENABLE CHANGE_TRACKING
    WITH (TRACK_COLUMNS_UPDATED = ON)
    GO
    --Use sys.change_tracking_tables to get the table list which enabled change tracking
    SELECT OBJECT_NAME(object_id) AS table_name, sys.change_tracking_tables.* FROM sys.change_tracking_tables
    GO

    /*************************************************************
    **************************************************************
    Section 2: How to get change tracking information
    **************************************************************
    **************************************************************/

    --The current version is 0 now.
    SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
    --Insert two rows in two transactions
    INSERT INTO TestCT VALUES (1, 'ABC', NULL)
    INSERT INTO TestCT VALUES (2, 'XYZ', NULL)
    --The current version is 2 now.
    SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
    --Get the changed table version with table function 'CHANGETABLE'.
    --You will see two rows with different SYS_CHANGE_VERSION and SYS_CHANGE_CREATION_VERSION.
    SELECT * FROM CHANGETABLE(CHANGES TestCT, 0) CT  

    --INSERT two rows in one transaction
    BEGIN TRAN
        INSERT INTO TestCT VALUES (3, 'CBA', NULL)
        INSERT INTO TestCT VALUES (4, 'ZYX', NULL)
    COMMIT
    --The current version is 3 now.
    SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
    --Get the changed table version with table function 'CHANGETABLE'.
    --You will see two rows with same SYS_CHANGE_VERSION and SYS_CHANGE_CREATION_VERSION.
    --We use 2 as the last_sync_version value here to see net changes of after the last time insertion.
    SELECT * FROM CHANGETABLE(CHANGES TestCT, 2) CT

    --DML update which not affected the primary key
    UPDATE TestCT SET Name = 'abc' WHERE ID = 1
    --The current version is 4 now.
    SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
    --Get the changed table version with table function 'CHANGETABLE'.
    --With different last_sync_version, you will see the different result
    SELECT * FROM CHANGETABLE(CHANGES TestCT, 0) CT  --the SYS_CHANGE_OPERATION is I now
    SELECT * FROM CHANGETABLE(CHANGES TestCT, 3) CT  --the SYS_CHANGE_OPERATION is U now
    --With the CHANGE_TRACKING_IS_COLUMN_IN_MASK function, you will see which row changed.
    SELECT CHANGE_TRACKING_IS_COLUMN_IN_MASK
        (COLUMNPROPERTY(OBJECT_ID('TestCT'), 'Name', 'ColumnId'), SYS_CHANGE_COLUMNS)
        AS is_column_Name_changed,
        CHANGE_TRACKING_IS_COLUMN_IN_MASK
        (COLUMNPROPERTY(OBJECT_ID('TestCT'), 'Description', 'ColumnId'), SYS_CHANGE_COLUMNS)
        AS is_column_Description_changed
        FROM CHANGETABLE(CHANGES TestCT, 3) CT

    --DML update which affected the primary key
    UPDATE TestCT SET ID = 5 WHERE ID = 1
    --The current version is 5 now.
    SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
    --Get the changed table version with table function 'CHANGETABLE'
    --you will see one record indicate the deletion of origional record
    --and one record indicate the insertion of new record
    SELECT * FROM CHANGETABLE(CHANGES TestCT, 4) CT

    --DML delete
    DELETE TestCT WHERE ID = 5
    --The current version is 6 now.
    SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
    --Get the changed table version with table function 'CHANGETABLE'
    --you will see one record indicate the deletion of origional record
    SELECT * FROM CHANGETABLE(CHANGES TestCT, 5) CT

    --Another usefull usage of CHANGETABLE, which can help you
    --to determine the current version of a specific row
    SELECT * FROM CHANGETABLE(VERSION TestCT, (ID), (4)) CT

    --Another usefull statement which can help you distinguish the
    --change context
    DECLARE @context AS varbinary(128);
    SET @context = CAST('Test_Change_Tracking' AS varbinary(128));
    WITH CHANGE_TRACKING_CONTEXT (@context)
        UPDATE TestCT SET Description = 'NA';
    -- The change now has an associated change context
    SELECT CT.SYS_CHANGE_VERSION, CT.SYS_CHANGE_OPERATION,
        CAST(SYS_CHANGE_CONTEXT AS varchar(20)) AS change_context
        FROM CHANGETABLE(CHANGES TestCT, 6) AS CT

    --Another usefull function which can help you determine the requirement
    --of re-initialization.
    --Before the truncation or periodical cleanup, the min_valid_version is 0 now.
    SELECT CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID('TestCT')) AS min_valid_version;
    --After the truncation or periodical cleanup, the min_valid_version is 7 now.
    TRUNCATE TABLE TestCT;
    SELECT CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID('TestCT')) AS min_valid_version;
    --So the insert statement will use 8 as the SYS_CHANGE_VERSION
    INSERT INTO TestCT VALUES (1, 'ABC', NULL)
    --So the last_sync_version parameter will not affect the result now
    SELECT * FROM CHANGETABLE(CHANGES TestCT, 0) CT
    SELECT * FROM CHANGETABLE(CHANGES TestCT, 7) CT

    /*************************************************************
    **************************************************************
    Section 3: DDL affection to the change tracking
    **************************************************************
    **************************************************************/

    --After the tables schema changed, the new column will also be monitored
    ALTER TABLE TestCT ADD Age int;
    UPDATE TestCT SET Age = 10;
    SELECT CHANGE_TRACKING_IS_COLUMN_IN_MASK
        (COLUMNPROPERTY(OBJECT_ID('TestCT'), 'Name', 'ColumnId'), SYS_CHANGE_COLUMNS)
        AS is_column_Name_changed,
        CHANGE_TRACKING_IS_COLUMN_IN_MASK
        (COLUMNPROPERTY(OBJECT_ID('TestCT'), 'Description', 'ColumnId'), SYS_CHANGE_COLUMNS)
        AS is_column_Description_changed,
        CHANGE_TRACKING_IS_COLUMN_IN_MASK
        (COLUMNPROPERTY(OBJECT_ID('TestCT'), 'Age', 'ColumnId'), SYS_CHANGE_COLUMNS)
        AS is_column_Age_changed
        FROM CHANGETABLE(CHANGES TestCT, 8) CT;

    --You will find the schema modification will not be tracked.
    SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
    ALTER TABLE TestCT DROP COLUMN Age
    SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version

    January 26

    SQL Server 2008对日期时间类型的改进

    微软在备受多年的争议后,终于对日期时间数据类型开刀了,在新版的SQL Server 2008中一口气增加了4种新的日期时间数据类型,包括:

    • Date:一个纯的日期数据类型。
    • Time:一个纯的时间数据类型。
    • DateTime2:新的日期时间类型,将精度提到到了100纳秒。
    • DateTimeOffset:新的日期时间类型,在DateTime2的基础上增加了时区部分。

    下面是在SQL Server 2008中日期时间数据类型的一个简单汇总表:

    数据类型 格式 取值范围 精度 存储尺寸
    date yyyy-mm-dd 0001-1-1
    9999-12-31
    1天 3字节
    time hh:mm:ss.nnnnnn 0:0:0.000000
    23:59:59.999999
    100纳秒 3-5字节
    smalldatetime yyyy-mm-dd
    hh:mm:ss
    1900-1-1
    2079-6-6
    1分钟 4字节
    datetime yyyy-mm-dd
    hh:mm:ss:nnn
    1753-1-1
    9999-12-31
    0.00333秒 8字节
    datetime2 yyyy-mm-dd
    hh:mm:ss:nnnnnn
    0001-1-1
    9999-12-31
    100纳秒 6-8字节
    datetimeoffset yyyy-mm-dd
    hh:mm:ss:nnnnnn
    +|- hh:mm
    0001-1-1
    9999-12-31
    (全球标准时间)
    100纳秒 8-10字节

    为了使用这些数据类型,SQL Server 2008同时还引入了一系列的T-SQL函数。

    三个用于获得高精度系统时间的函数(因为是这三个函数都是取的操作系统时间,所以精度仅能达到10毫秒):

    • SYSDATETIME:返回运行SQL Server实例的服务器的本地时间,数据类型是datetime2(7),不包含时区信息。
    • SYSDATETIMEOFFSET:返回运行SQL Server实例的服务器的本地时间及时区信息,数据类型是datetimeoffset(7)。
    • SYSUTCDATETIME:返回运行SQL Server实例的服务器的标准世界时间,数据类型是datetime2(7)。

    用于时区转换的函数:

    • SWITCHOFFSET(datetimeoffset, time_zone):根据输入的世界时间以及时区信息返回某个特定时区的数据,例如SWITCHOFFSET('2008-1-1 0:0:0 + 8:00', '-07:00')返回值将是'2007-12-31 9:00 -07:00',这样我们就晓得我们元旦的时候老美的时间只是早上9:00。(有个有趣的情况是SWITCHOFFSET函数time_zone参数小时的前导0时不能省略的,就我们刚才用的那个例子如果time_zone参数写成'7:00'就会报错,必须写成'07:00',不过datetimeoffset数据里那个时区部分小时的前导0时可以省略的,也就是说'2008-1-1 0:0:0 + 8:00'和'2008-1-1 0:0:0 + 08:00'都是可以接受的,对于时区中分钟部分也是如此。不过建议大家养成良好的编码习惯,所有前导0都不要省略。)
    • TODATETIMEOFFSET(datetime, offset):根据输入的日期时间参数值和时区参数值返回一个世界时间值。例如TODATETIMEOFFSET('2008-1-1 0:0:0', '+08:00')返回值是'2008-1-1 0:0:0 + 08:00'。

    顺便列举一下SQL Server 2005中已经提供的日期时间函数,不过就不做介绍了:

    • 用于获取系统时间的函数:CURRENT_TIMESTAMP,GETDATE,GETUTCDATE
    • 返回日期时间的指定部分:DATENAME,DATEPART,DAY,MONTH,YEAR
    • 计算日期时间差异的函数:DATEDIFF, DATEADD (必须注意的是datetime、smalldatetime数据类型支持+和-运算符,但是对于date、time、datetime2、datetimeoffset则不支持。)
    • 对日期时间进行计算的函数:DATEADD
    • 设置日期时间显示格式的函数:@@DATEFIRST,SET DATEFIRST,SET DATEFORMAT,@@LANGUAGE,SET LANGUAGE,sp_helplanguage
    • 用于确认日期时间数据格式的函数:ISDATE
    January 22

    SQL Server 2008 CDC对存储空间的影响

    前面有一个帖子出了几张图用来说明CDC对数据库IO的影响,接着俺又接着对CDC产生的存储空间的影响进行研究。

    首先CDC功能对数据库存储空间产生显著影响的两张表是cdc.<capture_instance>_CT表和cdc.lsn_time_mapping表,这里简称为表1和表2。下面是对表1和表2作的一些较为深入地剖析:

    • 表1和表2的数据
      • 表1主要由3个binary(10)字段、1个int字段、1个varbinary(128)字段以及所有被选定更新跟踪的原始表字段构成。因此表1每行数据的尺寸大概是在30 + 4 + 5 (因为通常一张表需要监控的字段会在16个以内,所以暂定为2bytes的binary然后加上varbinary数据2个bytes的固定开销),也就是 39 + x(假定原始表需要监控的字段键总尺寸为x个字节)个字节。
      • 表2则有1个binary(10)字段、2个datetime字段和1个varbinary(10)字段构成。因此表2每行数据应该是20 + 16 + 12 = 48个字节。
    • 表1和表2的索引(这个不太好估算,因为不同的表聚簇索引的键值密度是不一样的,一般按照1/4的数据尺寸估算,只有多没有少啦)
      • 表1的3个binary(10)字段构成了聚簇索引,同时3个binary(10)字段加上原始数据表的聚簇索引构成一个非聚簇索引,同上面一样,我们假定原始表聚簇索引键是x个字节,那么表1的非聚簇索引每行是(30 + y(假定原始表聚簇索引键尺寸为y个字节) + 4(指向聚簇索引的内部指针))个字节。
      • 而表2中的binary(10)字段构成了聚簇索引,其中1个datetime字段构成了非聚簇索引。因此表2的非聚簇索引每行是8 + 4 = 12个字节。
    • 对原始数据表的一行数据进行UPDATE操作,会在表1中添加2行数据,而DELETE操作和INSERT操作则会增加1行数据;而对于表2则是每笔事务增加1行数据。

    因此我们作如下假定:

    • 典型的OLTP环境:
      • 原始数据表的聚簇索引为1个整型字段,同时需要监控的字段总尺寸为50字节(约为5个decimal(19)或5个char(10))
      • 对原始表提交100,000个事务
      • 产生1,000,000行次数据操作,其中UPDATE占60%,INSERT和DELETE占40%
      • 那么最终CDC产生的额外数据存储空间应该为(39 + 4 + 50) * (1000000 * 1.2 + 1000000 * 0.4) + 48 * 100000 = 153,600,000个字节,约为164MB(假定数据页填充率为90%)。
      • CDC产生的额外的非聚簇索引存储空间按照数据尺寸的1/4估算,大概是40MB左右。
      • 因此约合200MB左右。
    • 典型的OLAP环境(貌似OLAP环境不应该使用CDC了吧,所以就不算了)

    经过这样的对比我们可以知道,CDC在生产环境特别是OLTP环境对存储空间的影响不算太明显的,当然这个还要取决于DBA在原始数据表上选取多少字段进行监控,以及这些字段的数据尺寸,同时还有原始数据表的聚簇索引键值密度。

    最后说明这里只是我长夜漫漫无心睡眠随便算算的,生产环境还是要仔细套用上面的公式计算一下的。另外需要说明的是表1和表2都是由一个异步的进程通过读取日志来完成的,因此表1和表2的数据刷新和原始数据表的刷新会有一定的延时。

    SQL Server 2008 CDC IO测试

    前两天测试了一下SQL Server 2008的CDC功能,今天又花了一些时间对CDC功能对IO的影响进行了一些评估。

    整个评估的思路是这样的:

    1. 创建两个数据库
    2. 在两个数据库中分别创建一张结构完全相同的表,一个数据库启用CDC功能,而另外一个禁用CDC功能
    3. 向两张表中写入相同行数的数据
    4. 利用动态管理视图sys.dm_io_virtual_file_stats来获得两个数据库文件上的IO
    5. 利用sysindexes来获得两个数据库中数据表的存储消耗情况

    因为是在虚拟机中进行的测试,所以选取了比较小的数据表(AdventureWorks数据库中的SalesOrderDetails),大约有12万行数据。

    评估的结果如下:

    Number of Bytes Read

    Number of Read

    从上面两张表中可以看到,CDC激活后日志文件的读会显著增加。原因是CDC在写更新跟踪表时,需要读取日志。

    Number of Bytes Written

    Number of Write

    从上面两张表中可以看到激活CDC后数据文件的写入和日志文件写入都会显著增加,不过需要考虑到CDC激活后会需要多写一张表,在本例中就是dbo_SalesOrderDetails_CT,所以这种增加是可以理解的。当然在生产环境中并不会对数据表的所有列进行CDC监控,所以激活CDC对IO写入的影响还需要针对不同情况进行分析。

     

    从上面这张图可以看出,CDC激活后会生产数据表不会消耗更多的存储空间,但是更新跟踪表会需要俄外的存储空间。另外可以发现的一点是,在本例中dbo_SalesOrderDetail_CT表消耗的空间比SalesOrderDetail表多,这是因为在dbo_SalesOrderDetail_CT表中加入了一些额外的字段,例如_$start_lsn和_$end_lsn,同时注意观察dbo_SalesOrderDetail_CT表会发现,SQL Server在这张表上使用_$start_lsn、_$end_lsn和_$seqval三个字段作为聚簇索引,而SalesOrderDetail表上原来的聚簇索引(SalesOrderID,SalesOrderDetailID)再加上_$start_lsn、_$end_lsn和_$seqval三个字段则被创建为一个非聚簇索引,所以这就导致了dbo_SalesOrderDetail_CT表需要消耗比原始表更多的空间,不过原始数据表上的非聚簇索引不会在CDC跟踪表上被创建,这也就说明了原始数据表聚簇索引的大小也会对CDC引发的IO产生影响。

    经过以上测试,我们可以发现以下情况:

    • CDC激活会显著增加日志文件的读操作
    • CDC激活后更新跟踪表会产生额外的写入,并消耗存储空间
    • CDC激活后,原数据表的聚簇索引尺寸会影响到CDC产生的IO数据量,而原始数据表上的非聚簇索引则不会

    因此建议CDC激活的环境下,应该将更新跟踪表写入与原始表不同的文件组并存放在不同的存储设备上,同时应该注意为日志文件选取可提高读取性能的存储硬件上,比如RAID10。

    最后,附上是用于测试的脚本:

    --Step 1: Test IO under CDC enabled enviroment
    USE master
    PRINT getdate()
    PRINT 'Create test database'
    GO
    CREATE DATABASE TestIO_CDC
    GO

    PRINT getdate()
    PRINT 'Enable CDC feature on the test database'
    GO
    USE TestIO_CDC
    EXEC sp_cdc_enable_db_change_data_capture;
    GO
    PRINT getdate()
    PRINT 'Create test table'
    GO
    CREATE TABLE dbo.SalesOrderDetail(
        SalesOrderID int NOT NULL,
        SalesOrderDetailID int NOT NULL,
        CarrierTrackingNumber nvarchar(25) NULL,
        OrderQty smallint NOT NULL,
        ProductID int NOT NULL,
        SpecialOfferID int NOT NULL,
        UnitPrice money NOT NULL,
        UnitPriceDiscount money NOT NULL,
        rowguid uniqueidentifier ROWGUIDCOL  NOT NULL,
        ModifiedDate datetime NOT NULL,
    CONSTRAINT PK_SalesOrderDetail PRIMARY KEY CLUSTERED
        (SalesOrderID ASC, SalesOrderDetailID ASC)
    )
    GO
    PRINT getdate()
    PRINT 'Enable CDC feature on the test table'
    GO
    EXEC sp_cdc_enable_table_change_data_capture 'dbo', 'SalesOrderDetail', @role_name= NULL,
        @supports_net_changes = 1;
    GO
    PRINT getdate()
    PRINT 'Start inserting data'
    GO
    INSERT INTO dbo.SalesOrderDetail
        (SalesOrderID, SalesOrderDetailID, CarrierTrackingNumber,
        OrderQty, ProductID, SpecialOfferID, UnitPrice, UnitPriceDiscount,
        rowguid, ModifiedDate)
        SELECT SalesOrderID, SalesOrderDetailID, CarrierTrackingNumber,
        OrderQty, ProductID, SpecialOfferID, UnitPrice, UnitPriceDiscount,
        rowguid, ModifiedDate FROM AdventureWorks.Sales.SalesOrderDetail
    GO

    --Step 2: Test IO under CDC disabled enviroment
    PRINT getdate()
    PRINT 'Create test database'
    GO
    USE master
    CREATE DATABASE TestIO_Non_CDC
    GO
    USE TestIO_Non_CDC
    GO
    PRINT getdate()
    PRINT 'Create test table'
    GO
    CREATE TABLE dbo.SalesOrderDetail(
        SalesOrderID int NOT NULL,
        SalesOrderDetailID int NOT NULL,
        CarrierTrackingNumber nvarchar(25) NULL,
        OrderQty smallint NOT NULL,
        ProductID int NOT NULL,
        SpecialOfferID int NOT NULL,
        UnitPrice money NOT NULL,
        UnitPriceDiscount money NOT NULL,
        rowguid uniqueidentifier ROWGUIDCOL  NOT NULL,
        ModifiedDate datetime NOT NULL,
    CONSTRAINT PK_SalesOrderDetail PRIMARY KEY CLUSTERED
        (SalesOrderID ASC, SalesOrderDetailID ASC)
    )
    GO
    PRINT getdate()
    PRINT 'Start inserting table'
    GO
    INSERT INTO dbo.SalesOrderDetail
        (SalesOrderID, SalesOrderDetailID, CarrierTrackingNumber,
        OrderQty, ProductID, SpecialOfferID, UnitPrice, UnitPriceDiscount,
        rowguid, ModifiedDate)
        SELECT SalesOrderID, SalesOrderDetailID, CarrierTrackingNumber,
        OrderQty, ProductID, SpecialOfferID, UnitPrice, UnitPriceDiscount,
        rowguid, ModifiedDate FROM AdventureWorks.Sales.SalesOrderDetail
    GO

    PRINT getdate()
    PRINT 'Wait 30 seconds for IO finished'
    GO
    WAITFOR DELAY '0:0:30'
    GO
    PRINT getdate()
    PRINT 'Start getting the IO statistics data'
    GO
    USE master
    GO
    --IO Statistics
    SELECT DB_NAME(database_id) AS Database_Name,
        FILE_NAME(file_id) AS File_Name, num_of_reads, num_of_bytes_read,
        num_of_writes, num_of_bytes_written
        FROM sys.dm_io_virtual_file_stats(DB_ID(N'TestIO_CDC'), NULL)
    UNION
    SELECT DB_NAME(database_id) AS Database_Name,
        FILE_NAME(file_id) AS File_Name, num_of_reads, num_of_bytes_read,
        num_of_writes, num_of_bytes_written
        FROM sys.dm_io_virtual_file_stats(DB_ID(N'TestIO_Non_CDC'), NULL);

    --Storage Concumed
    USE TestIO_CDC
    GO
    SELECT 'Enabled' AS 'CDC Feature', OBJECT_NAME(id) AS TableName, dpages * 8 AS 'Data (KB)', reserved * 8 AS 'Reserved (KB)'
        FROM TestIO_CDC.sys.sysindexes
        WHERE id IN(OBJECT_ID('dbo.SalesOrderDetail'), OBJECT_ID('cdc.dbo_SalesOrderDetail_CT'))
            AND indid = 1
    GO
    USE TestIO_Non_CDC
    GO
    SELECT 'Disabled' AS 'CDC Feature', OBJECT_NAME(id) AS TableName, dpages * 8 AS 'Data (KB)', reserved * 8 AS 'Reserved (KB)'
        FROM TestIO_Non_CDC.sys.sysindexes
        WHERE id IN(OBJECT_ID('dbo.SalesOrderDetail'))
    GO