存档

‘SQL SERVER’ 分类的存档,文章数:35

本人的pc机上装的数据库比较多。有:oracle 10g,MySQL5.0.45,Sybase ASE15.0.3,Sybase IQ15.1,MSSQLSERVER2005,MSSQLSERVER2000,Sybase Anywhere8,Sybase Anywhere9,Sybase Anywhere10等等。还有非安装版的Sybase ASE12.5.x,ASA5,ASA6等。

数据库比较多,都通过系统工具中的服务项来启动不太现实。有时用到的时候直接用命令行启动,这样挺方便。注册表信息也省得维护了。

最近,在用SQLSERVER2005 management studio读取表数据的时候,报了下面的两个错误。

在对象资源管理器中找到sqlserver2000数据库中的用户表,点右键“打开表”报错如下:

标题: Microsoft SQL Server Management Studio
------------------------------
调用的目标发生了异常。 (SQLEditors)
------------------------------
其他信息:
找不到请求的 .Net Framework 数据提供程序。可能没有安装。 (System.Data)
 

在对象资源管理器中找到sqlserver2005中某个用户数据库中的用户表,点右键“打开表”报错如下:

标题: Microsoft SQL Server Management Studio
------------------------------
调用的目标发生了异常。 (SQLEditors)
------------------------------
其他信息:
创建 system.data 的配置节处理程序时出错: 列“InvariantName”被约束为是唯一的。值“iAnywhere.Data.SQLAnywhere”已存在。 (C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Config\machine.config line 126) (System.Configuration)
------------------------------
列“InvariantName”被约束为是唯一的。值“iAnywhere.Data.SQLAnywhere”已存在。 (System.Data)
 

问题怎么解决呢?

在网上搜索了很多,有说和MySQL有关的。的确我的pc上有MySQL5.0.45。但是将MySQL的相关项目去掉还是不管用。这些链接如下,

大家可以参考下:

找不到请求的 .Net Framework 数据提供程序。可能没有安装。

MySql “找不到请求的 .Net Framework 数据提供程序。可能没有安装。”解决方法

ASP.NET 2.0中如何连接到Mysql

根据访问sqlserver2005数据库时的报错,可能和.NET FRAMEWORK的配置文件有关系。查看C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG 下的machine.config文件。发现的DbProviderFactories节下的最后四个项目为:

  <add name="SQL Anywhere 10 Data Provider" invariant="iAnywhere.Data.SQLAnywhere" description=".Net Framework Data Provider for SQL Anywhere 10" type="iAnywhere.Data.SQLAnywhere.SAFactory, iAnywhere.Data.SQLAnywhere,  Version=10.0.1.34152, Culture=neutral, PublicKeyToken=f222fc4333e0d400" />
     
     <add name="SQL Anywhere UltraLite.NET 10 Data Provider" invariant="iAnywhere.Data.UltraLite" description=".Net Framework Data Provider for SQL Anywhere UltraLite.NET 10" type="iAnywhere.Data.UltraLite.ULFactory, iAnywhere.Data.UltraLite,  Version=10.0.1.3415, Culture=neutral, PublicKeyToken=ff11483eb5a8c1a5" />
     
     <add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL" type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=5.0.2.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" /> 
     
     <add name="SQL Anywhere 11 Data Provider" invariant="iAnywhere.Data.SQLAnywhere" description=".Net Framework Data Provider for SQL Anywhere 11" type="iAnywhere.Data.SQLAnywhere.SAFactory, iAnywhere.Data.SQLAnywhere, Version=2, Culture=neutral, PublicKeyToken=f222fc4333e0d400" />

根据列"InvariantName"被约束为是唯一的。值"iAnywhere.Data.SQLAnywhere"已存在。观察到SQL Anywhere 10 Data Provider 和 SQL Anywhere 11 Data Provider 这两个项目下的 invariant 都是 "iAnywhere.Data.SQLAnywhere",尝试着将"SQL Anywhere 11 Data Provider"项目删除 ,然后重新打开management studio,问题解决。

总结一下:问题不在于微软的.NET FRAMEWORK3.5的安装时间先后的问题, 是Sybase公司的Anywhere起的驱动程序名称的问题。iAnywhere.Data.UltraLite 就没问题。 为什么SQL Anywhere 10和SQL Anywhere 11 都对应一样相同的Invariant名称呢?

如果ASA10和ASA11都装在一台机器上,就肯定会出现这个问题。 不敢把SQL Anywhere 11 Data Provider对象的 invariant改为:iAnywhere.Data.SQLAnywhere11。反正我也不做数据库开发,我只关系数据库管理方面的。 二者取其一,将SQL Anywhere 11 Data Provider项目暂时注释掉,

 <!-- <add name="SQL Anywhere 11 Data Provider" invariant="iAnywhere.Data.SQLAnywhere" description=".Net Framework Data Provider for SQL Anywhere 11" type="iAnywhere.Data.SQLAnywhere.SAFactory, iAnywhere.Data.SQLAnywhere, Version=2, Culture=neutral, PublicKeyToken=f222fc4333e0d400" />  -->

保存machine.config,重新启动management studio。 问题解决。 o(≧v≦)o~~

 

————————————————————————————————-
—- 本文为andkylee个人原创,请在尊重作者劳动成果的前提下进行转载;
—- 转载务必注明原始出处 : http://www.dbainfo.net
—- 关键字:ASA 10 11 SQLSERVER management studio  DbProvider InvariantName
————————————————————————————————-

今天在sqlcentral群里面有人讨论主键和索引的区别,特地搜索了一下。找到一篇,特作收藏。

http://www.cnblogs.com/chinhr/archive/2007/09/30/911792.html

其实指定列的索引就相当于对指定的列进行排序,为什么要排序呢?因为 排序有利于对该列的查询,可以大大增加查询效率。(那么可能有人认为应该对所有的列排序,这样就可以增加整个数据库的查询效率?这样的想法是错误的,原因 是建立索引也是要消耗系统资源的,给每个表里的每个列都建立索引那么将对系统造成极大的负担,那就更别提效率了!)---------简单的说建立一个列 的索引,就相当与建立一个列的排序。

    主键其实就是一个索引,但是这个索引跟一般的索引有所不同,不 同在于主键所在的列里的每一个的记录都是唯一的,也可以说不能在主键里出现相同的记录,在同一个表里只能有一个主键。(主键等于索引,索引不一定等于主 键)----------简单的说主键就是所在列不能出现相同记录的特殊索引,而且这个索引只能在表里出现一次。


    1.主键一定是唯一性索引,唯一性索引并不一定就是主键;
    2.一个表中可以有多个唯一性索引,但只能有一个主键;
    3.主键列不允许空值,而唯一性索引列允许空值。

在csdn论坛上看到有人问,如何获取指定数据库所在磁盘的磁盘剩余空间。

http://topic.csdn.net/u/20100518/16/70cae8d3-a9de-4e45-9f49-fe8b00d8ba74.html?seed=1945596721&r=65529065#r_65529065

解决的思路是:通过sysfiles系统表找到数据库所属数据文件所在的物理路径,从数据文件的物理路径中提取所在磁盘盘符(第一个字母),最后用扩展存储过程xp_fixeddrives来获得磁盘驱动器的剩余空间(MB Bytes)。

SQL语句如下:

declare @drivename char(1)
select @drivename=left(filename,1) from sysfiles where fileid=  1
if not exists(select 1 from tempdb.dbo.sysobjects where name like '#FreeSpace%' and type='U')
  create table #FreeSpace(
    Drive char(1),
    MB_Free int
    )
else
      truncate table #FreeSpace
     
insert into #FreeSpace
exec xp_fixeddrives
select MB_Free from #FreeSpace where Drive = @drivename
go
 

我的测试环境是的结果如下:

1> declare @drivename char(1)
2> select @drivename=left(filename,1) from sysfiles where fileid=  1
3>
4> if not exists(select 1 from tempdb.dbo.sysobjects where name like '#FreeSpace
%' and type='U')
5>   create table #FreeSpace(
6>     Drive char(1),
7>     MB_Free int
8>     )
9> else
10>       truncate table #FreeSpace
11>
12> insert into #FreeSpace
13> exec xp_fixeddrives
14>
15> select MB_Free from #FreeSpace where Drive = @drivename
16> go

(3 行受影响)
MB_Free
-----------
      18905

(1 行受影响)
1>
2>
3> select filename from sysfiles
4> go
filename

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------
D:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\DATA\master.mdf

D:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\DATA\mastlog.ldf

(2 行受影响)
1> xp_fixeddrives
2> go
drive MB 可用空间
----- -----------
C           26562
D           18905
E           22517

(3 行受影响)

上面的sql语句和csdn上帖子的发帖人要求的有点差距,

第一:指定数据库,这点很容易。直接select filename from <your_db_name>.dbo.sysfiles即可。

第二:某个数据库的数据文件可能跨越两个及以上不同的物理磁盘;所以@drivename变量有可能得到不唯一的值。可以考虑用游标来实现。

————————————————————————————————-
—- 本文为andkylee个人原创,请在尊重作者劳动成果的前提下进行转载;
—- 转载务必注明原始出处 : http://www.dbainfo.net
—- 关键字:sqlcmd sqlserver2005 sysfiles xp_fixeddrives 磁盘剩余空间
————————————————————————————————-

关于SQL2005安装失败的一种可行性解决办法

1、在安装向导过程中,出现如下错误:

{英文原意如下
When you install Microsoft SQL Server 2005, you receive the following error message:
There was an unexpected failure during the setup wizard. You may review the setup logs and/or click the help button for more information.}

2、当我检查C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Summary.txt

Microsoft SQL Server 2005 9.00.1399.06
==============================
OS Version      : Microsoft Windows Server 2003 family, Enterprise Edition Service Pack 2 (Build 3790)
Time            : Wed Oct 10 08:34:17 2007

CSZ : 执行安装向导期间出错。有关详细信息,您可以查看安装日志和/或单击“帮助”按钮。
SQL Server 安装程序失败。有关详细信息,请查看 %ProgramFiles%\Microsoft SQL Server\90\Setup Bootstrap\LOG\Summary.txt 中的安装日志文件。
Time            : Wed Oct 10 08:42:32 2007
List of log files:
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_Core(Local).log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_Datastore.xml
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework 2.0.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_Core.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Summary.txt
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework 2.0 LangPack.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Upgrade Advisor.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Upgrade Advisor LangPack.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Windows Installer.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Windows Installer LangPack.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_Support.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_SCC.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_WI.log

3、当我检查到日志文件SQLSetup0001_CSZ_Core.log,发现在日志文件末尾有如下的错误提示:
{英文原意如下:
When you examine the SQLSetupNumber_ServerName_Core(local).log file at this point, you notice the following error message at the end of the log file: }

Running: InstallToolsAction.10 at: 2007/9/10 8:35:36
Error: Action "InstallToolsAction.10" threw an exception during execution.  Error information reported during run:
Target collection includes the local machine.
Fatal Exception caught while installing package: "10"
Error Code: 0x80070002 (2)
Windows Error Text: 系统找不到指定的文件。
Source File Name: sqlchaining\sqlprereqpackagemutator.cpp
Compiler Timestamp: Tue Aug  9 01:14:20 2005
Function Name: sqls::SqlPreReqPackageMutator::modifyRequest
Source Line Number: 196
---- Context -----------------------------------------------
sqls::InstallPackageAction::perform
WinException caught while installing package. : 1603
Error Code: 0x80070643 (1603)
Windows Error Text: 安装时发生严重错误
Source File Name: packageengine\installpackageaction.cpp
Compiler Timestamp: Fri Jul  1 01:28:25 2005
Function Name: sqls::InstallPackageAction::perform
Source Line Number: 167
---- Context -----------------------------------------------
sqls::InstallPackageAction::perform
Error: Failed to add file :"C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework 2.0.log" to cab file : "C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\SqlSetup0001.cab" Error Code : 2
Error: Failed to add file :"C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework 2.0 LangPack.log" to cab file : "C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\SqlSetup0001.cab" Error Code : 2
Error: Failed to add file :"C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Upgrade Advisor.log" to cab file : "C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\SqlSetup0001.cab" Error Code : 2
Error: Failed to add file :"C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Upgrade Advisor LangPack.log" to cab file : "C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\SqlSetup0001.cab" Error Code : 2
Error: Failed to add file :"C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Windows Installer.log" to cab file : "C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\SqlSetup0001.cab" Error Code : 2
Error: Failed to add file :"C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Windows Installer LangPack.log" to cab file : "C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\SqlSetup0001.cab" Error Code : 2
Running: UploadDrWatsonLogAction at: 2007/9/10 8:42:36
Message pump returning: 1603

4、问题解决
在d:盘根目录下建立sqlserver2005Setup目录,再在d:\sqlserver2005Setup\下创建2个目录
\Servers
\Tools
如 果是2个cd的安装程序,将cd1上的所有文件拷贝到Servers目录下,将cd2目录下的所有文件拷贝到Tools下;如果是一个dvd的安装程序, 则分别把dvd上的Servers和Tools目录下的所有文件分别拷贝到d:\sqlserver2005Setup\Servers和d: \sqlserver2005Setup\Tools
再进行安装应该就没有问题。

还有需要注意的问题
安装到上述错误的时候,SQL Server2005很多文件已经安装到硬盘上了,所有你重新安装的时候,可能会遇到
1、提示磁盘空间不够
目标磁盘中的空间不足,无法执行当前的 SQL Server 安装。若要继续,请释放磁盘空间以安装所选功能、为此次安装选择较少的功能或将所选功能安装到另一个驱动器中。

2、安装仍然失败
所以建议你重新安装之前,到添加删除程序里把SQL Server2005卸载掉!

上述问题是8月份我在一个客户那里搞了一个晚上都没有搞定,最后第二天早上在宾馆里上网到微软的网站上找到了答案,真是让人哭笑不得。

微软网站上标题为:
Error message when you install SQL Server 2005: "There was an unexpected failure during the setup wizard"

Article ID : 916760
Last Review : May 9, 2006
Revision : 2.0
Bug #: 408784 (SQLBUDT)

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

性能计数器问题,主要是卸载SQL2005,再重新安装的错误,这方面的文章在网上很多,网友可搜索一下。

关于sql.cab找不到网上这样的疑问同样很多,但是没有明确的解答。这里说说我是怎么解决的。

应该说这是MSDN  SQL2005安装程序的一个bug(不是十分确定),在SQL2005安装目录有两个子目录,一个是Servers目录, 一个是Tools目录,当初我只把Servers拷贝我硬盘上了,所以安装时如果选择了工作站组件、联机丛书和开发工具选项,安装到最后,就会提示你 sql.cab找不到(或其它安装不成功的提示信息),其实安装sql时先不要选该项,这样就可以安装成功,最后安装Tools的setup程序,它就是 仅安装该选项的安装包。

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

假如,以上这种办法还是出现SQL.CAB找不到类似的错误,那么你可以在上面那个办法的基础上,打开TOOLS文件夹(有的可能是CD2文件夹),找到TOOLS\Setup\SqlRun_Tools.msi并,运行,然后按照正常的安装流程来,这样应该可以解决了

题外话:我熬了好几个夜,把SQL2005装了删,删了装,终于装上了,真是辛苦啊~~。以上是我的安装解决办法的总结,有的是网站感觉比较有用的转载,有的是自己实际经历的过程的记录~~!

转自:http://www.nmju.net/article.asp?id=101

CSDN上有篇帖子http://topic.csdn.net/u/20100223/15/644e6212-9fdc-42de-81ad-785d28ed71d3.html 讨论查询计划读取索引页的问题。

主要问题是: 在一张建有聚集索引(没有其它非聚集索引)的表执行select count(*) 。查询显示扫描了所有的索引中间层叶。从理论上讲,由于每个数据页都记录着它的上一个page和下一个page,那么最小的io读取应该是:
读根页--->读最小的中间页--->读数据叶子---->依次往后读全部数据页.

但实际上却是读取全部的索引中间页。

帖子中比较精彩的回复:

1
2
3
4
5
首先楼主的说法不够准确,根据我的测试结果来看,更准确的说法应该是sql server在做索引全扫(INDEX FULL scan)时,会先扫描全部的level 1的节点,然后在扫描处于level 0的叶节点。也就是说一次索引全扫的顺序是(这个顺序对于clustered INDEX和普通INDEX都是一样的):
读根页--->读最小的中间页(level 1以上的层)--->依次读level 1的所有节点的索引页---->读全部数据页
上述顺序可以在将数据表索引层数增加到3层以上时得到验证。
实际上我觉得sql server这样做是很有道理的,楼主认为的是所读的页越少,io就越小,这是不对的。对于sql server来说,一次io并不一定只读取一个页,很多时候特别是做表全扫或索引全扫时,sql server都会尽量的让一次io读取尽可能多的数据页,这样才能较少io的次数。为达到这个目的,sql server首先就要知道它到底需要读取那些页,知道要读取页的分布之后,就可以安排io调度器尽可能的将临近的数据页用一次io读取上来,而要知道需要读取数据页的分布就需要先读取到level 1上的所有页,以找出要读取io页的分布情况,然后再做最优的io读取安排。
同样的道理,如果sql server是先定位到叶节点的起始数据页,然后从依次的读取所有的数据页的话,那sql server就只能一次io读取一个数据页,然后找出对应的下一页的指针,再去读取下一个数据页,这样实际上花费的io会远比批量读取数据页多很多的。这就好比我们在做表的lookup时,如果需要lookup的行非常之多的话sql server会转而选择表扫描来达到目的的。

预读

我觉得SQL Server内部存在这样一个机制:
它会尽量将当前使用的及使用最频繁的表的数据都带到buffer中来,这跟buffer的算法有一定的关系。
清掉buffer後,你不做任何动作,隔几秒钟,sql会自动将master库中一些比较重要的表都带到buffer中,或者
你select * from tb where 1 <>1 ,尽管该查询不会扫描任何数据,但sql还是会自动将tb的所有索引页到带到
buffer中去,因为它可能觉得tb这个表後续可能会使用到.

关于计算逻辑读的数量

IAM页是否会计算在逻辑读中我都是持怀疑态度的。
比如说,一个堆表扫描时肯定是从IAM页开始的,然而用set statistics io on看到的逻辑读数量却只是表所占用的总页数,并没有加上相应的IAM页数量。
而索引扫描却更有意思,就像楼主说的那样,会比我们计算的数据页加索引页的页数还要多1个,但这1个却不知是从何而来的。按照堆表扫描的计算方法,我认为更不可能是IAM页,再说索引扫描也不会用到IAM页。
一直没有看到有描述逻辑读怎么计算的资料。
在没有详细资料的情况下要研究的这么精确,不纠结才怪

实际上: 是sql server 内部引擎设计上的问题。sql server 利用预读机制来改善IO。通过读取所有的索引中间层叶sql server能够知道预读哪些页面,而不是利用数据页面上的前后链接指针那样一页一页的读。 sql server 最大一次IO可以读取64页。

以下是从msdn上摘下来的帮助文档。

SQL Server 2008 联机丛书(2009 年 7 月)

读取页

SQL Server 数据库引擎实例的 I/O 包括逻辑读取和物理读取。每次数据库引擎从缓冲区高速缓存 请求页时都会发生逻辑读取。如果页当前不在缓冲区高速缓存中,物理读取将首先将页从磁盘复制到缓存中。

数据库引擎实例生成的读取请求由关系引擎控制,并由存储引擎优化。关系引擎决定最有效的访问方法(例如,表扫描、索引扫描或键读取);存储引擎的访问方法和缓冲区管理器组件确定要执行的读取的常规模式,并对实现访问方法所需的读取进行优化。执行批处理的线程将安排读取。

预读

数据库引擎支持称为“预读”的性能优化机制。预读首先预测执行查询执行计划所需的数据和索引页,然后在查询实际使用这些页之前将它们读入缓冲区高速缓存。这样可以让计算和 I/O 重叠进行,从而充分利用 CPU 和磁盘。

预 读机制允许数据库引擎从一个文件中读取最多 64 个连续页 (512KB)。该读取作为缓冲区高速缓存中相应数量(可能是非相邻的)缓冲区的一次散播-聚集读取来执行。如果此范围内的任何页在缓冲区高速缓存中已存 在,当读取完成时,所读取的相应页将被放弃。如果相应页在缓存中已存在,也可以从任何一端“裁剪”页的范围。

有两种类型的预读:一种用于数据页,一种用于索引页。

读取数据页

用 于读取数据页的表扫描在数据库引擎中非常有效。SQL Server 数据库中的索引分配映射 (IAM) 页列出了表或索引使用的区。存储引擎可以读取 IAM 以生成必须读取的磁盘地址的排序列表。这使得存储引擎能够根据要读取的磁盘位置,将其 I/O 操作优化为按顺序执行的大型顺序读取。有关 IAM 页的详细信息,请参阅管理对象使用的空间

读取索引页

存储引擎按键的顺序依次读取索引页。例如,下图显示了一组叶级页的简化表示法,该组叶级页包含映射叶级页的键集和中间索引节点。有关索引中页的结构的详细信息,请参阅聚集索引结构

存 储引擎使用高于叶级的中间索引页上的信息为包含键的页安排序列预读。如果请求针对的是 ABC 到 DEF 之间的所有键,则存储引擎将首先读取高于叶级页的索引页,但它并不是仅仅按顺序读取页 504 到页 556(即指定范围内的包含键的最后一页)之间的每个数据页。相反,存储引擎将扫描中间索引页并生成必须要读取的叶级页的列表。然后,存储引擎会按键的顺 序安排所有读取。存储引擎还会识别出页 504/505 以及页 527/528 是相邻页,并执行一次散播读取,从而在单个操作中检索这些相邻页。如果在一个序列操作中要检索许多页,则存储引擎将一次安排一个读取块。完成这些读取子集 后,存储引擎将安排同等数量的新读取,直到安排完所需的全部读取。

存储引擎使用预提取加快非聚集索引的基表查找。 非聚集索引的叶级行包含指针,指向含有每个特定键值的数据行。存储引擎浏览非聚集索引的叶级页时,它也会开始计划异步读取已检索了其指针的数据行。这可以 使存储引擎在完成非聚集索引的扫描之前从基础表中检索数据行。无论表是否有聚集索引,都会使用预提取。SQL Server Enterprise 比 SQL Server 其他版本使用更多的预提取,可以预读更多页。在任何版本中都无法配置预提取的级别。有关非聚集索引的详细信息,请参阅非聚集索引结构

高级扫描

在 SQL Server Enterprise 中,高级扫描功能使得多项任务可以共享完全表扫描。如果 Transact-SQL 语句的执行计划需要扫描表中的数据页,并且数据库引擎检测到其他执行计划正在扫描该表,则数据库引擎会在第二个扫描的当前位置将第二个扫描加入第一个扫 描。数据库引擎会一次读取一页,并将每一页的行传递给这两个执行计划。此操作将一直持续到该表的结尾处。

此时,第一个执行 计划已有完整的扫描结果,而第二个执行计划仍必须检索在它加入正在进行的扫描之前读取的数据页。然后,第二个执行计划中的扫描将绕回到表的第一个数据页, 并从这里向前扫描到它加入第一个扫描时所处的位置。可以按这种方式组合任意数量的扫描。数据库引擎将循环遍历数据页,直到完成所有扫描。这种机制也称为 “走马灯式扫描”,说明了为何在没有 ORDER BY 子句的情况下无法保证 SELECT 语句所返回结果的顺序。

例 如,假设某个表有 500,000 页。UserA 执行了一条 Transact-SQL 语句,要求对该表进行扫描。当扫描已处理了 100,000 页时,UserB 执行了另一条 Transact-SQL 语句,要对同一个表进行扫描。数据库引擎将为页 100,001 之后的页安排一组读取请求,并将每页中的行同时传递回两个扫描。当扫描到页 200,000 时,UserC 执行了另一条 Transact-SQL 语句,要对同一个表进行扫描。则从页 200,001 开始,数据库引擎将把它读取的每一页中的行传递回所有三个扫描。当数据库引擎读取完第 500,000 行之后,UserA 的扫描就完成了,而 UserB 和 UserC 的扫描将绕回到页 1 开始读取。当数据库引擎到达页 100,000 时,UserB 的扫描就完成了。然后 UserC 的扫描将继续进行,直到它读取完页 200,000。此时,所有扫描便均已完成。

在没有高级扫描的情况下,每个用户都必须要争用缓冲区空间并因此导致磁盘臂争用。然后,会分别为每个用户读取一次相同的页,而不是一次读取并由多个用户共享,这样会降低性能并加重资源负担。


SQL Server Architecture (SQL Server 2000)
Reading Pages

The read requests generated by an instance of Microsoft® SQL Server™ 2000 are controlled by the relational engine and further optimized by the storage engine. The access method used to read pages from a table, such as a table scan, an index scan, or a keyed read, determines the general pattern of reads that will be performed. The relational engine determines the most effective access method. This request is then given to the storage engine, which optimizes the reads required to implement the access method. The thread executing the batch schedules the reads.

Table scans are extremely efficient in SQL Server 2000. The IAM pages in a SQL Server 2000 database list the extents used by a table or index. The storage engine can read the IAM to build a sorted list of the disk addresses that must be read. This allows SQL Server 2000 to optimize its I/Os as large sequential reads that are done in sequence based on their location on the disk. SQL Server 2000 issues multiple serial read-ahead reads at once for each file involved in the scan. This takes advantage of striped disk sets. SQL Server 2000 Enterprise Edition dynamically adjusts the maximum number of read ahead pages based on the amount of memory present; it is fixed in all other editions of SQL Server 2000.

One part of the SQL Server 2000 Enterprise Edition advanced scan feature allows multiple tasks to share full table scans. If the execution plan of a SQL statement calls for a scan of the data pages in a table, and the relational database engine detects that the table is already being scanned for another execution plan, the database engine joins the second scan to the first, at the current location of the second scan. The database engine reads each page once and passes the rows from each page to both execution plans. This continues until the end of the table is reached. At that point, the first execution plan has the complete results of a scan, but the second execution plan must still retrieve the data pages that occur before the point at which it joined the in-progress scan. The scan for second execution plan then wraps back to the first data page of the table and scans forward to the point at which it joined the first scan. Any number of scans can be combined in this way, the database engine will keep looping through the data pages until it has completed all the scans.

For example, assume that you have a table with 500,000 pages. UserA executes a SQL statement that requires a scan of the table. When that scan has processed 100,000 pages, UserB executes another SQL statement that scans the same table. The database engine will schedule one set of read requests for pages after 100,001, and passes the rows from each page back to both scans. When the scan reaches the 200,000th page, UserC executes another SQL statement that scans the same table. Starting with page 200,001, the database engine passes the rows from each page it reads back to all three scans. After reading the 500,000th row, the scan for UserA is complete, and the scans for UserB and UserC wrap back and start reading pages starting with page 1. When the database engine gets to page 100,000, the scan for UserB is complete. The scan for Userc then keeps going alone until it reads page 200,000, at which point all the scans have been completed.

Reading Index Pages

SQL Server 2000 reads index pages serially in key order. For example, this illustration shows a simplified representation of a set of leaf pages containing a set of keys and the intermediate index node mapping the leaf pages.

SQL Server 2000 uses the information in the intermediate index page above the leaf level to schedule serial read-ahead I/Os for the pages containing the keys. If a request is made for all the keys from 'ABC' to 'DEF', the instance of SQL Server 2000 first reads the index page above the leaf page. It does not, however, simply read each individual data page in sequence from page 504 to page 556, the last one with keys in the desired range. Instead, the storage engine scans the intermediate index page and builds a list of the leaf pages that must be read. The storage engine then schedules all the I/Os in key order. The storage engine also recognizes that pages 504/505 and 527/528 are contiguous, and performs a single scatter-gather read to retrieve the adjacent pages in one operation. When there are many pages to be retrieved in a serial operation, SQL Server schedules a block of reads at a time. When a subset of these reads is completed, SQL Server schedules an equal number of new reads until all the needed reads have been scheduled.

SQL Server 2000 uses pre-fetching to speed the processing of non-clustered indexes. The leaf rows of a non-clustered index contain pointers to the data rows containing each specific key value. As the database engine reads through the leaf pages of the non-clustered index, it also starts scheduling asynchronous reads for the data rows whose pointers have already been retrieved. This allows the database engine to start retrieving rows before it has completed the scan of the non-clustered index. This process is followed regardless of whether or not the table has a clustered index. SQL Server 2000 Enterprise Edition uses more pre-fetching than other editions of SQL Server, and the level of pre-fetching is not configurable in any edition.

-- 创建测试数据库
CREATE DATABASE Db
GO

-- 对数据库进行备份
BACKUP DATABASE Db TO DISK = ' c:\db.bak ' WITH FORMAT
GO

-- 创建测试表
CREATE TABLE Db.dbo.TB_test(ID int )

-- 延时1秒钟,再进行后面的操作(这是由于SQL Server的时间精度最大为百分之三秒,不延时的话,可能会导致还原到时间点的操作失败)
WAITFOR DELAY ' 00:00:01 '
GO

-- 假设我们现在误操作删除了 Db.dbo.TB_test 这个表
DROP TABLE Db.dbo.TB_test

-- 保存删除表的时间
SELECT dt = GETDATE () INTO #
GO

-- 在删除操作后,发现不应该删除表 Db.dbo.TB_test

-- 下面演示了如何恢复这个误删除的表 Db.dbo.TB_test

-- 首先,备份事务日志(使用事务日志才能还原到指定的时间点)
BACKUP LOG Db TO DISK = ' c:\db_log.bak ' WITH FORMAT
GO

-- 接下来,我们要先还原完全备份(还原日志必须在还原完全备份的基础上进行)
RESTORE DATABASE Db FROM DISK = ' c:\db.bak ' WITH REPLACE ,NORECOVERY
GO

-- 将事务日志还原到删除操作前(这里的时间对应上面的删除时间,并比删除时间略早
DECLARE @dt datetime
SELECT @dt = DATEADD (ms, - 20 ,dt) FROM #  -- 获取比表被删除的时间略早的时间
RESTORE LOG Db FROM DISK = ' c:\db_log.bak ' WITH RECOVERY,STOPAT = @dt
GO

-- 查询一下,看表是否恢复
SELECT * FROM Db.dbo.TB_test

/* --结果:
ID
-----------

(所影响的行数为 0 行)
-- */

-- 测试成功
GO

-- 最后删除我们做的测试环境
DROP DATABASE  Db
DROP TABLE #

随着当今数据库的容量越来越快的朝着在大型数据库或超大型数据库的发展,对于数据库中的大型表以及具有各种访问模式的表的可伸缩性和可管理性运行环境变得 尤为重要,SQL server从SQL server 7.0的分区视图到SQL server 2000中的分区视图中到SQL server 2005所使用的分区表,不断改善大型表所面临的性能、阻塞、备份空间、时间、运营成本等。当表和索引非常大的时候,通过分区表的实现,可以将数据分为更 小,更易于管理,获得更好的可操作性能。本实验介绍基于已存在的表来如何创建分区,管理分区。
一、实验目的:对于已经存在的表且不断增大的情况下构建分区表,管理分区表,提高其性能。
二、主要步骤:对于已经存在的表,我们可以采取以下步骤来对其创建分区表
1.创建分区函数
2.创建分区架构并关联到分区函数
3.删除已经存在的聚集索引
4.基于分区架构重建聚集索引
三、实验环境:
1. windows xp pro (英文版) + sp2

2. SQL server 2005 Developer + sp3
3.实验数据库Performance,此数据库参照实验二:SQL server 2005高可用性之----数据库镜像 中的生成脚本生成数据库,本实验对其数据库的存放做了调整,将数据和日志文件存放在D:\SQL_Data\Performance目录下。
4.对已存在要创建的分区表为:Performance数据库下的Orders表.
5.对Orders表中的orderdate列按年进行水平分区
四、具体试验步骤:
1.创建分区函数
确定分区的数目及分区的列,列的数据类型。本例将Orders表的orderdate按年份水平分五个区,则需要定义四个边界点值。如下,
use Performance;
go
Create partition function
Part_func_orders(datetime) as
range left
for values('20021231 23:59:59.997',
'20031231 23:59:59.997',
'20041231 23:59:59.997',
'20051231 23:59:59.997');
go
--或者使用range right来创建分区函数
Create partition function
Part_func_orders(datetime) as
range right
for values('20030101 00:00:00.000',
'20040101 00:00:00.000',
'20050101 00:00:00.000',
'20060101 00:00:00.000');
go
/*分区值的表示范围(使用range left)
–infinity < x1 <= 20021231
20030101 < x2 <= 20031231
20040101 < x3 <= 20041231
20050101 < x4 <= 20051231
20060101 < x5 <= +infinity
infinity本应当为20020101或20061231,此处仅用于说明表示范围
----------------------------------------------------------
分区值的表示范围(使用range right)
–infinity < x1 < 20030101
20030101 <= x2 < 20040101
20040101 <= x3 < 20050101
20050101 <= x4 < 20060101
20060101 <= x5 < +infinity
通过以上分析表明当range中使用left时,分区的范围右边为小于等于values所指定的值,
当range中使用right时,分区范围左边为大于等于values所指定的值。

规律:在使用 LEFT 分区函数时,第一个值将作为第一个分区中的上边界。在使用 RIGHT 分区函数时,第一个值将作为第二个分区的下边界*/
2. 添加文件组和文件
针对所创建的分区来创建文件组和文件,我们可以创建五个文件组,五个不同的ndf文件来存放不同年份的orders,可以放置于不同的磁盘来减少I/O的 开销,也可以在一个文件组中创建多个文件来存放不同年份的orders,本例创建了四个文件组,其中有一年的orders放置到了Primary组中。
alter database Performance
add filegroup [FG1];
go
alter database Performance
add filegroup [FG2];
go
alter database Performance
add filegroup [FG3];
go
alter database Performance
add filegroup [FG4];
go
alter database Performance
add file
(name = FG1_data,filename = 'D:\SQL_Data\Performance\FG1_data.ndf',size = 3MB)
to filegroup [FG1];
alter database Performance
add file
(name = FG2_data,filename = 'D:\SQL_Data\Performance\FG2_data.ndf',size = 3MB)
to filegroup [FG2];
alter database Performance
add file
(name = FG3_data,filename = 'D:\SQL_Data\Performance\FG3_data.ndf',size = 3MB)
to filegroup [FG3];
alter database Performance
add file
(name = FG4_data,filename = 'D:\SQL_Data\Performance\FG4_data.ndf',size = 3MB)
to filegroup [FG4];
go
3. 创建分区架构并关联到分区函数
Create partition scheme Part_func_orders_scheme
as partition Part_func_orders
to ([FG1],[FG2],[FG3],[FG4],[Primary]);
go
4.重建索引(删除聚集索引以及需要分区字段的索引后重建该类索引,表被按分区值将分配到各文件组)
EXEC sp_helpindex N'orders' --查看orders中使用的索引
drop index idx_cl_od
on orders;
go
create clustered index idx_cl_od
on orders(orderdate)

on Part_func_orders_scheme(orderdate);
go

5. 查看分区的相关情况
--查看分区及分区范围的情况
select * from sys.partitions where object_id = object_id('orders');
select * from sys.partition_range_values;
--查看分区架构情况
select * from sys.partition_schemes;
--查看某一特定分区列值属于哪个分区
select Performance.$partition.Part_func_orders('20050325') as partition_num;
--查看某一特定分区的记录
select * from orders where Performance.$partition.Part_func_orders(orderdate) = 2
--查看各分区所包含的记录数
select $partition.Part_func_orders(orderdate) as partition_num,
count(*) as record_num
from orders
group by $partition.Part_func_orders(orderdate)
order by $partition.Part_func_orders(orderdate);

6.分区的管理
--增加分区值,增加分区之前应先增加或设置新分区使用的文件组
alter database Performance
add filegroup [FG5];
go
alter database Performance
add file
(name = FG5_data,filename = 'D:\SQL_Data\Performance\FG5_data.ndf',size = 3MB )
to filegroup [FG5];
go
alter partition scheme Part_func_orders_scheme
next used [FG5];
go

alter partition function Part_func_orders()
split range('20061231 23:59:59.997')
go

insert into orders
select 10000001,'C0000012906',213,'I','20070101','a'
union all select 10000002,'C0000019995',213,'I','20070109','a'
union all select 10000003,'C0000019996',410,'I','20070512','a';
go

select * from orders where Performance.$partition.Part_func_orders(orderdate) = 6
--合并分区
--合并分区后,以下将新增的三条记录放到了第5个分区中
alter partition function Part_func_orders()
merge range('20061231 23:59:59.997');
go

参考:http://blog.csdn.net/robinson_0612/archive/2009/11/07/4783702.aspx

近期因工作需要,希望比较全面的总结下 SQL SERVER 数据库性能优化相关的注意事项,在网上搜索了一下 , 发现很多文章 , 有的都列出了上百条 , 但是仔细看发现,有很多似是而非或者过时 ( 可能对 SQL SERVER6.5 以前的版本或者 ORACLE 是适用的 ) 的信息,只好自己根据以前的经验和测试结果进行总结了。
我始终认为,一个系统的性能的提高,不单单是试运行或者维护阶段的性能调优的任务,也不单单是开发阶段的事情,而是在整个软件生命周期都需要注意,进行有效工作才能达到的。所以我希望按照软件生命周期的不同阶段来总结数据库性能优化相关的注意事项。
一、 分析阶段
一般来说,在系统分析阶段往往有太多需要关注的地方,系统各种功能性、可用性、可靠性、安全性需求往往吸引了我们大部分的注意力,但是,我们必须注意,性能是很重要的非功能性需求,必须根据系统的特点确定其实时性需求、响应时间的需求、硬件的配置等。最好能有各种需求的量化的指标。
另一方面,在分析阶段应该根据各种需求区分出系统的类型,大的方面,区分是 OLTP (联机事务处理系统)和 OLAP (联机分析处理系统)。
二、 设计阶段
设计阶段可以说是以后系统性能的关键阶段,在这个阶段,有一个关系到以后几乎所有性能调优的过程 — 数据库设计。
在数据库设计完成后,可以进行初步的 索引设计 ,好的索引设计可以指导编码阶段写出高效率的代码,为整个系统的性能打下良好的基础。
以下是性能要求设计阶段需要注意的:
1、 数据库逻辑设计的规范化
数据库逻辑设计的规范化就是我们一般所说的范式,我们可以这样来简单理解范式:
第 1 规范:没有重复的组或多值的列,这是数据库设计的最低要求。
第 2 规范 : 每个非关键字段必须依赖于主关键字,不能依赖于一个组合式主关键字的某些组成部分。消除部分依赖,大部分情况下,数据库设计都应该达到第二范式。
第 3 规范 : 一个非关键字段不能依赖于另一个非关键字段。消除传递依赖,达到第三范式应该是系统中大部分表的要求,除非一些特殊作用的表。
更高的范式要求这里就不再作介绍了,个人认为,如果全部达到第二范式,大部分达到第三范式,系统会产生较少的列和较多的表,因而减少了数据冗余,也利于性能的提高。
2、 合理的冗余
完全按照规范化设计的系统几乎是不可能的,除非系统特别的小,在规范化设计后,有计划地加入冗余是必要的。
冗余可以是冗余数据库、冗余表或者冗余字段,不同粒度的冗余可以起到不同的作用。
冗余可以是为了编程方便而增加,也可以是为了性能的提高而增加。从性能角度来说,冗余数据库可以分散数据库压力,冗余表可以分散数据量大的表的并发压力,也可以加快特殊查询的速度,冗余字段可以有效减少数据库表的连接,提高效率。
3、 主键的设计
主键是必要的, SQL SERVER 的主键同时是一个唯一索引,而且在实际应用中,我们往往选择最小的键组合作为主键,所以主键往往适合作为表的聚集索引。聚集索引对查询的影响是比较大的,这个在下面索引的叙述。
在有多个键的表,主键的选择也比较重要,一般选择总的长度小的键,小的键的比较速度快,同时小的键可以使主键的 B 树结构的层次更少。
主键的选择还要注意组合主键的字段次序,对于组合主键来说,不同的字段次序的主键的性能差别可能会很大,一般应该选择重复率低、单独或者组合查询可能性大的字段放在前面。
4、 外键的设计
外键作为数据库对象,很多人认为麻烦而不用,实际上,外键在大部分情况下是很有用的,理由是:
外键是最高效的一致性维护方法,数据库的一致性要求,依次可以用外键、 CHECK 约束、规则约束、触发器、客户端程序,一般认为,离数据越近的方法效率越高。
谨慎使用级联删除和级联更新,级联删除和级联更新作为 SQL SERVER 2000 当年的新功能,在 2005 作了保留,应该有其可用之处。我这里说的谨慎,是因为级联删除和级联更新有些突破了传统的关于外键的定义,功能有点太过强大,使用前必须确定自己已经把握好其功能范围,否则,级联删除和级联更新可能让你的数据莫名其妙的被修改或者丢失。从性能看级联删除和级联更新是比其他方法更高效的方法。
5、 字段的设计
字段是数据库最基本的单位,其设计对性能的影响是很大的。需要注意如下:
A、 数据类型尽量用数字型,数字型的比较比字符型的快很多。
B、 数据类型尽量小,这里的尽量小是指在满足可以预见的未来需求的前提下的。
C、 尽量不要允许 NULL ,除非必要,可以用 NOT NULL+DEFAULT 代替。
D、 少用 TEXT 和 IMAGE ,二进制字段的读写是比较慢的,而且,读取的方法也不多,大部分情况下最好不用。
E、 自增字段要慎用,不利于数据迁移。

6、 数据库物理存储和环境的设计
在设计阶段,可以对数据库的物理存储、操作系统环境、网络环境进行必要的设计,使得我们的系统在将来能适应比较多的用户并发和比较大的数据量。
这里需要注意文件组的作用,适用文件组可以有效把 I/O 操作分散到不同的物理硬盘,提高并发能力。
7、 系统设计
整个系统的设计特别是系统结构设计对性能是有很大影响的,对于一般的 OLTP 系统,可以选择 C/S 结构、三层的 C/S 结构等,不同的系统结构其性能的关键也有所不同。
系统设计阶段应该归纳一些业务逻辑放在数据库编程实现,数据库编程包括数据库存储过程、触发器和函数。用数据库编程实现业务逻辑的好处是减少网络流量并可更充分利用数据库的预编译和缓存功能。
8、 索引的设计
在设计阶段,可以根据功能和性能的需求进行初步的索引设计,这里需要根据预计的数据量和查询来设计索引,可能与将来实际使用的时候会有所区别。
关于索引的选择,应改主意:
A、 根据数据量决定哪些表需要增加索引,数据量小的可以只有主键。
B、 根据使用频率决定哪些字段需要建立索引,选择经常作为连接条件、筛选条件、聚合查询、排序的字段作为索引的候选字段。
C、 把经常一起出现的字段组合在一起,组成组合索引,组合索引的字段顺序与主键一样,也需要把最常用的字段放在前面,把重复率低的字段放在前面。
D、 一个表不要加太多索引,因为索引影响插入和更新的速度。

三、 编码阶段
编码阶段是本文的重点,因为在设计确定的情况下,编码的质量几乎决定了整个系统的质量。
编码阶段首先是需要所有程序员有性能意识,也就是在实现功能同时有考虑性能的思想,数据库是能进行集合运算的工具,我们应该尽量的利用这个工具,所谓集合运算实际是批量运算,就是尽量减少在客户端进行大数据量的循环操作,而用 SQL 语句或者存储过程代替。关于思想和意识,很难说得很清楚,需要在编程过程中来体会。
下面罗列一些编程阶段需要注意的事项:
1、 只返回需要的数据
返回数据到客户端至少需要数据库提取数据、网络传输数据、客户端接收数据以及客户端处理数据等环节,如果返回不需要的数据,就会增加服务器、网络和客户端的无效劳动,其害处是显而易见的,避免这类事件需要注意:
A、 横向来看,不要写 SELECT * 的语句,而是选择你需要的字段。
B、 纵向来看,合理写 WHERE 子句,不要写没有 WHERE 的 SQL 语句。
C、 注意 SELECT INTO 后的 WHERE 子句,因为 SELECT INTO 把数据插入到临时表,这个过程会锁定一些系统表,如果这个 WHERE 子句返回的数据过多或者速度太慢,会造成系统表长期锁定,诸塞其他进程。
D、 对于聚合查询,可以用 HAVING 子句进一步限定返回的行。
2、 尽量少做重复的工作
这一点和上一点的目的是一样的,就是尽量减少无效工作,但是这一点的侧重点在客户端程序,需要注意的如下:
A、 控制同一语句的多次执行,特别是一些基础数据的多次执行是很多程序员很少注意的。
B、 减少多次的数据转换,也许需要数据转换是设计的问题,但是减少次数是程序员可以做到的。
C、 杜绝不必要的子查询和连接表,子查询在执行计划一般解释成外连接,多余的连接表带来额外的开销。
D、 合并对同一表同一条件的多次 UPDATE ,比如

1. UPDATE EMPLOYEE SET FNAME=’HAIWER’ WHERE EMP_ID=’ VPA30890F’
2.
3. UPDATE EMPLOYEE SET LNAME=’YANG’ WHERE EMP_ID=’ VPA30890F’
4.
5.

这两个语句应该合并成以下一个语句

1. UPDATE EMPLOYEE SET FNAME=’HAIWER’,LNAME=’YANG’
2. WHERE EMP_ID=’ VPA30890F’

E、 UPDATE 操作不要拆成 DELETE 操作 +INSERT 操作的形式,虽然功能相同,但是性能差别是很大的。
F、 不要写一些没有意义的查询,比如
SELECT * FROM EMPLOYEE WHERE 1=2
3、 注意事务和锁
事务是数据库应用中和重要的工具,它有原子性、一致性、隔离性、持久性这四个属性,很多操作我们都需要利用事务来保证数据的正确性。在使用事务中我们需要做到尽量避免死锁、尽量减少阻塞。具体以下方面需要特别注意:
A、 事务操作过程要尽量小,能拆分的事务要拆分开来。
B、 事务操作过程不应该有交互,因为交互等待的时候,事务并未结束,可能锁定了很多资源。
C、 事务操作过程要按同一顺序访问对象。
D、 提高事务中每个语句的效率,利用索引和其他方法提高每个语句的效率可以有效地减少整个事务的执行时间。
E、 尽量不要指定锁类型和索引, SQL SERVER 允许我们自己指定语句使用的锁类型和索引,但是一般情况下, SQL SERVER 优化器选择的锁类型和索引是在当前数据量和查询条件下是最优的,我们指定的可能只是在目前情况下更有,但是数据量和数据分布在将来是会变化的。
F、 查询时可以用较低的隔离级别,特别是报表查询的时候,可以选择最低的隔离级别(未提交读)。
4、 注意临时表和表变量的用法
在复杂系统中,临时表和表变量很难避免,关于临时表和表变量的用法,需要注意:
A、 如果语句很复杂,连接太多,可以考虑用临时表和表变量分步完成。
B、 如果需要多次用到一个大表的同一部分数据,考虑用临时表和表变量暂存这部分数据。
C、 如果需要综合多个表的数据,形成一个结果,可以考虑用临时表和表变量分步汇总这多个表的数据。
D、 其他情况下,应该控制临时表和表变量的使用。
E、 关于临时表和表变量的选择,很多说法是表变量在内存,速度快,应该首选表变量,但是在实际使用中发现,这个选择主要考虑需要放在临时表的数据量,在数据量较多的情况下,临时表的速度反而更快。
F、 关于临时表产生使用 SELECT INTO 和 CREATE TABLE + INSERT INTO 的选择,我们做过测试,一般情况下, SELECT INTO 会比 CREATE TABLE + INSERT INTO 的方法快很多,但是 SELECT INTO 会锁定 TEMPDB 的系统表 SYSOBJECTS 、 SYSINDEXES 、 SYSCOLUMNS ,在多用户并发环境下,容易阻塞其他进程,所以我的建议是,在并发系统中,尽量使用 CREATE TABLE + INSERT INTO ,而大数据量的单个语句使用中,使用 SELECT INTO 。
G、 注意排序规则,用 CREATE TABLE 建立的临时表,如果不指定字段的排序规则,会选择 TEMPDB 的默认排序规则,而不是当前数据库的排序规则。如果当前数据库的排序规则和 TEMPDB 的排序规则不同,连接的时候就会出现排序规则的冲突错误。一般可以在 CREATE TABLE 建立临时表时指定字段的排序规则为 DATABASE_DEFAULT 来避免上述问题。
5、 子查询的用法
子查询是一个 SELECT 查询,它嵌套在 SELECT 、 INSERT 、 UPDATE 、 DELETE 语句或其它子查询中。任何允许使用表达式的地方都可以使用子查询。
子查询可以使我们的编程灵活多样,可以用来实现一些特殊的功能。但是在性能上,往往一个不合适的子查询用法会形成一个性能瓶颈。
如果子查询的条件中使用了其外层的表的字段,这种子查询就叫作相关子查询。相关子查询可以用 IN 、 NOT IN 、 EXISTS 、 NOT EXISTS 引入。
关于相关子查询,应该注意:
A、 NOT IN 、 NOT EXISTS 的相关子查询可以改用 LEFT JOIN 代替写法。比如:

1. SELECT PUB_NAME
2. FROM PUBLISHERS
3. WHERE PUB_ID NOT IN
4. (SELECT PUB_ID
5. FROM TITLES
6. WHERE TYPE = 'BUSINESS' )

可以改写成:

1. SELECT A . PUB_NAME
2. FROM PUBLISHERS A LEFT JOIN TITLES B
3. ON B . TYPE = 'BUSINESS' AND
4. A . PUB_ID=B. PUB_ID
5. WHERE B . PUB_ID IS NULL

1. SELECT TITLE
2. FROM TITLES
3. WHERE NOT EXISTS
4. (SELECT TITLE_ID
5. FROM SALES
6. WHERE TITLE_ID = TITLES . TITLE_ID)

可以改写成:

1. SELECT TITLE
2. FROM TITLES LEFT JOIN SALES
3. ON SALES . TITLE_ID = TITLES . TITLE_ID
4. WHERE SALES . TITLE_ID IS NULL

B、 如果保证子查询没有重复 , IN 、 EXISTS 的相关子查询可以用 INNER JOIN 代替。比如:

1. SELECT PUB_NAME
2. FROM PUBLISHERS
3. WHERE PUB_ID IN
4. (SELECT PUB_ID
5. FROM TITLES
6. WHERE TYPE = 'BUSINESS' )

可以改写成:

1. SELECT DISTINCT A.PUB_NAME
2. FROM PUBLISHERS A INNER JOIN TITLES B
3. ON B.TYPE = 'BUSINESS' AND
4. A.PUB_ID=B. PUB_ID

C、 IN 的相关子查询用 EXISTS 代替,比如

1. SELECT PUB_NAME
2. FROM PUBLISHERS
3. WHERE PUB_ID IN
4. (SELECT PUB_ID
5. FROM TITLES
6. WHERE TYPE = 'BUSINESS' )

可以用下面语句代替:

1. SELECT PUB_NAME
2. FROM PUBLISHERS
3. WHERE EXISTS
4. (SELECT 1
5. FROM TITLES
6. WHERE TYPE = 'BUSINESS' AND
7. PUB_ID= PUBLISHERS . PUB_ID)

D、 不要用 COUNT(*) 的子查询判断是否存在记录,最好用 LEFT JOIN 或者 EXISTS ,比如有人写这样的语句:

1. SELECT JOB_DESC FROM JOBS
2. WHERE (SELECT COUNT(*) FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)=0

应该改成:

1. SELECT JOBS . JOB_DESC FROM JOBS LEFT JOIN EMPLOYEE
2. ON EMPLOYEE . JOB_ID=JOBS . JOB_ID
3. WHERE EMPLOYEE . EMP_ID IS NULL

1. SELECT JOB_DESC FROM JOBS
2. WHERE (SELECT COUNT(*) FROM EMPLOYEE WHERE JOB_ID=JOBS . JOB_ID)<> 0

应该改成:

1. SELECT JOB_DESC FROM JOBS
2. WHERE EXISTS (SELECT 1 FROM EMPLOYEE WHERE JOB_ID=JOBS . JOB_ID)

6、 慎用游标
数据库一般的操作是集合操作,也就是对由 WHERE 子句和选择列确定的结果集作集合操作,游标是提供的一个非集合操作的途径。一般情况下,游标实现的功能往往相当于客户端的一个循环实现的功能,所以,大部分情况下,我们把游标功能搬到客户端。
游标是把结果集放在服务器内存,并通过循环一条一条处理记录,对数据库资源(特别是内存和锁资源)的消耗是非常大的,所以,我们应该只有在没有其他方法的情况下才使用游标。
另外,我们可以用 SQL SERVER 的一些特性来代替游标,达到提高速度的目的。
A、 字符串连接的例子
这是论坛经常有的例子,就是把一个表符合条件的记录的某个字符串字段连接成一个变量。比如需要把 JOB_ID=10 的 EMPLOYEE 的 FNAME 连接在一起,用逗号连接,可能最容易想到的是用游标:

1. DECLARE @NAME VARCHAR( 20 )
2. DECLARE @NAME VARCHAR( 1000 )
3. DECLARE NAME_CURSOR CURSOR FOR
4. SELECT FNAME FROM EMPLOYEE WHERE JOB_ID= 10 ORDER BY EMP_ID
5. OPEN NAME_CURSOR
6. FETCH NEXT FROM RNAME_CURSOR INTO @NAME
7. WHILE @@FETCH_STATUS = 0
8. BEGIN
9. SET @NAMES = ISNULL(@NAMES+’,’,’’)+@NAME
10. FETCH NEXT FROM NAME_CURSOR INTO @NAME
11. END
12. CLOSE NAME_CURSOR
13. DEALLOCATE NAME_CURSOR

可以如下修改,功能相同:

1. DECLARE @NAME VARCHAR( 1000 )
2. SELECT @NAMES = ISNULL(@NAMES+’,’,’’)+FNAME
3. FROM EMPLOYEE WHERE JOB_ID= 10 ORDER BY EMP_ID

B、 用 CASE WHEN 实现转换的例子
很多使用游标的原因是因为有些处理需要根据记录的各种情况需要作不同的处理,实际上这种情况,我们可以用 CASE WHEN 语句进行必要的判断处理,而且 CASE WHEN 是可以嵌套的。比如 :
表结构 :

1. CREATE TABLE 料件表(
2. 料号 VARCHAR( 30 ),
3. 名称 VARCHAR( 100 ),
4. 主单位 VARCHAR( 20 ),
5. 单位 1 VARCHAR( 20 ),
6. 单位 1 参数 NUMERIC( 18 , 4 ),
7. 单位 2 VARCHAR( 20 ),
8. 单位 2 参数 NUMERIC( 18 , 4 )
9. )
10.
11. GO
12.
13. CREATE TABLE 入库表(
14. 时间 DATETIME,
15. 料号 VARCHAR( 30 ),
16. 单位 INT,
17. 入库数量 NUMERIC( 18 , 4 ),
18. 损坏数量 NUMERIC( 18 , 4 )
19. )
20.
21. GO

其中,单位字段可以是 0 , 1 , 2 ,分别代表主单位、单位 1 、单位 2 ,很多计算需要统一单位,统一单位可以用游标实现:

1. DECLARE @料号 VARCHAR( 30 ),
2. @单位 INT,
3. @参数 NUMERIC( 18 , 4 ),
4.
5. DECLARE CUR CURSOR FOR
6. SELECT 料号,单位 FROM 入库表 WHERE 单位 <> 0
7. OPEN CUR
8. FETCH NEXT FROM CUR INTO @料号,@单位
9. WHILE @@FETCH_STATUS<>- 1
10. BEGIN
11. IF @单位= 1
12. BEGIN
13. SET @参数=(SELECT 单位 1 参数 FROM 料件表 WHERE 料号 =@料号)
14. UPDATE 入库表 SET 数量=数量*@参数,损坏数量=损坏数量*@参数,单位= 1 WHERE CURRENT OF CUR
15. END
16. IF @单位= 2
17. BEGIN
18. SET @参数=(SELECT 单位 1 参数 FROM 料件表 WHERE 料号 =@料号)
19. UPDATE 入库表 SET 数量=数量*@参数,损坏数量=损坏数量*@参数,单位= 1 WHERE CURRENT OF CUR
20. END
21. FETCH NEXT FROM CUR INTO @料号,@单位
22. END
23. CLOSE CUR
24. DEALLOCATE CUR

可以改写成:

1. UPDATE A SET
2. 数量=CASE A.单位 WHEN 1 THEN A.数量*B. 单位 1 参数
3. WHEN 2 THEN A.数量*B. 单位 2 参数
4. ELSE A.数量
5. END,
6. 损坏数量= CASE A.单位 WHEN 1 THEN A. 损坏数量*B. 单位 1 参数
7. WHEN 2 THEN A. 损坏数量*B. 单位 2 参数
8. ELSE A. 损坏数量
9. END,
10. 单位= 1
11. FROM入库表 A, 料件表 B
12. WHERE A.单位<> 1 AND
13. A.料号=B.料号

C、 变量参与的 UPDATE 语句的例子
SQL ERVER 的语句比较灵活,变量参与的 UPDATE 语句可以实现一些游标一样的功能,比如:

1. SELECT A,B,C,CAST(NULL AS INT) AS 序号
2. INTO #T
3. FROM 表
4. ORDER BY A ,NEWID()

产生临时表后,已经按照 A 字段排序,但是在 A 相同的情况下是乱序的,这时如果需要更改序号字段为按照 A 字段分组的记录序号,就只有游标和变量参与的 UPDATE 语句可以实现了,这个变量参与的 UPDATE 语句如下:

1. DECLARE @A INT
2. DECLARE @序号 INT
3. UPDATE #T SET
4. @序号=CASE WHEN A=@A THEN @序号+1 ELSE 1 END,
5. @A=A,
6. 序号=@序号

D、 如果必须使用游标,注意选择游标的类型,如果只是循环取数据,那就应该用只进游标(选项 FAST_FORWARD ),一般只需要静态游标(选项 STATIC )。
E、 注意动态游标的不确定性,动态游标查询的记录集数据如果被修改,会自动刷新游标,这样使得动态游标有了不确定性,因为在多用户环境下,如果其他进程或者本身更改了纪录,就可能刷新游标的记录集。
7、 尽量使用索引
建立索引后,并不是每个查询都会使用索引,在使用索引的情况下,索引的使用效率也会有很大的差别。只要我们在查询语句中没有强制指定索引,索引的选择和使用方法是 SQLSERVER 的优化器自动作的选择,而它选择的根据是查询语句的条件以及相关表的统计信息,这就要求我们在写 SQL 语句的时候尽量使得优化器可以使用索引。
为了使得优化器能高效使用索引,写语句的时候应该注意:
A、 不要对索引字段进行运算,而要想办法做变换,比如
SELECT ID FROM T WHERE NUM/2=100
应改为 :
SELECT ID FROM T WHERE NUM=100*2

SELECT ID FROM T WHERE NUM/2=NUM1
如果 NUM 有索引应改为 :
SELECT ID FROM T WHERE NUM=NUM1*2
如果 NUM1 有索引则不应该改。

发现过这样的语句:

1. SELECT 年,月,金额 FROM 结余表
2. WHERE 100 *年+月= 2007 * 100 + 10

应该改为:

1. SELECT 年,月,金额 FROM 结余表
2. WHERE 年=2007 AND
3. 月=10

B、 不要对索引字段进行格式转换
日期字段的例子:
WHERE CONVERT(VARCHAR(10), 日期字段 ,120)=’2008-08-15’
应该改为
WHERE 日期字段〉 =’2008-08-15’ AND 日期字段 <’2008-08-16’

ISNULL 转换的例子:
WHERE ISNULL( 字段 , ’’ )<> ’’应改为 :WHERE 字段 <> ’’
WHERE ISNULL( 字段 , ’’ )= ’’不应修改
WHERE ISNULL( 字段 , ’ F ’ ) = ’ T ’应改为 : WHERE 字段 = ’ T ’
WHERE ISNULL( 字段 , ’ F ’ )<> ’ T ’不应修改
C、 不要对索引字段使用函数
WHERE LEFT(NAME, 3)='ABC' 或者 WHERE SUBSTRING(NAME,1, 3)='ABC'
应改为 :
WHERE NAME LIKE 'ABC%'

日期查询的例子:
WHERE DATEDIFF(DAY, 日期 ,'2005-11-30')=0 应改为 :WHERE 日期 >='2005-11-30' AND 日期 <'2005-12-1 ‘
WHERE DATEDIFF(DAY, 日期 ,'2005-11-30')>0 应改为 :WHERE 日期 <'2005-11-30 ‘
WHERE DATEDIFF(DAY, 日期 ,'2005-11-30')>=0 应改为 :WHERE 日期 <'2005-12-01 ‘
WHERE DATEDIFF(DAY, 日期 ,'2005-11-30')<0 应改为 :WHERE 日期 >='2005-12-01 ‘
WHERE DATEDIFF(DAY, 日期 ,'2005-11-30')<=0 应改为 :WHERE 日期 >='2005-11-30 ‘
D、 不要对索引字段进行多字段连接
比如:
WHERE FAME+ ’ . ’ +LNAME= ‘ HAIWEI.YANG ’
应改为 :
WHERE FNAME=‘HAIWEI’ AND LNAME=‘YANG’
8、 注意连接条件的写法
多表连接的连接条件对索引的选择有着重要的意义,所以我们在写连接条件条件的时候需要特别的注意。
A、 多表连接的时候,连接条件必须写全,宁可重复,不要缺漏。
B、 连接条件尽量使用聚集索引
C、 注意 ON 部分条件和 WHERE 部分条件的区别

9、 其他需要注意的地方
经验表明,问题发现的越早解决的成本越低,很多性能问题可以在编码阶段就发现,为了提早发现性能问题,需要注意:
A、 程序员注意、关心各表的数据量。
B、 编码过程和单元测试过程尽量用数据量较大的数据库测试,最好能用实际数据测试。
C、 每个 SQL 语句尽量简单
D、 不要频繁更新有触发器的表的数据
E、 注意数据库函数的限制以及其性能
10、 学会分辩 SQL 语句的优劣
自己分辨 SQL 语句的优劣非常重要,只有自己能分辨优劣才能写出高效的语句。
A、 查看 SQL 语句的执行计划,可以在查询分析其使用 CTRL+L 图形化的显示执行计划,一般应该注意百分比最大的几个图形的属性,把鼠标移动到其上面会显示这个图形的属性,需要注意预计成本的数据,也要注意其标题,一般都是 CLUSTERED INDEX SEEK 、 INDEX SEEK 、 CLUSTERED INDEX SCAN 、 INDEX SCAN 、 TABLE SCAN 等,其中出现 SCAN 说明语句有优化的余地。也可以用语句
SET SHOWPLAN_ALL ON
要执行的语句
SET SHOWPLAN_ALL OFF
查看执行计划的文本详细信息。
B、 用事件探查器跟踪系统的运行,可疑跟踪到执行的语句,以及所用的时间, CPU 用量以及 I/O 数据,从而分析语句的效率。
C、 可以用 WINDOWS 的系统性能检测器,关注 CPU 、 I/O 参数
四、 测试、试运行、维护阶段
测试的主要任务是发现并修改系统的问题,其中性能问题也是一个重要的方面。重点应该放在发现有性能问题的地方,并进行必要的优化。主要进行语句优化、索引优化等。
试运行和维护阶段是在实际的环境下运行系统,发现的问题范围更广,可能涉及操作系统、网络以及多用户并发环境出现的问题,其优化也扩展到操作系统、网络以及数据库物理存储的优化。
这个阶段的优花方法在这里不再展开,只说明下索引维护的方法:
A、 可以用 DBCC DBREINDEX 语句或者 SQL SERVER 维护计划设定定时进行索引重建,索引重建的目的是提高索引的效能。
B、 可以用语句 UPDATE STATISTICS 或者 SQL SERVER 维护计划设定定时进行索引统计信息的更新,其目的是使得统计信息更能反映实际情况,从而使得优化器选择更合适的索引。
C、 可以用 DBCC CHECKDB 或者 DBCC CHECKTABLE 语句检查数据库表和索引是否有问题,这两个语句也能修复一般的问题。
D、
五、 网上资料中一些说法的个人不同意见
1、 “应尽量避免在 WHERE 子句中对字段进行 NULL 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
SELECT ID FROM T WHERE NUM IS NULL
可以在 NUM 上设置默认值 0 ,确保表中 NUM 列没有 NULL 值,然后这样查询:
SELECT ID FROM T WHERE NUM=0 ”
个人意见:经过测试, IS NULL 也是可以用 INDEX SEEK 查找的, 0 和 NULL 是不同概念的,以上说法的两个查询的意义和记录数是不同的。
2、 “应尽量避免在 WHERE 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。”
个人意见:经过测试, <> 也是可以用 INDEX SEEK 查找的。
3、 “应尽量避免在 WHERE 子句中使用 OR 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
SELECT ID FROM T WHERE NUM=10 OR NUM=20
可以这样查询:
SELECT ID FROM T WHERE NUM=10
UNION ALL
SELECT ID FROM T WHERE NUM=20 ”
个人意见:主要对全表扫描的说法不赞同。
4、 “ IN 和 NOT IN 也要慎用,否则会导致全表扫描,如:
SELECT ID FROM T WHERE NUM IN(1,2,3)
对于连续的数值,能用 BETWEEN 就不要用 IN 了:
SELECT ID FROM T WHERE NUM BETWEEN 1 AND 3 ”
个人意见:主要对全表扫描的说法不赞同。
5、 “如果在 WHERE 子句中使用参数,也会导致全表扫描。因为 SQL 只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
SELECT ID FROM T WHERE NUM=@NUM
可以改为强制查询使用索引:
SELECT ID FROM T WITH(INDEX( 索引名 )) WHERE NUM=@NUM ”
个人意见:关于局部变量的解释比较奇怪,使用参数如果会影响性能,那存储过程就该校除了,我坚持我上面对于强制索引的看法。
6、 “尽可能的使用 VARCHAR/NVARCHAR 代替 CHAR/NCHAR ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。”
个人意见:“在一个相对较小的字段内搜索效率显然要高些”显然是对的,但是字段的长短似乎不是由变不变长决定,而是业务本身决定。在 SQLSERVER6.5 或者之前版本,不定长字符串字段的比较速度比定长的字符串字段的比较速度慢很多,所以对于那些版本,我们都是推荐使用定长字段存储一些关键字段。而在 2000 版本,修改了不定长字符串字段的比较方法,与定长字段的比较速度差别不大了,这样为了方便,我们大量使用不定长字段。
7、 关于连接表的顺序或者条件的顺序的说法,经过测试,在 SQL SERVER ,这些顺序都是不影响性能的,这些说法可能是对 ORACLE 有效。

本文摘自Haiwer的专栏