TSQL性能优化详解billiat

故事开篇:你和你的团队经过不懈努力,终于使网站成功上线,刚开始时,注册用户较少,网站性能表现不错,但随着注册用户的增多,访问速度开始变慢,一些用户开始发来邮件表示抗议,事情变得越来越糟,为了留住用户,你开始着手调查访问变慢的原因。

范围

同时,我介绍的这些技巧主要是面向程序开发人员的,虽然DBA也是优化数据库的一支主要力量,但DBA使用的优化方法不在我的讨论范围之内。

当一个基于数据库的应用程序运行起来很慢时,90%的可能都是由于数据访问程序的问题,要么是没有优化,要么是没有按最佳方法编写代码,因此你需要审查和优化你的数据访问/处理程序。

我将会谈到10个步骤来优化数据访问程序,先从最基本的索引说起吧!

第一步:应用正确的索引

我之所以先从索引谈起是因为采用正确的索引会使生产系统的性能得到质的提升,另一个原因是创建或修改索引是在数据库上进行的,不会涉及到修改程序,并可以立即见到成效。

我们还是温习一下索引的基础知识吧,我相信你已经知道什么是索引了,但我见到很多人都还不是很明白,我先给大家将一个故事吧。

[这就好比数据表没有主键一样,搜索表中的数据时,数据库引擎必须进行全表扫描,效率极其低下。]

更糟的是图书馆的图书越来越多,图书管理员的工作变得异常痛苦,有一天来了一个聪明的小伙子,他看到图书管理员的痛苦工作后,想出了一个办法,他建议将每本书都编上号,然后按编号放到书架上,如果有人指定了图书编号,那么图书管理员很快就可以找到它的位置了。

[给图书编号就象给表创建主键一样,创建主键时,会创建聚集索引树,表中的所有行会在文件系统上根据主键值进行物理排序,当查询表中任一行时,数据库首先使用聚集索引树找到对应的数据页(就象首先找到书架一样),然后在数据页中根据主键键值找到目标行(就象找到书架上的书一样)。]

[在一个表上只能创建一个聚集索引,就象书只能按一种规则摆放一样。]

[这就好像你给Product表增加了主键ProductID,但除此之外没有建立其它索引,当使用ProductName进行检索时,数据库引擎又只要进行全表扫描,逐个寻找了。]

图书管理员开始了新的思考,读者可能还会根据图书的其它属性来找书,如作者,于是他用同样的办法为作者也创建了目录,现在可以根据图书编号,书名和作者在1分钟内查找任何图书了,图书管理员的工作变得轻松了,故事也到此结束。

到此,我相信你已经完全理解了索引的真正含义。假设我们有一个Products表,创建了一个聚集索引(根据表的主键自动创建的),我们还需要在ProductName列上创建一个非聚集索引,创建非聚集索引时,数据库引擎会为非聚集索引自动创建一个索引树(就象故事中的“图书名称”目录一样),产品名称会存储在索引页中,每个索引页包括一定范围的产品名称和它们对应的主键键值,当使用产品名称进行检索时,数据库引擎首先会根据产品名称查找非聚集索引树查出主键键值,然后使用主键键值查找聚集索引树找到最终的产品。

下图显示了一个索引树的结构

图1索引树结构

它叫做B+树(或平衡树),中间节点包含值的范围,指引SQL引擎应该在哪里去查找特定的索引值,叶子节点包含真正的索引值,如果这是一个聚集索引树,叶子节点就是物理数据页,如果这是一个非聚集索引树,叶子节点包含索引值和聚集索引键(数据库引擎使用它在聚集索引树中查找对应的行)。

确保每个表都有主键

这样可以确保每个表都有聚集索引(表在磁盘上的物理存储是按照主键顺序排列的),使用主键检索表中的数据,或在主键字段上进行排序,或在where子句中指定任意范围的主键键值时,其速度都是非常快的。

在下面这些列上创建非聚集索引:

1)搜索时经常使用到的;

2)用于连接其它表的;

3)用于外键字段的;

4)高选中性的;

5)ORDERBY子句使用到的;

6)XML类型。

下面是一个创建索引的例子:

也可以使用SQLServer管理工作台在表上创建索引,如图2所示。

图2使用SQLServer管理工作台创建索引

第二步:创建适当的覆盖索引

假设你在Sales表(SelesID,SalesDate,SalesPersonID,ProductID,Qty)的外键列(ProductID)上创建了一个索引,假设ProductID列是一个高选中性列,那么任何在where子句中使用索引列(ProductID)的select查询都会更快,如果在外键上没有创建索引,将会发生全部扫描,但还有办法可以进一步提升查询性能。

假设Sales表有10,000行记录,下面的SQL语句选中400行(总行数的4%):

我们来看看这条SQL语句在SQL执行引擎中是如何执行的:

1)Sales表在ProductID列上有一个非聚集索引,因此它查找非聚集索引树找出ProductID=112的记录;

2)包含ProductID=112记录的索引页也包括所有的聚集索引键(所有的主键键值,即SalesID);

3)针对每一个主键(这里是400),SQLServer引擎查找聚集索引树找出真实的行在对应页面中的位置;

SQLServer引擎从对应的行查找SalesDate和SalesPersonID列的值。

在上面的步骤中,对ProductID=112的每个主键记录(这里是400),SQLServer引擎要搜索400次聚集索引树以检索查询中指定的其它列(SalesDate,SalesPersonID)。

如果非聚集索引页中包括了聚集索引键和其它两列(SalesDate,,SalesPersonID)的值,SQLServer引擎可能不会执行上面的第3和4步,直接从非聚集索引树查找ProductID列速度还会快一些,直接从索引页读取这三列的数值。

幸运的是,有一种方法实现了这个功能,它被称为“覆盖索引”,在表列上创建覆盖索引时,需要指定哪些额外的列值需要和聚集索引键值(主键)一起存储在索引页中。下面是在Sales表ProductID列上创建覆盖索引的例子:

创建覆盖索引时使用数据库调整顾问

我们知道,当SQL出问题时,SQLServer引擎中的优化器根据下列因素自动生成不同的查询计划:

1)数据量

2)统计数据

3)索引变化

4)TSQL中的参数值

因此我们在创建索引时,要知道执行计划是否会真正利用它,但我们怎么才能知道呢答案就是在测试服务器上模拟生产环境负载,然后创建合适的索引并进行测试,如果这样测试发现索引可以提高性能,那么它在生产环境也就更可能提高应用程序的性能了。

虽然要模拟一个真实的负载比较困难,但目前已经有很多工具可以帮助我们。

第三步:整理索引碎片

你可能已经创建好了索引,并且所有索引都在工作,但性能却仍然不好,那很可能是产生了索引碎片,你需要进行索引碎片整理。

什么是索引碎片

有两种类型的索引碎片:内部碎片和外部碎片。

内部碎片:为了有效的利用内存,使内存产生更少的碎片,要对内存分页,内存以页为单位来使用,最后一页往往装不满,于是形成了内部碎片。

外部碎片:为了共享要分段,在段的换入换出时形成外部碎片,比如5K的段换出后,有一个4k的段进来放到原来5k的地方,于是形成1k的外部碎片。

如何知道是否发生了索引碎片

执行下面的SQL语句就知道了(下面的语句可以在SQLServer2005及后续版本中运行,用你的数据库名替换掉这里的AdventureWorks):

执行后显示AdventureWorks数据库的索引碎片信息。

图3索引碎片信息

使用下面的规则分析结果,你就可以找出哪里发生了索引碎片:

1)ExternalFragmentation的值>10表示对应的索引发生了外部碎片;

2)InternalFragmentation的值<75表示对应的索引发生了内部碎片。

如何整理索引碎片

有两种整理索引碎片的方法:

1)重组有碎片的索引:执行下面的命令

ALTERINDEXALLONTableNameREORGANIZE

2)重建索引:执行下面的命令

ALTERINDEXALLONTableNameREBUILDWITH(FILLFACTOR=90,ONLINE=ON)

也可以使用索引名代替这里的“ALL”关键字重组或重建单个索引,也可以使用SQLServer管理工作台进行索引碎片的整理。

图4使用SQLServer管理工作台整理索引碎片

什么时候用重组,什么时候用重建呢

当对应索引的外部碎片值介于10-15之间,内部碎片值介于60-75之间时使用重组,其它情况就应该使用重建。

值得注意的是重建索引时,索引对应的表会被锁定,但重组不会锁表,因此在生产系统中,对大表重建索引要慎重,因为在大表上创建索引可能会花几个小时,幸运的是,从SQLServer2005开始,微软提出了一个解决办法,在重建索引时,将ONLINE选项设置为ON,这样可以保证重建索引时表仍然可以正常使用。

虽然索引可以提高查询速度,但如果你的数据库是一个事务型数据库,大多数时候都是更新操作,更新数据也就意味着要更新索引,这个时候就要兼顾查询和更新操作了,因为在OLTP数据库表上创建过多的索引会降低整体数据库性能。

我给大家一个建议:如果你的数据库是事务型的,平均每个表上不能超过5个索引,如果你的数据库是数据仓库型,平均每个表可以创建10个索引都没问题。

在前面我们介绍了如何正确使用索引,调整索引是见效最快的性能调优方法,但一般而言,调整索引只会提高查询性能。除此之外,我们还可以调整数据访问代码和TSQL,本文就介绍如何以最优的方法重构数据访问代码和TSQL。

第四步:将TSQL代码从应用程序迁移到数据库中

也许你不喜欢我的这个建议,你或你的团队可能已经有一个默认的潜规则,那就是使用ORM(ObjectRelationalMapping,即对象关系映射)生成所有SQL,并将SQL放在应用程序中,但如果你要优化数据访问性能,或需要调试应用程序性能问题,我建议你将SQL代码移植到数据库上(使用存储过程,视图,函数和触发器),原因如下:

1、使用存储过程,视图,函数和触发器实现应用程序中SQL代码的功能有助于减少应用程序中SQL复制的弊端,因为现在只在一个地方集中处理SQL,为以后的代码复用打下了良好的基础。

2、使用数据库对象实现所有的TSQL有助于分析TSQL的性能问题,同时有助于你集中管理TSQL代码。

3、将TSQL移植到数据库上去后,可以更好地重构TSQL代码,以利用数据库的高级索引特性。此外,应用程序中没了SQL代码也将更加简洁。

虽然这一步可能不会象前三步那样立竿见影,但做这一步的主要目的是为后面的优化步骤打下基础。如果在你的应用程序中使用ORM(如NHibernate)实现了数据访问例行程序,在测试或开发环境中你可能发现它们工作得很好,但在生产数据库上却可能遇到问题,这时你可能需要反思基于ORM的数据访问逻辑,利用TSQL对象实现数据访问例行程序是一种好办法,这样做有更多的机会从数据库角度来优化性能。

我向你保证,如果你花1-2人月来完成迁移,那以后肯定不止节约1-2人年的的成本。

OK!假设你已经照我的做的了,完全将TSQL迁移到数据库上去了,下面就进入正题吧!

第五步:识别低效TSQL,采用最佳实践重构和应用TSQL

由于每个程序员的能力和习惯都不一样,他们编写的TSQL可能风格各异,部分代码可能不是最佳实现,对于水平一般的程序员可能首先想到的是编写TSQL实现需求,至于性能问题日后再说,因此在开发和测试时可能发现不了问题。

也有一些人知道最佳实践,但在编写代码时由于种种原因没有采用最佳实践,等到用户发飙的那天才乖乖地重新埋头思考最佳实践。

我觉得还是有必要介绍一下具有都有哪些最佳实践。

1、在查询中不要使用“select*”

(1)检索不必要的列会带来额外的系统开销,有句话叫做“该省的则省”;

(2)数据库不能利用“覆盖索引”的优点,因此查询缓慢。

2、在select清单中避免不必要的列,在连接条件中避免不必要的表

(1)在select查询中如有不必要的列,会带来额外的系统开销,特别是LOB类型的列;

3、不要在子查询中使用count()求和执行存在性检查

(1)不要使用

使用

代替;

(2)当你使用count()时,SQLServer不知道你要做的是存在性检查,它会计算所有匹配的值,要么会执行全表扫描,要么会扫描最小的非聚集索引;

(3)当你使用EXISTS时,SQLServer知道你要执行存在性检查,当它发现第一个匹配的值时,就会返回TRUE,并停止查询。类似的应用还有使用IN或ANY代替count()。

4、避免使用两个不同类型的列进行表的连接

(1)当连接两个不同类型的列时,其中一个列必须转换成另一个列的类型,级别低的会被转换成高级别的类型,转换操作会消耗一定的系统资源;

(2)如果你使用两个不同类型的列来连接表,其中一个列原本可以使用索引,但经过转换后,优化器就不会使用它的索引了。例如:

在这个例子中,SQLServer会将int列转换为float类型,因为int比float类型的级别低,large_table.int_column上的索引就不会被使用,但smalltable.float_column上的索引可以正常使用。

5、避免死锁

(1)在你的存储过程和触发器中访问同一个表时总是以相同的顺序;

(2)事务应经可能地缩短,在一个事务中应尽可能减少涉及到的数据量;

(3)永远不要在事务中等待用户输入。

6、使用“基于规则的方法”而不是使用“程序化方法”编写TSQL

(1)数据库引擎专门为基于规则的SQL进行了优化,因此处理大型结果集时应尽量避免使用程序化的方法(使用游标或UDF[UserDefinedFunctions]处理返回的结果集);

(2)如何摆脱程序化的SQL呢有以下方法:

-使用内联子查询替换用户定义函数;

-如果确实需要程序化代码,至少应该使用表变量代替游标导航和处理结果集。

7、避免使用count(*)获得表的记录数

(1)为了获得表中的记录数,我们通常使用下面的SQL语句:

这条语句会执行全表扫描才能获得行数。

(2)但下面的SQL语句不会执行全表扫描一样可以获得行数:

8、避免使用动态SQL

除非迫不得已,应尽量避免使用动态SQL,因为:

(1)动态SQL难以调试和故障诊断;

(2)如果用户向动态SQL提供了输入,那么可能存在SQL注入风险。

9、避免使用临时表

(1)除非却有需要,否则应尽量避免使用临时表,相反,可以使用表变量代替;

10、使用全文搜索搜索文本数据,取代like搜索

全文搜索始终优于like搜索:

(1)全文搜索让你可以实现like不能完成的复杂搜索,如搜索一个单词或一个短语,搜索一个与另一个单词或短语相近的单词或短语,或者是搜索同义词;

(2)实现全文搜索比实现like搜索更容易(特别是复杂的搜索);

11、使用union实现or操作

(1)在查询中尽量不要使用or,使用union合并两个不同的查询结果集,这样查询性能会更好;

(2)如果不是必须要不同的结果集,使用unionall效果会更好,因为它不会对结果集排序。

12、为大对象使用延迟加载策略

(1)在不同的表中存储大对象(如VARCHAR(MAX),Image,Text等),然后在主表中存储这些大对象的引用;

(2)在查询中检索所有主表数据,如果需要载入大对象,按需从大对象表中检索大对象。

13、使用VARCHAR(MAX),VARBINARY(MAX)和NVARCHAR(MAX)

(1)在SQLServer2000中,一行的大小不能超过800字节,这是受SQLServer内部页面大小8KB的限制造成的,为了在单列中存储更多的数据,你需要使用TEXT,NTEXT或IMAGE数据类型(BLOB);

(2)这些和存储在相同表中的其它数据不一样,这些页面以B-Tree结构排列,这些数据不能作为存储过程或函数中的变量,也不能用于字符串函数,如REPLACE,CHARINDEX或SUBSTRING,大多数时候你必须使用READTEXT,WRITETEXT和UPDATETEXT;

(3)为了解决这个问题,在SQLServer2005中增加了VARCHAR(MAX),VARBINARY(MAX)和NVARCHAR(MAX),这些数据类型可以容纳和BLOB相同数量的数据(2GB),和其它数据类型使用相同的数据页;

(4)当MAX数据类型中的数据超过8KB时,使用溢出页(在ROW_OVERFLOW分配单元中)指向源数据页,源数据页仍然在IN_ROW分配单元中。

14、在用户定义函数中使用下列最佳实践

不要在你的存储过程,触发器,函数和批处理中重复调用函数,例如,在许多时候,你需要获得字符串变量的长度,无论如何都不要重复调用LEN函数,只调用一次即可,将结果存储在一个变量中,以后就可以直接使用了。

15、在存储过程中使用下列最佳实践

(1)不要使用SP_xxx作为命名约定,它会导致额外的搜索,增加I/O(因为系统存储过程的名字就是以SP_开头的),同时这么做还会增加与系统存储过程名称冲突的几率;

(3)当索引结构发生变化时,在EXECUTE语句中(第一次)使用WITHRECOMPILE子句,以便存储过程可以利用最新创建的索引;

(4)使用默认的参数值更易于调试。

16、在触发器中使用下列最佳实践

(1)最好不要使用触发器,触发一个触发器,执行一个触发器事件本身就是一个耗费资源的过程;

(2)如果能够使用约束实现的,尽量不要使用触发器;

(3)不要为不同的触发事件(Insert,Update和Delete)使用相同的触发器;

(4)不要在触发器中使用事务型代码。

17、在视图中使用下列最佳实践

(1)为重新使用复杂的TSQL块使用视图,并开启索引视图;

(2)如果你不想让用户意外修改表结构,使用视图时加上SCHEMABINDING选项;

(3)如果只从单个表中检索数据,就不需要使用视图了,如果在这种情况下使用视图反倒会增加系统开销,一般视图会涉及多个表时才有用。

18、在事务中使用下列最佳实践

(1)SQLServer2005之前,在BEGINTRANSACTION之后,每个子查询修改语句时,必须检查@@ERROR的值,如果值不等于0,那么最后的语句可能会导致一个错误,如果发生任何错误,事务必须回滚。从SQLServer2005开始,Try..Catch..代码块可以处理TSQL中的事务,因此在事务型代码中最好加上Try…Catch…;

(2)避免使用嵌套事务,使用@@TRANCOUNT变量检查事务是否需要启动(为了避免嵌套事务);

要完全列举最佳实践不是本文的初衷,当你了解了这些技巧后就应该拿来使用,否则了解了也没有价值。此外,你还需要评审和监视数据访问代码是否遵循下列标准和最佳实践。

如何分析和识别你的TSQL中改进的范围

理想情况下,大家都想预防疾病,而不是等病发了去治疗。但实际上这个愿望根本无法实现,即使你的团队成员全都是专家级人物,我也知道你有进行评审,但代码仍然一团糟,因此需要知道如何治疗疾病一样重要。

首先需要知道如何诊断性能问题,诊断就得分析TSQL,找出瓶颈,然后重构,要找出瓶颈就得先学会分析执行计划。

理解查询执行计划

当你将SQL语句发给SQLServer引擎后,SQLServer首先要确定最合理的执行方法,查询优化器会使用很多信息,如数据分布统计,索引结构,元数据和其它信息,分析多种可能的执行计划,最后选择一个最佳的执行计划。

可以使用SQLServerManagementStudio预览和分析执行计划,写好SQL语句后,点击SQLServerManagementStudio上的评估执行计划按钮查看执行计划,如图1所示。

图1在ManagementStudio中评估执行计划

在执行计划图中的每个图标代表计划中的一个行为(操作),应从右到左阅读执行计划,每个行为都一个相对于总体执行成本(100%)的成本百分比。

在上面的执行计划图中,右边的那个图标表示在HumanResources表上的一个“聚集索引扫描”操作(阅读表中所有主键索引值),需要100%的总体查询执行成本,图中左边那个图标表示一个select操作,它只需要0%的总体查询执行成本。

下面是一些比较重要的图标及其对应的操作:

图2常见的重要图标及对应的操作

注意执行计划中的查询成本,如果说成本等于100%,那很可能在批处理中就只有这个查询,如果在一个查询窗口中有多个查询同时执行,那它们肯定有各自的成本百分比(小于100%)。

图3查看执行计划中行为(操作)的详细信息

查看执行计划时,我们应该获得什么信息

当你的查询很慢时,你就应该看看预估的执行计划(当然也可以查看真实的执行计划),找出耗时最多的操作,注意观察以下成本通常较高的操作:

1、表扫描(TableScan)

当表没有聚集索引时就会发生,这时只要创建聚集索引或重整索引一般都可以解决问题。

2、聚集索引扫描(ClusteredIndexScan)

有时可以认为等同于表扫描,当某列上的非聚集索引无效时会发生,这时只要创建一个非聚集索引就ok了。

3、哈希连接(HashJoin)

当连接两个表的列没有被索引时会发生,只需在这些列上创建索引即可。

4、嵌套循环(NestedLoops)

当非聚集索引不包括select查询清单的列时会发生,只需要创建覆盖索引问题即可解决。

5、RID查找(RIDLookup)

当你有一个非聚集索引,但相同的表上却没有聚集索引时会发生,此时数据库引擎会使用行ID查找真实的行,这时一个代价高的操作,这时只要在该表上创建聚集索引即可。

TSQL重构真实的故事

只有解决了实际的问题后,知识才转变为价值。当我们检查应用程序性能时,发现一个存储过程比我们预期的执行得慢得多,在生产数据库中检索一个月的销售数据居然要50秒,下面就是这个存储过程的执行语句:

execuspGetSalesInfoForDateRange‘1/1/2009’,31/12/2009,’Cap’

Tom受命来优化这个存储过程,下面是这个存储过程的代码:

分析索引

首先,Tom想到了审查这个存储过程使用到的表的索引,很快他发现下面两列的索引无故丢失了:

OrderDetails.ProductID

OrderDetails.SalesOrderID

他在这两个列上创建了非聚集索引,然后再执行存储过程:

execuspGetSalesInfoForDateRange‘1/1/2009’,31/12/2009withrecompile

性能有所改变,但仍然低于预期(这次花了35秒),注意这里的withrecompile子句告诉SQLServer引擎重新编译存储过程,重新生成执行计划,以利用新创建的索引。

分析查询执行计划

Tom接下来查看了SQLServerManagementStudio中的执行计划,通过分析,他找到了某些重要的线索:

2、发生了一个嵌套循环连接。

实施最佳实践

Tom发现有个UDF有问题,代码如下:

在计算订单总金额时看起来代码很程序化,Tom决定在UDF的SQL中使用内联SQL。

dbo.ufnGetLineTotal(SalesOrderDetailID)Total--旧代码

(CurrentProductRate-CurrentDiscount)*OrderQtyTotal--新代码

在select查询清单中放弃不必要的Text列

小结

看起来我们介绍了好多种优化数据访问的技巧,但大家要知道优化数据访问是一个无止境的过程,同样大家要相信一个信念,无论你的系统多么庞大,多么复杂,只要灵活运用我们所介绍的这些技巧,你一样可以驯服它们。下一篇将介绍高级索引和反范式化。

经过索引优化,重构TSQL后你的数据库还存在性能问题吗完全有可能,这时必须得找另外的方法才行。SQLServer在索引方面还提供了某些高级特性,可能你还从未使用过,利用高级索引会显著地改善系统性能,本文将从高级索引技术谈起,另外还将介绍反范式化技术。

第六步:应用高级索引

实施计算列并在这些列上创建索引

你可能曾经写过从数据库查询一个结果集的应用程序代码,对结果集中每一行进行计算生成最终显示输出的信息。例如,你可能有一个查询从数据库检索订单信息,在应用程序代码中你可能已经通过对产品和销售量执行算术操作计算出了总的订单价格,但为什么你不在数据库中执行这些操作呢

请看下面这张图,你可以通过指定一个公式将一个数据库表列作为计算列,你的TSQL在查询清单中包括这个计算列,SQL引擎将会应用这个公式计算出这一列的值,在执行查询时,数据库引擎将会计算订单总价,并为计算列返回结果。

图1计算列

使用计算列你可以将计算工作全部交给后端执行,但如果表的行数太多可能计算性能也不高,如果计算列出现在Select查询的where子句中情况会更糟,在这种情况下,为了匹配where子句指定的值,数据库引擎不得不计算表中所有行中计算列的值,这是一个低效的过程,因为它总是需要全表扫描或全聚集索引扫描。

因此问题就来了,如何提高计算列的性能呢解决办法是在计算列上创建索引,当计算列上有索引后,SQLServer会提前计算结果,然后在结果之上构建索引。此外,当对应列(计算列依赖的列)的值更新时,计算列上的索引值也会更新。因此,在执行查询时,数据库引擎不会为结果集中的每一行都执行一次计算公式,相反,通过索引可直接获得计算列预先计算出的值,因此在计算列上创建一个索引将会加快查询速度。

提示:如果你想在计算列上创建索引,必须确保计算列上的公式不能包括任何“非确定的”函数,例如getdate()就是一个非确定的函数,因为每次调用它,它返回的值都是不一样的。

创建索引视图

你是否知道可以在视图上创建索引OK,不知道没关系,看了我的介绍你就明白了。

为什么要使用视图

大家都知道,视图本身不存储任何数据,只是一条编译的select语句。数据库会为视图生成一个执行计划,视图是可以重复使用的,因为执行计划也可以重复使用。

视图本身不会带来性能的提升,我曾经以为它会“记住”查询结果,但后来我才知道它除了是一个编译了的查询外,其它什么都不是,视图根本记不住查询结果,我敢打赌好多刚接触SQL的人都会有这个错误的想法。

但是现在我要告诉你一个方法让视图记住查询结果,其实非常简单,就是在视图上创建索引就可以了。

如果你在视图上应用了索引,视图就成为索引视图,对于一个索引视图,数据库引擎处理SQL,并在数据文件中存储结果,和聚集表类似,当基础表中的数据发生变化时,SQLServer会自动维护索引,因此当你在索引视图上查询时,数据库引擎简单地从索引中查找值,速度当然就很快了,因此在视图上创建索引可以明显加快查询速度。

但请注意,天下没有免费的午餐,创建索引视图可以提升性能,当基础表中的数据发生变化时,数据库引擎也会更新索引,因此,当视图要处理很多行,且要求和,当数据和基础表不经常发生变化时,就应该考虑创建索引视图。

如何创建索引视图

1)创建/修改视图时指定SCHEMABINDING选项:

2)在视图上创建一个唯一的聚集索引;

3)视需要在视图上创建一个非聚集索引。

不是所有视图上都可以创建索引,在视图上创建索引存在以下限制:

1)创建视图时使用了SCHEMABINDING选项,这种情况下,数据库引擎不允许你改变表的基础结构;

2)视图不能包含任何非确定性函数,DISTINCT子句和子查询;

3)视图中的底层表必须由聚集索引(主键)。

如果你发现你的应用程序中使用的TSQL是用视图实现的,但存在性能问题,那此时给视图加上索引可能会带来性能的提升。

为用户定义函数(UDF)创建索引

在用户定义函数上也可以创建索引,但不能直接在它上面创建索引,需要创建一个辅助的计算列,公式就使用用户定义函数,然后在这个计算列字段上创建索引。具体步骤如下:

1)首先创建一个确定性的函数(如果不存在的话),在函数定义中添加SCHEMABINDING选项,如:

2)在目标表上增加一个计算列,使用前面定义的函数作为该列的计算公式,如图2所示。

3)在计算列上创建索引

当你的查询中包括UDF时,如果在该UDF上创建了以计算列为基础的索引,特别是两个表或视图的连接条件中使用了UDF,性能都会有明显的改善。

在XML列上创建索引

在SQLServer(2005和后续版本)中,XML列是以二进制大对象(BLOB)形式存储的,可以使用XQuery进行查询,但如果没有索引,每次查询XML数据类型时都非常耗时,特别是大型XML实例,因为SQLServer在运行时需要分隔二进制大对象评估查询。为了提升XML数据类型上的查询性能,XML列可以索引,XML索引分为两类。

主XML索引

创建XML列上的主索引时,SQLServer会切碎XML内容,创建多个数据行,包括元素,属性名,路径,节点类型和值等,创建主索引让SQLServer更轻松地支持XQuery请求。下面是创建一个主XML索引的示例语法。

次要XML索引

虽然XML数据已经被切条,但SQLServer仍然要扫描所有切条的数据才能找到想要的结果,为了进一步提升性能,还需要在主XML索引之上创建次要XML索引。有三种次要XML索引。

1)“路径”(Path)次要XML索引:使用.exist()方法确定一个特定的路径是否存在时它很有用;

2)“值”(Value)次要XML索引:用于执行基于值的查询,但不知道完整的路径或路径包括通配符时;

3)“属性”(Secondary)次要XML索引:知道路径时检索属性的值。

下面是一个创建次要XML索引的示例:

请注意,上面讲的原则是基础,如果盲目地在表上创建索引,不一定会提升性能,因为有时在某些表的某些列上创建索引时,可能会致使插入和更新操作变慢,当这个表上有一个低选中性列时更是如此,同样,当表中的记录很少(如<500)时,如果在这样的表上创建索引反倒会使数据检索性能降低,因为对于小表而言,全表扫描反而会更快,因此在创建索引时应放聪明一点。

第七步:应用反范式化,使用历史表和预计算列

反范式化

如果你正在为一个OLTA(在线事务分析)系统设计数据库,主要指为只读查询优化过的数据仓库,你可以(和应该)在你的数据库中应用反范式化和索引,也就是说,某些数据可以跨多个表存储,但报告和数据分析查询在这种数据库上可能会更快。

但如果你正在为一个OLTP(联机事务处理)系统设计数据库,这样的数据库主要执行数据更新操作(包括插入/更新/删除),我建议你至少实施第一、二、三范式,这样数据冗余可以降到最低,数据存储也可以达到最小化,可管理性也会好一点。

无论我们在OLTP系统上是否应用范式,在数据库上总有大量的读操作(即select查询),当应用了所有优化技术后,如果发现数据检索操作仍然效率低下,此时,你可能需要考虑应用反范式设计了,但问题是如何应用反范式化,以及为什么应用反范式化会提升性能让我们来看一个简单的例子,答案就在例子中。

假设我们有两个表OrderDetails(ID,ProductID,OrderQty)和Products(ID,ProductName)分别存储订单详细信息和产品信息,现在要查询某个客户订购的产品名称和它们的数量,查询SQL语句如下:

如果这两个都是大表,当你应用了所有优化技巧后,查询速度仍然很慢,这时可以考虑以下反范式化设计:

1)在OrderDetails表上添加一列ProductName,并填充好数据;

2)重写上面的SQL语句

注意在OrderDetails表上应用了反范式化后,不再需要连接Products表,因此在执行SQL时,SQL引擎不会执行两个表的连接操作,查询速度当然会快一些。

为了提高select操作性能,我们不得不做出一些牺牲,需要在两个地方(OrderDetails和Products表)存储相同的数据(ProductName),当我们插入或更新Products表中的ProductName字段时,不得不同步更新OrderDetails表中的ProductName字段,此外,应用这种反范式化设计时会增加存储资源消耗。

因此在实施反范式化设计时,我们必须在数据冗余和查询操作性能之间进行权衡,同时在应用反范式化后,我们不得不重构某些插入和更新操作代码。有一个重要的原则需要遵守,那就是只有当你应用了所有其它优化技术都还不能将性能提升到理想情况时才使用反范式化。同时还需注意不能使用太多的反范式化设计,那样会使原本清晰的表结构设计变得越来模糊。

历史表

如果你的应用程序中有定期运行的数据检索操作(如报表),如果涉及到大表的检索,可以考虑定期将事务型规范化表中的数据复制到反范式化的单一的历史表中,如利用数据库的Job来完成这个任务,并对这个历史表建立合适的索引,那么周期性执行的数据检索操作可以迁移到这个历史表上,对单个历史表的查询性能肯定比连接多个事务表的查询速度要快得多。

1)使用反范式化结构创建一个历史表,并对销售数据建立合适的索引;

2)在SQLServer上创建一个定期执行的操作,每隔24小时运行一次,在半夜往历史表中填充数据;

3)修改报表代码,从历史表获取数据。

创建定期执行的操作

按照下面的步骤在SQLServer中创建一个定期执行的操作,定期从事务表中提取数据填充到历史表中。

1)首先确保SQLServer代理服务处于运行状态;

2)在SQLServer配置管理器中展开SQLServer代理节点,在“作业”节点上创建一个新作业,在“常规”标签页中,输入作业名称和描述文字;

3)在“步骤”标签页中,点击“新建”按钮创建一个新的作业步骤,输入名字和TSQL代码,最后保存;

4)切换到“调度”标签页,点击“新建”按钮创建一个新调度计划;

5)最后保存调度计划。

在数据插入和更新中提前执行耗时的计算,简化查询

大多数情况下,你会看到你的应用程序是一个接一个地执行数据插入或更新操作,一次只涉及到一条记录,但数据检索操作可能同时涉及到多条记录。

如果你的查询中包括一个复杂的计算操作,毫无疑问这将导致整体的查询性能下降,你可以考虑下面的解决办法:

1)在表中创建额外的一列,包含计算的值;

2)为插入和更新事件创建一个触发器,使用相同的计算逻辑计算值,计算完成后更新到新建的列;

3)使用新创建的列替换查询中的计算逻辑。

实施完上述步骤后,插入和更新操作可能会更慢一点,因为每次插入和更新时触发器都会执行一下,但数据检索操作会比之前快得多,因为执行查询时,数据库引擎不会执行计算操作了。

第八步:使用SQL事件探查器和性能监控工具有效地诊断性能问题

在SQLServer应用领域SQL事件探查器可能是最著名的性能故障排除工具,大多数情况下,当得到一个性能问题报告后,一般首先启动它进行诊断。

你可能已经知道,SQL事件探查器是一个跟踪和监控SQLServer实例的图形化工具,主要用于分析和衡量在数据库服务器上执行的TSQL性能,你可以捕捉服务器实例上的每个事件,将其保存到文件或表中供以后分析。例如,如果生产数据库速度很慢,你可以使用SQL事件探查器查看哪些存储过程执行时耗时过多。

SQL事件探查器的基本用法

你可能已经知道如何使用它,那么你可以跳过这一小节,但我还是要重复一下,也许有许多新手阅读本文。

1)启动SQL事件探查器,连接到目标数据库实例,创建一个新跟踪,指定一个跟踪模板(跟踪模板预置了一些事件和用于跟踪的列),如图1所示;

图1选择跟踪模板

2)作为可选的一步,你还可以选择特定事件和列

图2选择跟踪过程要捕捉的事件

3)另外你还可以点击“组织列”按钮,在弹出的窗口中指定列的显示顺序,点击“列过滤器”按钮,在弹出的窗口中设置过滤器,例如,通过设置数据库的名称(在like文本框中),只跟踪特定的数据库,如果不设置过滤器,SQL事件探查器会捕捉所有的事件,跟踪的信息会非常多,要找出有用的关键信息就如大海捞针。

图3过滤器设置

4)运行事件探查器,等待捕捉事件

图4运行事件探查器

5)跟踪了足够的信息后,停掉事件探查器,将跟踪信息保存到一个文件中,或者保存到一个数据表中,如果保存到表中,需要指定表名,SQLServer会自动创建表中的字段。

图5将探查器跟踪数据保存到表中

6)执行下面的SQL查询语句找出执行代价较高的TSQL

图6查找成本最高的TSQL/存储过程

SQL事件探查器除了可以用于找出执行成本最高的那些TSQL或存储过程外,还可以利用它许多强大的功能诊断和解决其它不同类型的问题。当你收到一个性能问题报告后,或者想提前诊断潜在的性能问题时都可以使用SQL事件探查器。下面是一些SQL事件探查器使用技巧,或许对你有帮助。

1)使用现有的模板,但需要时应创建你自己的模板

大多数时候现有的模板能够满足你的需求,但当诊断一个特殊类型的数据库性能问题时(如数据库发生死锁),你可能需要创建自己的模板,在这种情况下,你可以点击“文件”*“模板”*“新建模板”创建一个新模板,需要指定模板名、事件和列。当然也可以从现有的模板修改而来。

图7创建一个新模板

图8为新模板指定事件和列

2)捕捉表扫描(TableScan)和死锁(DeadLock)事件

没错,你可以使用SQL事件探查器监听这两个有趣的事件。

先假设一种情况,假设你已经在你的测试库上创建了合适的索引,经过测试后,现在你已经将索引应用到生产服务器上了,但由于某些不明原因,生产数据库的性能一直没达到预期的那样好,你推测执行查询时发生了表扫描,你希望有一种方法能够检测出是否真的发生了表扫描。

这时你可以打开SQL事件探查器,修改一个现有模板,使其可以捕捉表扫描和死锁事件,修改好后,启动事件探查器,运行你的应用程序,当再次发生表扫描和死锁事件时,事件探查器就可以捕捉到,利用跟踪信息就可以找出执行代价最高的TSQL。

注意:从SQLServer日志文件中可能也可以找到死锁事件记录,在某些时候,你可能需要结合SQLServer日志和跟踪信息才能找出引起数据库死锁的数据库对象和TSQL。

图9检测表扫描

图10检测死锁

3)创建重放跟踪

某些时候,为了解决生产数据库的性能问题,你需要在测试服务器上模拟一个生产环境,这样可以重演性能问题。使用SQL事件探查器的TSQL_Replay模板捕捉生产库上的事件,并将跟踪信息保存为一个.trace文件,然后在测试服务器上播放跟踪文件就可以重现性能问题是如何出现的了。

图11创建重放跟踪

4)创建优化跟踪

数据库调优顾问是一个伟大的工具,它可以给你提供很好的调优建议,但要真正从它那获得有用的建议,你需要模拟出与生产库一样的负载,也就是说,你需要在测试服务器上执行相同的TSQL,打开相同数量的并发连接,然后运行调优顾问。SQL事件探查器的Tuning模板可以捕捉到这类事件和列,使用Tuning模板运行事件探查器,捕捉跟踪信息并保存,通过调优顾问使用跟踪文件在测试服务器上创建相同的负载。

图12创建Tuning事件探查器跟踪

5)捕捉ShowPlan在事件探查器中包括SQL执行计划

有时相同的查询在测试服务器和生产服务器上的性能完全不一样,假设你遇到这种问题,你应该仔细查看一下生产数据库上TSQL的执行计划。但问题是现在不能在生产库上执行这个TSQL,因为它已经有严重的性能问题。这时SQL事件探查器可以派上用场,在跟踪属性中选中ShowPlan或ShowPlanXML,这样可以捕捉到SQL执行计划和TSQL文本,然后在测试服务器上执行相同的TSQL,并比较两者的执行计划。

图13指定捕捉执行计划

图14在事件探查器跟踪中的执行计划

使用性能监视工具(PerfMon)诊断性能问题

当你的数据库遇到性能问题时,大多数时候使用SQL事件探查器就能够诊断和找出引起性能问题的背后原因了,但有时SQL事件探查器并不是万能的。

使用性能监视工具可以创建多个不同的性能计数器,通过图形界面分析计数器日志,此外还可以将性能计数器日志和SQL事件探查器跟踪信息结合起来分析。

性能监视器基本用法介绍

Windows内置了许多性能监视计数器,安装SQLServer时会添加一个SQLServer性能计数器,下面是创建一个性能计数器日志的过程。

1)在SQL事件探查器中启动性能监视工具(“工具”*“性能监视器”);

图15启动性能监视工具

2)点击“计数器日志”*“新建日志设置”创建一个新的性能计数器日志

图16创建一个性能计数器日志

指定日志文件名,点击“确定”。

图17为性能计数器日志指定名字

3)点击“添加计数器”按钮,选择一个需要的计数器

图18为性能计数器日志指定计数器

4)从列表中选择要监视的对象和对应的计数器,点击“关闭”

图19指定对象和对应的计数器

5)选择的计数器应显示在窗体中

图20指定计数器

6)点击“日志文件”标签,再点击“配置”按钮,指定日志文件保存位置,如果需要现在还可以修改日志文件名

图21指定性能计数器日志文件保存位置

9)点击“确定”,选择刚刚创建的计数器日志,点击右键启动它。

图24启动性能计数器日志

10)为了查看日志数据,再次打开性能监视工具,点击查看日志图标(红色),在“源”标签上选中“日志文件”单选按钮,点击“添加”按钮添加一个日志文件。

图25查看性能计数器日志

11)默认情况下,在日志输出中只有三个计数器被选中,点击“数据”标签可以追加其它计数器。

图26查看日志数据时追加计数器

12)点击“确定”,返回图形化的性能计数器日志输出界面

图27查看性能计数器日志

关联性能计数器日志和SQL事件探查器跟踪信息进行深入的分析

将二者结合起来分析可以更容易找出性能问题的根本原因,例如,你可能发现在生产服务器上每次查询都需要10秒,CPU利用率达到了100%,这时就应该放下SQL调优,先调查一下为什么CPU利用率会上升到100%。

关联SQL事件探查器跟踪信息和性能计数器日志的步骤如下:

1)创建性能计数器日志,包括下列常见的性能计数器,指定“手动”方式启动和停止计数器日志:

--网络接口\输出队列长度

--SQLServer:缓冲管理器\缓冲区缓存命中率

--SQLServer:缓冲管理器\页面生命周期

--SQLServer:SQL统计\批量请求数/秒

--SQLServer:SQL统计\SQL编译

--SQLServer:SQL统计\SQL重新编译/秒

创建好性能计数器日志,但不启动它。

3)跟踪到足够信息后,同时停掉SQL事件探查器跟踪和性能计数器日志,将SQL事件探查器跟踪信息保存为一个.trc文件;

4)关闭SQL事件探查器跟踪窗口,再使用事件探查器打开.trc文件,点击“文件”*“导入性能数据”关联性能计数器日志,此时会打开一个文件浏览器窗口,选择刚刚保存的性能计数器日志文件进行关联;

5)在打开的窗口中选择所有计数器,点击“确定”,你将会看到下图所示的界面,它同时显示SQL事件探查器的跟踪信息和性能计数器日志;

图28关联SQL事件探查器和性能监视工具输出

我相信你学会如何关联这两个工具的输出数据后,一定会觉得非常方便和有趣。

诊断SQLServer性能问题的工具和技术有很多,例如查看SQLServer日志文件,利用调优顾问(DTA)获得调优建议,无论使用哪种工具,你都需要深入了解内部的细节原因,只有找出最根本的原因之后,解决性能问题才会得心应手。

本系列最后一篇将介绍如何优化数据文件和应用分区。

优化技巧主要是面向DBA的,但我认为即使是开发人员也应该掌握这些技巧,因为不是每个开发团队都配有专门的DBA的。

第九步:合理组织数据库文件组和文件

创建SQLServer数据库时,数据库服务器会自动在文件系统上创建一系列的文件,之后创建的每一个数据库对象实际上都是存储在这些文件中的。SQLServer有下面三种文件:

1).mdf文件

这是最主要的数据文件,每个数据库只能有一个主数据文件,所有系统对象都存储在主数据文件中,如果不创建次要数据文件,所有用户对象(用户创建的数据库对象)也都存储在主数据文件中。

2).ndf文件

这些都是次要数据文件,它们是可选的,它们存储的都是用户创建的对象。

3).ldf文件

这些是事务日志文件,数量从一到几个不等,它里面存储的是事务日志。

默认情况下,创建SQLServer数据库时会自动创建主数据文件和事务日志文件,当然也可以修改这两个文件的属性,如保存路径。

文件组

为了便于管理和获得更好的性能,数据文件通常都进行了合理的分组,创建一个新的SQLServer数据库时,会自动创建主文件组,主数据文件就包含在主文件组中,主文件组也被设为默认组,因此所有新创建的用户对象都自动存储在主文件组中(具体说就是存储在主数据文件中)。

如果你想将你的用户对象(表、视图、存储过程和函数等)存储在次要数据文件中,那需要:

1)创建一个新的文件组,并将其设为默认文件组;

2)创建一个新的数据文件(.ndf),将其归于第一步创建的新文件组中。

以后创建的对象就会全部存储在次要文件组中了。

注意:事务日志文件不属于任何文件组。

文件/文件组组织最佳实践

如果你的数据库不大,那么默认的文件/文件组应该就能满足你的需要,但如果你的数据库变得很大时(假设有1000MB),你可以(应该)对文件/文件组进行调整以获得更好的性能,调整文件/文件组的最佳实践内容如下:

1)主文件组必须完全独立,它里面应该只存储系统对象,所有的用户对象都不应该放在主文件组中。主文件组也不应该设为默认组,将系统对象和用户对象分开可以获得更好的性能;

3)将访问频繁的表及其索引放到一个单独的文件组中,这样读取表数据和索引都会更快;

4)将访问频繁的包含Text和Image数据类型的列的表放到一个单独的文件组中,最好将其中的Text和Image列数据放在一个独立的硬盘中,这样检索该表的非Text和Image列时速度就不会受Text和Image列的影响;

5)将事务日志文件放在一个独立的硬盘上,千万不要和数据文件共用一块硬盘,日志操作属于写密集型操作,因此保证日志写入具有良好的I/O性能非常重要;

6)将“只读”表单独放到一个独立的文件组中,同样,将“只写”表单独放到一个文件组中,这样只读表的检索速度会更快,只写表的更新速度也会更快;

第十步:在大表上应用分区

什么是表分区

表分区就是将大表拆分成多个小表,以免检索数据时扫描的数据太多,这个思想参考了“分而治之”的理论。

当你的数据库中有一个大表(假设有上百万行记录),如果其它优化技巧都用上了,但查询速度仍然非常慢时,你就应该考虑对这个表进行分区了。首先来看一下分区的类型:

水平分区:假设有一个表包括千万行记录,为了便于理解,假设表有一个自动增长的主键字段(如id),我们可以将表拆分成10个独立的分区表,每个分区包含100万行记录,分区就要依据id字段的值实施,即第一个分区包含id值从1-1000000的记录,第二个分区包含1000001-2000000的记录,以此类推。这种以水平方向分割表的方式就叫做水平分区。

垂直分区:假设有一个表的列数和行数都非常多,其中某些列被经常访问,其余的列不是经常访问。由于表非常大,所有检索操作都很慢,因此需要基于频繁访问的列进行分区,这样我们可以将这个大表拆分成多个小表,每个小表由大表的一部分列组成,这种垂直拆分表的方法就叫做垂直分区。

另一个垂直分区的原则是按有索引的列无索引列进行拆分,但这种分区法需要小心,因为如果任何查询都涉及到检索这两个分区,SQL引擎不得不连接这两个分区,那样的话性能反而会低。

本文主要对水平分区做一介绍。

分区最佳实践

1)将大表分区后,将每个分区放在一个独立的文件中,并将这个文件存放在独立的硬盘上,这样数据库引擎可以同时并行检索多块硬盘上的不同数据文件,提高并发读写速度;

2)对于历史数据,可以考虑基于历史数据的“年龄”进行分区,例如,假设表中存储的是订单数据,可以使用订单日期列作为分区的依据,如将每年的订单数据做成一个分区。

如何分区

假设Order表中包含了四年(1999-2002)的订单数据,有上百万的记录,那如果要对这个表进行分区,采取的步骤如下:

1)添加文件组

使用下面的命令创建一个文件组:

ALTERDATABASEOrderDBADDFILEGROUP[1999]

ALTERDATABASEOrderDBADDFILE(NAME=N'1999',FILENAME

=N'C:\OrderDB\1999.ndf',SIZE=5MB,MAXSIZE=100MB,FILEGROWTH=5MB)TO

FILEGROUP[1999]

通过上面的语句我们添加了一个文件组1999,然后增加了一个次要数据文件“C:\OrderDB\1999.ndf”到这个文件组中。

使用上面的命令再创建三个文件组2000,2001和2002,每个文件组存储一年的销售数据。

2)创建分区函数

分区函数是定义分界点的一个对象,使用下面的命令创建分区函数:

CREATEPARTITIONFUNCTIONFNOrderDateRange(DateTime)AS

RANGELEFTFORVALUES('19991231','20001231','20011231')

上面的分区函数指定:

DateTime<=1999/12/31的记录进入第一个分区;

DateTime>1999/12/31且<=2000/12/31的记录进入第二个分区;

DateTime>2000/12/31且<=2001/12/31的记录进入第三个分区;

DateTime>2001/12/31的记录进入第四个分区。

RANGELEFT指定应该进入左边分区的边界值,例如小于或等于1999/12/31的值都应该进入第一个分区,下一个值就应该进入第二个分区了。如果使用RANGERIGHT,边界值以及大于边界值的值都应该进入右边的分区,因此在这个例子中,边界值2000/12/31就应该进入第二个分区,小于这个边界值的值就应该进入第一个分区。

3)创建分区方案

通过分区方案在表/索引的分区和存储它们的文件组之间建立映射关系。创建分区方案的命令如下:

CREATEPARTITIONSCHEMEOrderDatePSchemeASPARTITIONFNOrderDateRange

TO([1999],[2000],[2001],[2002])

在上面的命令中,我们指定了:

第一个分区应该进入1999文件组;

第二个分区就进入2000文件组;

第三个分区进入2001文件组;

第四个分区进入2002文件组。

4)在表上应用分区

至此,我们定义了必要的分区原则,现在需要做的就是给表分区了。首先使用DROPINDEX命令删除表上现有的聚集索引,通常主键上有聚集索引,如果是删除主键上的索引,还可以通过DROPCONSTRAINT删除主键来间接删除主键上的索引,如下面的命令删除PK_Orders主键:

ALTERTABLEOrdersDROPCONSTRAINTPK_Orders;

在分区方案上重新创建聚集索引,命令如下:

CREATEUNIQUECLUSTEREDINDEXPK_OrdersONOrders(OrderDate)ON

OrderDatePScheme(OrderDate)

第十一步:使用TSQL模板更好地管理DBMS对象(额外的一步)

为了帮助大家更好地管理DBMS对象,我创建了一些TSQL模板,利用这些模板你可以快速地开发出结构一致的DBMS对象。

如果你的团队有人专门负责检查团队成员编写的TSQL代码,在这些模板中专门有一个“审查”段落用来描写审查意见。

我提交几个常见的DBMS对象模板,它们是:

1)如何创建模板

首先下载前面给出的模板代码,然打开SQLServer管理控制台,点击“查看”*“模板浏览器”;

点击“存储过程”节点,点击右键,在弹出的菜单中选择“新建”*“模板”,为模板取一个易懂的名字;

在新创建的模板上点击右键,选择“编辑”,在弹出的窗口中输入身份验证信息,点击“连接”;

连接成功后,在编辑器中打开下载的Template_StoredProcedure.txt,拷贝文件中的内容粘贴到新建的模板中,然后点击“保存”。

上面是创建一个存储过程模板的过程,创建其它DBMS对象过程类似。

2)如何使用模板

创建好模板后,下面就演示如何使用模板了。

首先在模板浏览器中,双击刚刚创建的存储过程模板,弹出身份验证对话框,输入对应的身份信息,点击“连接”;

连接成功后,模板将会在编辑器中打开,变量将会赋上适当的值;

按Ctrl+Shift+M为模板指定值,如下图所示;

图1为模板参数指定值

点击“OK”,然后在SQLServer管理控制台中选择目标数据库,然后点击“执行”按钮;

如果一切顺利,存储过程就创建成功了。你可以根据上面的步骤创建其它DBMS对象。

(注:完全是摘抄的,在自己这里备个份而已)

下面地址里边还有很多有营养的东西!

---------------------------------------------------------------------------

THE END
1.在小区丢了东西,物业不让查看监控怎么办?中已经对业主、业主委员会及物业管理公司之间的权利和义务作出了明确规定,业主和物业管理公司可以依据有https://www.lawtime.cn/wenda/q_48401191.html
2.2024内地游客进入香港不能带什么?容易带错的物品大盘点+常见罚款在香港的游泳池、泳滩、图书馆、博物馆等港府康乐及文化事务署的场地内使用了淫亵性言语以致令人烦扰,据香港法规,均属违法,一经定罪,最高可被判罚款2000港元及监禁10天。 被罚款的公众场所涵盖地区:港铁、机场、海洋公园、迪士尼、昂坪360等10多个内地游客常去的地方。 https://www.extrabux.cn/chs/guide/7982562
3.www.xcmoqie.com/aplmine12报道称,事实上,除非出现什么大问题,否则这是美国将不得不执行的唯一核任务。然而,如果威慑失败,美国不得不实施核打击,几乎可以肯定,它不会依赖轰炸机群这样做。在美国的“三位一体”核力量中,洲际弹道导弹最有可能派上用场,因为它们不仅具有准确性,而且还能穿透敌人的防御系统。(参考消息网 编译/金进龙)||。 http://www.xcmoqie.com/aplmine12_17/706572
4.常见问题11、如何获得我馆无馆藏的国内外期刊或图书原(全)文资料? 我馆没有馆藏的文献,可与天津大学、南开大学及北京大学、国家图书馆等馆际协作通过文献传递(馆际互借)方式获取,当您在本馆的馆藏和电子资源中查到相关文献提要、文摘、索引又无法获取全文时,可以与图书馆信息部联系申请原文传递服务,具体见图书馆网站主页https://lib.tute.edu.cn/rgzn/cjwt.htm
5.汇报管理工作总结(15篇)校园布局合理、环境幽雅、办学设施设备完善,图书馆、科学实验室、计算机网络教室、音乐教室、舞蹈教室、美术教室、劳技教室等近20个按照省i类标准建设的现代化专用场室一应俱全,同时建有200米环形跑道和近1200平方米的风雨操场。目前学校拥有教学班13个,在校学生490人,专任教师31人。学校建成了千兆校园网络,做到了室https://www.ruiwen.com/gongzuozongjiehuibao/6423466.html
6.www.vita361.com/mokslip72666.html奥依克斯大学位于奥克兰体育馆附近的一片办公园区的一栋建筑内,是一间韩裔牧师创办的基督教职业大学,学生以韩裔和亚裔为主,教授护士、东方医学、科技和音乐等课程。警察局长乔丹说,他怀疑43岁的吴丸在学校里因为英语不好而遭到过许多冷嘲热讽。而这些,很有可能在吴丸心中累积成了强烈的怨恨情绪。||。 https://www.vita361.com/mokslip72666.html
7.www.jxmzxx.com{$woaini}>www.jxmzxx.com{$woaini}同时,推进中医药服务体系建设,全省基本实现了两个全覆盖,即每个市、农业县都至少有一所公立中医医院,每个乡镇卫生院、社区卫生服务中心都建有中医馆,做到“公共卫生服务延伸到哪里,中医药服务就跟进到哪里。” 推进中医药教育科技人才方面,山西支持山西中医药大学增列博士学位授予单位,建设4个高水平中医药重点学科;聚焦http://www.jxmzxx.com/appnews_888544.html
8.www.0769sz.com/mokaka23122.html民进党高雄市议会党团总召集人洪平朗也表示,未来两岸政策可以更务实,如果再不检讨,民进党“不会长大”。反对的声音也不少。民进党籍前“驻美代表”吴钊燮18日表示,民进党败选后对“与中国交流”的讨论,是“被设计”进入“不对的议程”。“新台湾国策智库”研究员刘世忠表示,民进党对于“中国政策”的讨论“严重http://www.0769sz.com/mokaka23122.html
9.www.chxinze.com/go78451857.shtml曰本一区二区 5天前 图书馆的她漂亮的她直到论淊ova 男女扒开双腿无遮挡猛进免费网站 4天前 欧美黑人内射夫妻大片国产 西西里人体Xxx艺术照片 2天前 国产婷婷色综合AV蜜臀丁香 18禁网站免费观看 0天前 欧美性乂乂乂性 娇妻的呻吟声 0天前 五月婷婷麻 久久官方福利导航 4天前 亚洲成人理论 宝宝张开http://www.chxinze.com/go78451857.shtml
10.安全操作规程口应设在顺行车方向的下游过度区内。对于同一方向上的相 同车道内,如果在不同断面要同时进行维修施工,若断面的 间距比较近,一般在 1000m 以内,可以作为同一个作业控 制区来布置;若维修施工的断面间距比较远,大于 1000m, -4- 这时应在下一个工作区前端设置施工标志。由于在同一个车 http://www.360doc.com/document/23/0829/02/82954396_1094272714.shtml
11.www.whfm88.com/mokaka26871.html面对海军上将和邓肯的钢铁内线组合,姚明所向披靡,18投10中砍下27分18篮板3助攻3盖帽带队赢了14分,面对历史上低位防守最好的几个人之一,姚明展现了他独特的低位技术,姚明低位翻身跳投可以从负角度出手,海军上将只能望球兴叹,而邓肯同样干扰不了,姚明的出手点还是太高,防守端姚明连续两次封盖当时的MVP邓肯则让全场彻https://www.whfm88.com/mokaka26871.html
12.www.maxcent.cn/mmmj19751037图书馆女友未增删带翻译樱 麻花传媒有限公司国产高清18禁 74.84MB 52好评 摸胸抠逼内射白虎 原神黄色在线成人观看 要看外国黄色操逼网站 84.65MB 5797好评 c到流水的黄文 哈啊好大好硬流水了视频 最新黄色网站网址视频 711.31MB 53好评 熟女骑头喂尿喝尿 公妇乱操古片杨贵妃 .sm调考教肛交http://www.maxcent.cn/mmmj19751037
13.邵可浪(清风)的美篇《打油诗34首》作者:张强民(期盼)前半生耕耘土地,曾担任农村基层干部几十年,兢兢业业于集体事业,勤勤恳恳服务于群众,个人威信及口碑极好。花甲之年随儿子移居https://www.meipian.cn/1xsrr5ho
14.图书馆视频监控系统技术要求5篇.docx图书馆视频监控系统技术要求[5篇]第一篇:图书馆视频监控系统技术要求图书馆视频监控系统4.1总体目标图书馆项目监控系统采用纯数字系统,视频监控系统由前端摄像机信号采集、信号传输、后端信号显示、控制及存储三部分组成,系统将采用全数字的信号采集、显示和存储方式进行设计,图书馆控制主机等设备设于图书馆一层消防安控https://www.renrendoc.com/paper/303449367.html
15.火灾事故的预防措施大家都知道学生宿舍里面都是被子和衣物之类的东西,这些东西都是十分易燃的产品,所以当学生宿舍内出现易燃品事故的时候往往会引发火灾。 2、不在教室、宿舍以及公共场所吸烟,不乱丢烟头、火种: 现在有很多火灾的'发生都是在星星之火燎原的情况下发生的,其中烟头是罪魁祸首,所以在学校中不要随意的丢弃烟头等火种,预防https://www.oh100.com/a/201803/1196738.html
16.大学毕业实习报告2.实习路线及内容:20xx年4月5日,早八点,在北大逸夫二楼前集合出发,经图书馆至燕南园,停留进行观察调查;经静园至翻尾石鱼;从北大西门出至蔚秀园及承泽园,解散进行调查;在海淀公园调查后,经六郎庄、北京城市绿化隔离带、京密引水渠,从南如意门进入颐和园,沿昆明湖南岸、颐和园北岸、万寿山北坡一路观察,听取老师https://www.pinda.com/zhichang/shixibaogao/29737.html
17.原州四中“高效课堂”实施方案固原市第四中学但遗憾的是,时下许多学校的文化恰恰是这样的,我们有那么多漂亮的楼房、草坪、树木、假山、塑胶跑道、图书馆,可体现出来的不是儿童的意志,而是校长的需要。这样设计出来的文化,没能起到“化”的作用,即便在“化”,“化”的当然也不是学生,这就是“专制”下文化极其尴尬的呈现方式——脱离了儿童。一旦文化和儿童https://yun.nxeduyun.com/index.php?r=space/school/theme/content/view&id=685605
18.图书馆恒温恒湿智能监控系统,增强环境的舒适度动环监控系统图书馆作为常识的海洋,不仅是人们获取信息的宝库,也是传承人类文明的摇篮。然而,图书馆的恒温恒湿环境对书本的保存与读者的阅览体会至关重要。本文将介绍一种图书馆恒温恒湿智能监控系统,旨在主动监控环境温度和湿度,为读者和书本供给最佳的保存与阅览环境。 https://www.szzht.com/articles/tsghwh.html
19.MySQL——索引及调优篇类似大学图书馆建立书目索引,提高数据的检索效率,降低数据库的IO成本,这也是创建索引最主要的原因。 通过创建唯一索引,可以保证数据库表中每一行数据的唯一性。 在实现数据的参考完整性方面,可以加速表与表之间的连接,换句话说,对于有依赖关系的子表和父表联合查询时,可以提高查询速度。 https://blog.csdn.net/weixin_68570147/article/details/127177880
20.大学生计算机应用技术职业生涯规划书(通用18篇)幸亏大学的空余时间不少,睡觉的时间游玩的时间和上网看电影的时间少一点,学习时间多一点,踏踏实实,认准方向一步一步汲取自己需要的营养。努力复习的同时充分利用网络资源图书馆资源还有人力资源,我想绝对没有过不去的关。 八、结束语: 现在正是经济危机时期,风云变幻,一日千里。职业生涯规划从某个角度上来说也是个https://www.yjbys.com/qiuzhizhinan/show-596683.html