技术开发 频道

行式数据库评测:SQL Server2008 R2版

  【IT168 专稿】SQL Server是微软开发的一套关系型数据库。SQL Server一开始并不是微软自己研发的产品,而是与Sybase合作所产生,其最早的开发者是Sybase,1989年发布SQL Server 1.0 for OS/2,同时微软也和Sybase合作过SQL Server 4.2版本的研发,1993年微软将SQL Server 4.2移植到Windows NT 3.1版,在与Sybase终止合作关系后,自行开发出SQL Server 6.0版,往后的SQL Server即均由微软自行研发。而Sybase后续的产品改名为Sybase Adaptive Server。

  SQL Server与其他数据库产品最大的区别是它的操作系统平台只能是Windows,包括Windows桌面和服务器操作系统。版本包括免费的Express和收费的Developer,Workgroup,Web,Standard,Enterprise和Datacenter版本。最复杂的版本是Parallel Data Warehouse,它实际上是一个解决方案,利用大规模并行处理(MPP)架构和合作伙伴的硬件结合,扩展数据仓库到数百TB的数据。免费的Express版只能使用1个CPU和1GB的内存,数据量不超过10GB,基本上只适用于很小规模的数据库。SQL Server数据库的版本演进在2000年后停滞了较长时间,2005年后基本上是2~3年推出一个新版本。目前主流的版本是2008和2008 R2,本文将基于2008 R2评估版展开评测!

  一、数据库安装

  微软在其官网提供了各种版本SQL Server的下载,我们选择最新的SQL Server 2008 R2简体中文版本(查看这里),这个试用版有180天的试用期限制。试用版默认是企业版,是仅次于数据中心版的版本,比其他版本具有更多高级功能,如:压缩、并行查询等,也具有更大的扩展性,能利用更多的内存和CPU。几种版本的主要技术指标如下,更详细的表格,请参考这里

一、数据库安装

  这里CPU个数是指物理CPU的个数,而不是核心的个数,因此企业版的最大能用到的CPU 8个一般也足够了。微软的产品安装中提供了本地化的联机帮助文档,同时在msdn网站提供在线浏览。在上面可以查到SQL Server各个版本的文档资料。

  SQL Server 2008 R2支持的操作系统版本只有微软自己的3种Windows Server:Windows Server 2003;Windows Server 2008;Windows Server 2008 R2。本次测试基于 Intel Xeon 7550*8的 PC服务器上用 VMWare VSphere 4.1管理的虚拟机,虚拟机的逻辑 CPU个数是 8,内存 100GB,存储为 8个 300GB SAS本地磁盘,采用一块 512M缓存 RAID卡,按 RAID5方式组成磁盘阵列。操作系统采用Windows Server 2008 R2 简体中文版,这是一个只有x64平台的版本,将来微软的Windows服务器版均只支持x64。为了充分发挥操作系统和硬件的能力,选用的安装文件也是x64版本,x64压缩可执行文件格式的安装包(点击下载),安装文件大约1.4GB,网站也提供了安装盘DVD镜像的iso文件,包括x86的32位、64位和IA64版本,大小大约为4GB。

  安装步骤如下:

  1.安装前准备工作

  将下载的安装文件上传到待安装的Windows机器,如果是一个大的EXE文件,那么双击运行它,按照提示将实际安装文件解压缩到某个目录,在此目录下找到一个名为Setup.exe的文件,双击运行它即可进入安装界面。如果下载的是一个包含多个平台的安装DVD镜像,则可以用虚拟光驱加载它,然后在虚拟光驱盘符根目录下找到Setup.exe。

  2.安装SQL Server 2008 R2数据库软件

  SQL Server评估版的安装过程比较直观,安装程序是图形界面,首先出来SQL Server安装中心的画面,点击左侧导航的“安装”文字,进入安装类型选择,如图1所示。

一、数据库安装
▲图1

  点击第一项“全新安装或向现有安装添加功能”。首先检查安装程序支持规则。如图2所示。

一、数据库安装
▲图2

  第一步检查通过以后,点击确定,安装程序提示指定安装版本,默认是评估版,如图3。如果用户购买了正式的版本,则在第2个输入框输入产品序列号,安装程序根据序列号判断用户可安装的版本。

一、数据库安装
▲图3

  接受许可协议后,安装程序开始安装程序支持文件,这一步容易出现错误,如图4。

一、数据库安装
▲图4

  如果出现类似下面图5的错误,按照安装程序给出的提示信息,一般用户是难以解决的。我上网查了相关错误的信息,在关闭了UAC后,其中一台服务器可以继续安装,而另一台相同配置的机器,还是同类的错误,从安装日志看也不能发现问题。根据以往的经验,关闭杀毒软件的防火墙后问题解决。

一、数据库安装
▲图5

  如果没有出错,下面安装就比较顺利了,基本上都是确定,下一步等等,需要用户选择的有下面几处:

  选择安装角色。

一、数据库安装
▲图6

  选择安装的组件和安装路径,这里点击全选按钮选择全部组件,并更改安装目录到硬盘空闲较多的逻辑盘下。如果系统盘比较空闲,也可以使用默认值。

一、数据库安装
▲图7

  在安装规则一步没有需要用户输入的信息。

  在实例配置一步使用默认的实例名SQLSERVER,并更改实例根目录。如图8所示。

一、数据库安装
▲图8

  在服务器配置一步,输入各种服务的用户名和口令,这里为了简单起见,所有服务采用默认的帐户名,密码留空。如图9所示。然后点击确定。

一、数据库安装
▲图9

  在数据库引擎配置一步,指定操作系统和数据库混合认证,输入用户sa的口令,口令应包括字母和数字符号,以满足复杂性的要求,这个口令在以后的测试过程中会用到。点击添加当前用户,指定数据库管理员。

一、数据库安装
▲图10

  在分析服务配置一步,同样添加当前用户,图略。

  在报表服务配置一步,选择默认配置,图略。

  在准备安装一步,安装程序给出了当前的配置选项和配置文件,这个配置文件可用于将来的静默安装,如图11。

一、数据库安装
▲图11

  以下是配置文件的例子,注意,如果要进行静默安装,需要将QUIET或QUITESIMPLE参数修改为True,并且删除或注释掉UIMODE参数,然后在命令行窗口运行setup程序,这一步也可以在SQL Server安装中心中点选高级下的基于配置文件安装,但出错信息不易观察,因此还是推荐在命令行输入Setup。

  完整的无人参与安装示例,下面显示所有必需的参数,这些参数都可以在配置文件中指定。如果使用混合验证,还需要提供SAPWD参数并输入sa的密码。

setup.exe /Q /IACCEPTSQLSERVERLICENSETERMS /ACTION=install /PID=<validpid> /FEATURES=SQL,AS,RS,IS,Tools
/INSTANCENAME=MSSQLSERVER /SQLSVCACCOUNT="MyDomain\MyAccount"
/SQLSVCPASSWORD="************" /SQLSYSADMINACCOUNTS="MyDomain\MyAccount "
/AGTSVCACCOUNT="MyDomain\MyAccount" /AGTSVCPASSWORD="************"
/ASSVCACCOUNT="MyDomain\MyAccount" /ASSVCPASSWORD="************"
/RSSVCACCOUNT="MyDomain\MyAccount" /RSSVCPASSWORD="************"
/ISSVCAccount="MyDomain\MyAccount" /ISSVCPASSWORD="************"
/ASSYSADMINACCOUNTS="MyDomain\MyAccount"

--实际安装示例
D:\temp\1>setup /CONFIGURATIONFILE=ConfigurationFile.ini
Microsoft (R) SQL Server
2008 R2
--安装如果没有问题将不提示任何信息返回cmd命令行,可以用下面命令验证SQL Server已经运行
--
添加系统路径
D:\temp\1>set path=D:\PFILE\Microsoft SQL Server\100\Tools\Binn;%PATH%

D:\
temp\1>osql -E
1> select count(*) from sysobjects;
2> go

-----------
        2002

(
1 行受影响)

   对于需要大量重复安装类似的服务器,静默安装选项比较有用。下面的例子用加粗字体突出显示了需要修改的参数。

;SQLSERVER2008 Configuration File
[SQLSERVER2008]
;接受 SQL Server 许可
IAcceptSQLServerLicenseTerms
="True"

;sa帐户的密码,sa帐户的密码要求为强密码(大于等于三位包含字母)
SAPWD
=abcd1234
; 为您已指定的 SQL Server 功能指定实例 ID。SQL Server 目录结构、注册表结构和服务名称将反映 SQL Server 实例的实例 ID。
INSTANCEID
="MSSQLSERVER"
; 指定安装程序的工作流,如 INSTALL、UNINSTALL 或 UPGRADE。这是必需的参数。
ACTION
="Install"
; 指定要安装、卸载或升级的功能。优异功能列表包括 SQL、
AS、RS、IS 和工具。SQL 功能将安装数据库引擎、复制和全文。工具功能将安装管理工具、联机丛书、Business Intelligence Development Studio 和其他共享组件。
FEATURES
=SQLENGINE,REPLICATION,FULLTEXT,AS,RS,BIDS,CONN,IS,BC,SDK,BOL,SSMS,ADV_SSMS,SNAC_SDK,OCS
; 显示命令行参数用法
HELP
="False"
; 指定应将详细的安装程序日志传送到控制台。
INDICATEPROGRESS
="False"
; 安装程序将不会显示任何用户界面。
QUIET
="True"
; 安装程序将只显示进度,不需要任何用户交互。
QUIETSIMPLE
="False"
; 指定安装程序应该安装到 WOW64 中。IA64 或
32 位系统不支持此命令行参数。
X86
="False"
; 尚未定义命令行参数 ENU 的详细帮助。
ENU
="False"
; 用于控制用户界面行为的参数。有效值对于完整 UI 为 Normal,对于简化的 UI 为 AutoAdvance。
;UIMODE
="Normal"
; 指定是否可将错误报告给 Microsoft 以便改进以后的 SQL Server 版本。指定
1 或 True 将启用此功能,指定 0 或 False 将禁用此功能。
ERRORREPORTING
="False"
; 指定本机共享组件的安装根目录。
INSTALLSHAREDDIR
="F:\PFILE\Microsoft SQL Server"
; 指定 WOW64 共享组件的安装根目录。
INSTALLSHAREDWOWDIR
="F:\PFILE(x86)\Microsoft SQL Server"
; 指定安装目录。
INSTANCEDIR
="F:\PFILE\Microsoft SQL Server"
; 指定可以收集 SQL Server 功能使用情况数据,并将数据发送到 Microsoft。指定
1 或 True 将启用此功能,指定 0 或 False 将禁用此功能。
SQMREPORTING
="False"
; 指定默认实例或命名实例。MSSQLSERVER 是非 Express 版本的默认实例,SQLExpress 则是 Express 版本的默认实例。在安装 SQL Server 数据库引擎(SQL)、Analysis Services (
AS)或 Reporting Services (RS)时,此参数是必需的。
INSTANCENAME
="MSSQLSERVER"
; 代理帐户名
AGTSVCACCOUNT
="NT AUTHORITY\NETWORK SERVICE"

。。。
FTSVCACCOUNT
="NT AUTHORITY\LOCAL SERVICE"

  此时所有准备工作都已具备,点击安装。进入文件复制界面,耐心等候大约几十分钟,安装完成,图略。

  安装程序创建了联机帮助文档,可在不联网的状态下查看。如图12。

一、数据库安装
▲图12

  3.创建和访问数据库

  软件安装完成后,自动创建了系统数据库master,tempdb等,并启动了数据库服务,可以接受用户命令进行数据库的各项操作了。如果用户需要,可以通过图形界面管理工具,也可以使用SQL命令创建自己的数据库。这里我们采用后一种,以便更深入地了解SQL Server数据库的命令格式和选项。具体命令和步骤在后面给出,下面先了解一下SQL Server的几个概念。

  至此,安装过程结束。

  二、SQL Server功能简介

  在前面安装配置过程中,我们已经接触到一些SQL Server数据库的概念,其中有些概念与其它数据库有所区别。下面着重解释一下数据库软件、实例和数据库的关系。

  (1) 数据库软件

  也就是数据库软件的一个拷贝,包括软件安装的二进制可执行文件和其他文件。

  可以在单台机器上安装多个 SQL Server 产品。比如2008和2005版本。

  (2) 实例

  实例 (instance)有2个含义。 1.计算机上运行的 SQL Server 副本。2.报表项(例如报表部件、子报表、动态成员或组)的特定副本。我们在安装过程中指定的MSSQLSERVER和就是数据库默认实例名。SQL Server的命名实例,该实例通过计算机的网络名称加上实例名称以<计算机名称>\<实例名称>格式进行标识。SQL Server可以在同一计算机上创建并同时运行多个关系数据库实例,但是需要用不同的监听端口访问。每个实例有其独立的系统和用户数据库集合。应用程序采用与连接不同计算机上的 SQL Server 实例大致相同的方式连接同一计算机上的各个实例。在同时运行较早版本的SQL Server的计算机上,无法安装SQL Server 2008 R2的默认实例。必须将以前版本的SQL Server安装升级到SQL Server 2008 R2默认实例,或者保留以前版本SQL Server的默认实例并安装SQL Server 2008 R2的命名实例。每个命名实例都由非重复的一组服务组成,并且对于排序规则和其他选项可以有完全不同的设置。目录结构、注册表结构和服务名称都反映了所指定的具体实例名称。

  (3) 数据库

  数据库 (database)是为特定目的(例如搜索、排序和重新组合数据)而组织和表示的信息、表和其他对象的集合。

  数据库在实例中创建,同一实例中的数据库,只要具有访问的权限,可以不受限制地互相访问。

  从上边的描述可知,一个系统可以包括多个SQL Server软件副本,每个副本可以包括多个实例,但多个副本的实例只能有1个默认实例。每个实例可以有多个数据库,通过指定实例名,可以访问某个副本的某个实例下的不同数据库。

  在我们的主测试环境中,目前只有一个SQL Server数据库软件,创建了一个默认实例,实例中创建了默认的系统数据库master,tempdb等数据库。后面的测试中还会创建测试专用的数据库。

  (4) 用户和架构(Schema)

  SQL Server的用户和登录模式相关,在信任登录模式就是利用操作系统下验证的用户登录。不需要提供用户名和口令。而如果利用sa或其他SQL Server的登录名连接到实例,那么需要输入用户名和口令。登录名(login)和数据库中的用户(user)关联,但只能对数据库的用户进行对象权限或系统权限的授权。

  Msdn文档中写到“数据库架构 (database schema)数据库的表、字段、数据类型以及主键和外键的名称。”这个叙述不是太清晰,更好的表述是:架构(schema)是用于在数据库中创建的数据库对象的一个高级限定符。它是数据库对象,例如表、视图、索引或触发器的一个集合。它提供了数据库对象的一个逻辑分类。在创建对象时,可以使用架构将这些对象进行分组。一个对象只能属于一种架构。使用 CREATE SCHEMA 语句来创建架构。有关架构的信息保存在连接的数据库的系统目录表中。架构是用来组织数据库中对象的一种方式,可以把它理解为目录。所有同一个目录下的内容属于一个架构,要访问某个对象,就需要同时指定架构名和对象名。若用户设定了默认架构,那么用该用户对应的登录名访问时,此架构下的所有对象可以不带架构名访问。

  从 SQL Server 2005 开始,架构的行为发生了更改。架构不再等效于数据库用户;现在,每个架构都是独立于创建它的数据库用户存在的不同命名空间。也就是说,架构只是对象的容器。任何用户都可以拥有架构,并且架构所有权可以转移。每个用户都拥有一个默认架构。可以使用 CREATE USER 或 ALTER USER 的 DEFAULT_SCHEMA 选项设置和更改默认架构。如果未定义 DEFAULT_SCHEMA,则数据库用户将使用 dbo 作为默认架构。

  作为 CREATE SCHEMA 语句的一部分创建的任何对象的定义者是架构所有者。此所有者可以授予和撤销其他用户的架构权限。

一、数据库安装
▲图13数据库引擎权限层次结构之间的关系

  从上图可以看出各种对象的层次关系。

  下面用一个例子解释登录名、用户、数据库、架构、对象之间的关系。

  创建一个登录名,也就是SQL Server的一个用户。

F:\soft\1>osql -E
1> SELECT SUSER_NAME() --显示操作系统用户,如果用-Usa方式登录,这里显示sa
2> go

---------------------------
WIN-NF10S6S7KTC\rjkfcuser
1> SELECT USER_NAME()  --显示当前用户
2> go

--------------------
dbo
1> SELECT schema_NAME()--显示当前架构
2> go

----------------------
dbo
1> CREATE LOGIN l01
2>     WITH PASSWORD = 'Abcd12#$';
3> go

   在tpch数据库中创建一个u01用户,并设置默认架构为s01,这个架构可以晚于用户创建,并授予用户u01,tpch数据库的ALL权限:

1> use tpch
2> go
1> CREATE USER u01 FOR LOGIN l01
2>     WITH DEFAULT_SCHEMA = s01;
3> GO
1> grant all to u01;
2> go
ALL 权限已不再推荐使用,并且只保留用于兼容性目的。它并不表示对实体定义了 ALL 权限。

   在tpch数据库中创建一个s01架构,并指定架构拥有这是u01用户,以u01用户创建的表自动属于s01架构。

1> CREATE SCHEMA s01 AUTHORIZATION u01
2> go
1> EXECUTE AS user = 'u01'
2> go
1> create table t (a int not null);
2> go
1> select * from t;
2> go
a
-----------

(
0 行受影响)
1> select * from tpch.s01.t;
2> go
a
-----------

(
0 行受影响)

  用sa用户访问,可以区别dbo架构和s01架构下的同名t表是不同的对象。

1> revert
2> go
1> select * from tpch.dbo.t;
2> go
c          c1          c2
---------- ----------- ----------
1                    2 3
2                   21 31
3                   31 91

(
3 行受影响)
1> select * from tpch.s01.t;
2> go
a
-----------

(
0 行受影响)

  用登录名l01登录数据库服务器,并打开tpch数据库,可以直接进入s01默认架构。

F:\soft\1>osql -Ul01
密码:

1> SELECT SUSER_NAME() --显示登录名
2> go

------------------------
l01
1> SELECT USER_NAME() --当前为默认master数据库,不存在对应的用户名,显示为guest用户
2> go

----------------------
guest

1> SELECT schema_NAME()
2> go

----------------------
guest

1> use tpch
2> go
1> SELECT USER_NAME() --当前为tpch数据库,存在对应的用户名u01
2> go

----------------------
u01

1> SELECT schema_NAME()--当前为tpch数据库,存在用户名u01对应的架构s01
2> go

----------------------
s01
1> use tpch
2> go
1> select * from t;
2> go
a
-----------

(
0 行受影响)

  (5)聚集索引和非聚集索引

  聚集索引,也可以理解为排序索引,就是说表中的数据存储位置,根据索引的排序进行实际存储,因此效率是相当高的。因为聚集索引决定了表中数据行的存储位置,所以,一个表不可能有两个或以上的聚集索引。因此,如果一个表中已经有一个聚集索引,那么这个表中其他的索引都将是非聚集索引。SQL Server的聚集索引和Oracle的索引组织表类似。

  聚集索引对于那些经常要搜索范围值的列特别有效。使用聚集索引找到包含第一个值的行后,便可以确保包含后续索引值的行在物理相邻。例如,如果应用程序执行的一个查询经常检索某一日期范围内的记录,则使用聚集索引可以迅速找到包含开始日期的行,然后检索表中所有相邻的行,直到到达结束日期。这样有助于提高此类查询的性能。同样,如果对从表中检索的数据进行排序时经常要用到某一列,则可以将该表在该列上聚集(物理排序),避免每次查询该列时都进行排序,从而节省成本。

  非聚集索引与其他数据库中的索引类似。数据存储在一个地方,索引存储在另一个地方,索引带有指针指向数据的存储位置。索引中的项目按索引键值的顺序存储,而表中的信息按另一种顺序存储(这可以由聚集索引规定)。如果在表中未创建聚集索引,则无法保证这些行具有任何特定的顺序。

  SQL Server在搜索数据值时,先对非聚集索引进行搜索,找到数据值在表中的位置,然后从该位置直接检索数据。这使非聚集索引成为精确匹配查询的非常好的方法,因为索引包含描述查询所搜索的数据值在表中的精确位置的条目。如果基础表使用聚集索引排序,则该位置为聚集键值;否则,该位置为包含行的文件号、页号和槽号的行ID(RID)。例如,对于在列C上有非聚集索引的表,如要搜索其列C='a'的行,SQL Server会在索引中查找这样一个条目,该条目精确列出匹配的列C在表中的页和行,然后直接转到该页该行。

  三、TPCH测试

  和前几次测试一样,主要测试数据加载和查询性能,也对数据压缩进行测试。

  1.准备工作

  osql是比较早版本的SQL Server开始提供的命令行工具,现在SQL Server 2008 R2仍然提供。虽然是微软不推荐的过时工具,但对我们的测试还是够用的,因此这里沿用它进行数据库创建、表创建、主键和外键创建、查询等工作,它有2种登录方式,用户名/口令方式和信任方式,后者在服务器所在计算机只要输入-E参数就可以登录。

  它包括单行命令方式和交互方式。

  osql完整的命令行参数选项如下:

F:\soft\1>osql /?
Microsoft (R) SQL Server 命令行工具
版本
10.50.1600.1 NT x64
版权所有 (c) Microsoft Corporation。保留所有权利。

注意: osql 并不支持 SQL Server
2008 R2的所有功能。
请使用 sqlcmd。有关详细信息,请参阅 SQL Server 联机丛书。

用法: osql                  
[-U 登录 ID]          [-P 密码]
  
[-S 服务器]                [-H 主机名]           [-E 可信连接]
  
[-d 使用数据库名称]        [-l 登录超时值]       [-t 查询超时值]
  
[-h 标题]                  [-s 列分隔符]         [-w 列宽]
  
[-a 数据包大小]            [-e 回显输入]         [-I 允许带引号的标识符]
  
[-L 列出服务器]            [-c 命令结束]         [-D ODBC DSN 名称]
  
[-q "命令行查询"]          [-Q "命令行查询" 并退出]
  
[-n 删除编号方式]          [-m 错误级别]
  
[-r 发送到 stderr 的消息]  [-V 严重级别]
  
[-i 输入文件]              [-o 输出文件]
  
[-p 打印统计信息]          [-b 出错时中止批处理]
  
[-X[1] 禁用命令,[退出的同时显示警告]]
  
[-O 使用旧 ISQL 行为禁用下列项]
      
<EOF> 批处理
      自动调整控制台宽度
      宽消息
      默认错误级别为
-11
  
[-? 显示语法摘要]

   构造测试环境,首先建立一个名为tpch的数据库,并设置它为当前数据库。然后执行创建表的脚本。注意在操作系统命令行中指定SQL脚本位置不需要用单引号括起,而在SQL命令中用到的文件路径需要用单引号括起。

F:\soft>osql -E
1> create database tpch
2> on (name=tpch,filename='F:\soft\1\tpch.mdf',size=20GB) --设定数据文件位置大小
3> log on(name=tpch_log,filename='F:\soft\1\tpch.ldf',size=5GB) --设置日志文件位置大小
4> go
1> alter database tpch set recovery simple; --改变恢复模式为简单模式
2> go
1> use tpch
2> go

--在操作系统命令行执行创建表的SQL脚本
osql -E -d tpch -i dss.ddl -o F:\soft\1\crttab.log
...

   2.数据加载

  SQL Server的大容量文本数据文件导入主要有2种方式,第一种在操作系统下,用bcp命令导入,这个命令的优点是支持各种文件格式,功能强大,缺点是比较复杂。第二种是在SQL Server交互界面中用Bulk insert语句导入,这个语句的语法简单,但支持的文件种类不如bcp多。两种方式都可以有效地减少日志的产生,达到高速导入的目的。

  下面分别举例说明这2种方式的导入。

  第1种,bulk insert 导入语句的语法结构是:

BULK   INSERT   表名    FROM   '文本文件全路径' WITH
(
FIELDTERMINATOR  
=   '列(字段)分隔符',
ROWTERMINATOR  
=   '行分隔符'
)

   Tpc-h的数据文件的列分隔符是竖线,包括最后一个数据列,行分隔符是回车换行,在Windows操作系统中用'\n'表示,因此,我们用下面的语句:

2> BULK   INSERT   dbo.[supplier]       FROM   'd:\soft\supplier.tbl' WITH
3> ( FIELDTERMINATOR   =   '|',
4> ROWTERMINATOR   =   '|\n'
5> )
6> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 15 ms.

SQL Server Execution Times:
CPU time
= 1266 ms,  elapsed time = 1328 ms.
(
100000 rows affected)

   10万行的导入时间大约是1.3秒。

  第2种,bcp命令的命令行格式是:

F:\soft\1>bcp /?
用法: bcp {dbtable
| query} {in | out | queryout | format} 数据文件
  
[-m 最大错误数]             [-f 格式化文件]       [-e 错误文件]
  
[-F 首行]                   [-L 末行]             [-b 批大小]
  
[-n 本机类型]               [-c 字符类型]         [-w 宽字符类型]
  
[-N 将非文本保持为本机类型] [-V 文件格式版本]     [-q 带引号的标识符]
  
[-C 代码页说明符]           [-t 字段终止符]       [-r 行终止符]
  
[-i 输入文件]               [-o 输出文件]         [-a 数据包大小]
  
[-S 服务器名称]             [-U 用户名]           [-P 密码]
  
[-T 可信连接]               [-v 版本]             [-R 允许使用区域设置]
  
[-k 保留 Null 值]           [-E 保留标识值]
  
[-h"加载提示"]              [-x 生成 xml 格式化文件]
  
[-d 数据库名称]

   对于我们的数据,如果像bulk insert命令一样只限制分隔符,bcp认为信息不够,需要人工确认每一列的格式,我们如果采用默认值是无法导入成功的。

D:\soft>bcp customer in customer.tbl -Usa -Pabcd1234  -t"|" -r"|\n"

Enter the
file storage type of field C_CUSTKEY [int]:
Enter prefix
-length of field C_CUSTKEY [0]:
Enter field terminator
[|]:

Enter the
file storage type of field C_NAME [char]:
Enter prefix
-length of field C_NAME [2]:
Enter field terminator
[|]:

。。。

Enter the
file storage type of field C_ACCTBAL [decimal]:
Enter prefix
-length of field C_ACCTBAL [1]:
Enter field terminator
[|]:

。。。

Enter the
file storage type of field C_COMMENT [char]:
Enter prefix
-length of field C_COMMENT [2]:
Enter field terminator
[|
]:

Do you want
to save this format information in a file? [Y/n] Y
Host filename
[bcp.fmt]: customer.fmt

Starting copy...
SQLState
= 22001, NativeError = 0
Error
= [Microsoft][SQL Server Native Client 11.0]String data, right truncation
。。。

   这是因为,bcp命令如果没有指定-c参数,那么把外部数据文件视为二进制格式,这样int类型是4个字节,date类型是7个字节等。这时即使把前缀长度统统改为0,虽然能够导入,数据也不正确。比如,外部文件中的0,导入数据库中就变成了48,刚好是字符'0'的ASCII码。要正确导入,我们需要手工修改刚才产生的fmt文件,将所有列都改为SQL_CHAR类型,如下文本所示。再用-f指定格式文件就可以了。而使用-c参数,就没有这么麻烦,只要指定分隔符即可。推荐使用-c方式导入。

D:\soft> type supplier.fmt
10.5
7
1 SQLCHAR 0 14 "|" 1 S_SUPPKEY ""
2 SQLCHAR 0 25 "|" 2 S_NAME Chinese_PRC_CI_AS
3 SQLCHAR 0 40 "|" 3 S_ADDRESS Chinese_PRC_CI_AS
4 SQLCHAR 0 14 "|" 4 S_NATIONKEY ""
5 SQLCHAR 0 15 "|" 5 S_PHONE Chinese_PRC_CI_AS
6 SQLCHAR 0 19 "|" 6 S_ACCTBAL ""
7 SQLCHAR 0 101 "|\r\n" 7 S_COMMENT Chinese_PRC_CI_AS

D:\soft
> bcp supplier in supplier.tbl -Usa -Pabcd1234 -f supplier.fmt

100000 rows copied.
Network packet size (bytes):
4096
Clock Time (ms.) Total     :
2469   Average : (40502.23 rows per sec.)

--把array size改大就快了,同时限制每批提交的数据行数
D:\soft> bcp supplier in supplier.tbl -Usa -Pabcd1234 -f supplier.fmt -a 40960 -b 1000000


100000 rows copied.
Network packet size (bytes):
32576
Clock Time (ms.) Total     :
843    Average : (118623.96 rows per sec.)

--使用-c参数,指定文件类型是文本格式
D:\soft>bcp part in part.tbl -Usa -Pabcd1234 -c -t"|" -r"|\n" -a 40960 -b 2000000

Starting copy...
2000000 rows sent to SQL Server. Total sent: 2000000

2000000 rows copied.
Network packet size (bytes):
32576
Clock Time (ms.) Total     :
20250  Average : (98765.43 rows per sec.)

--使用可信连接,并指定数据库为tpch,输出导入日志文件
bcp part in part.tbl -d tpch -T -c -t"|" -r"|\n" -a 40960 -b 2000000 -o F:\DF\bcpp.log

   我们看到使用系统默认的数组大小,bcp导入的速度比bulk insert 要慢一些,10万行的导入时间大约是2.5秒,但是指定一个较大的数组尺寸比如32K,导入速度就比bulk insert快了,10万行只要843毫秒。其他报表的导入情况类似。-b参数可以限制每批提交的行数,避免提交太频繁影响性能。

  8个表bcp导入的时间分别是13秒、685秒、129秒、67秒、19秒、1秒、0秒、0秒,总时间914秒。

  SQL Server 允许多个客户端“并行地大容量导入数据”到单个无索引的表中。这样可以提高大容量导入操作的性能。但必须要求存在多个外部文件,再人工地发起多个不同的命令,无法在单独一个命令中使用单独一个外部文件并行导入。

  3.测试数据和查询语句的产生步骤

  主要步骤参照本系列第一篇文章《Oracle 11g R2企业版评测》。然后需要针对SQL Server的特性作修改。

  SQL Server对SQL语句的特殊要求主要有4点:

  (1)取前若干行的语法。使用Top n写法。比如select top 1 * from t。

  (2)日期间隔的表达式。不支持date,interval 'n' year/month等SQL 92写法,要改为 DateAdd函数。比如:date '1998-12-01' - interval '73' day 要改为dateadd(day,-73, '1998-12-01')。其中,用字符串'1998-12-01'直接代表日期。

  (3)从日期提取年月日的表达式。不支持extract year from 等写法,要改为 DatePart函数或更简洁的Year函数。如:extract (year from o_orderdate )改为year(o_orderdate)

  (4)用go命令执行SQL语句,可以在最后用1个GO命令批量执行一组分号结尾的SQL语句。这些SQL语句既可以是查询语句,也可以是DDL语句和DML语句。

  将修改完成后的22个查询语句保存为mssql_tpch.sql文件。同时在文件开始处加入:

use tpch
go
set staticstics time on
go

   以限制查询的数据库,并显示SQL解析和运行时间。在文件末加上go命令。

  4.数据压缩测试

  表和索引压缩是SQL Server 2008起才具有的功能,有行压缩和页压缩2种选项。只有在Enterprise 版和 Developer 版中才能使用压缩。

  对表进行压缩有2种方法,一种是在创建表的时候指定压缩属性,再加载数据,加载数据又分为从外部文本导入和从数据库另一个表复制二种;另一种是对已经包含数据的非压缩表进行修改压缩属性的操作,然后重新整理表。另外SQL Server也提供了一个存储过程,可以预测一个表压缩后的大小,供人作决定前参考。

  预测压缩率的存储过程sp_estimate_data_compression_savings的语法为:

sp_estimate_data_compression_savings
      
[ @schema_name = ] 'schema_name'  
     ,
[ @object_name = ] 'object_name'
    ,
[@index_id = ] index_id
     ,
[@partition_number = ] partition_number
    ,
[@data_compression = ] 'data_compression'

   其中5个参数分别是模式名、对象名,索引的 ID、分区号,要评估的压缩的类型(NONE、ROW和PAGE),不存在的参数用NULL表示。

D:\soft>osql -Usa -Pabcd1234
1> use tpch;
2> go
1> set statistics time on
2> go
1> select * into tpch.dbo.l  from lineitem --从压缩表复制到非压缩表
2> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 2 ms.

SQL Server Execution Times:
CPU time
= 236109 ms,  elapsed time = 239178 ms.
(
59986052 rows affected)
1> sp_spaceused 'L'
2> go

name        rows     reserved           data               index_size         unused
-----   ----------  ------------------ ------------------ ------------------ ----------
l        59986052   8280224 KB         8280184 KB         32 KB              8 KB

1> set statistics time off
2> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 0 ms.
1> drop table l;
2> go
1> set statistics time on
2> go
1>
2> select * into tpch.dbo.l  from master.dbo.lineitem --从非压缩表复制到非压缩表
3> go
SQL Server parse
and compile time:
CPU time
= 8 ms, elapsed time = 8 ms.

SQL Server Execution Times:
CPU time
= 107609 ms,  elapsed time = 109807 ms.
(
59986052 rows affected)
1> set statistics time off
2> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 0 ms.
1> sp_spaceused 'L'
2> go

name        rows     reserved           data               index_size         unused
-----   ----------  ------------------ ------------------ ------------------ ----------
l        59986052   8280216 KB         8280184 KB         24 KB              8 KB

1> drop table L;
2> go
1> select * into tpch.dbo.l  from master.dbo.lineitem where 1=2; --复制表结构
2> go
(
0 rows affected)
1> sp_spaceused 'L'
2> go

name        rows     reserved           data               index_size         unused
-----   ----------  ------------------ ------------------ ------------------ ----------
l              0    80 KB              8 KB               16 KB              56 KB

1> alter table tpch.dbo.l rebuild WITH (DATA_COMPRESSION =page); --修改为页压缩属性
2> go
1> set statistics time on
2> go
1> insert into tpch.dbo.l with (tablock) select * from master.dbo.lineitem; --用insert with (tablock) 大批量插入压缩表
2> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 0 ms.

SQL Server Execution Times:
CPU time
= 479813 ms,  elapsed time = 480804 ms.
(
59986052 rows affected)
1> set statistics time off
2> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 0 ms.
1> sp_spaceused 'L'
2> go

name        rows     reserved           data               index_size         unused
-----   ----------  ------------------ ------------------ ------------------ ----------
l        59986052   4404056 KB         4403976 KB         24 KB              56 KB


1> truncate table L;
2> go
1> set statistics time on
2> go
1> BULK INSERT L FROM 'd:\soft\lineitem.tbl' WITH ( FIELDTERMINATOR = '|',ROWTERMINATOR = '|\n'); --用BULK insert大批量插入压缩表
2> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 17 ms.

SQL Server Execution Times:
CPU time
= 1141437 ms,  elapsed time = 1223991 ms.
(
59986052 rows affected)
1> set statistics time off
2> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 0 ms.
1> truncate table L;
2> go
1> alter table tpch.dbo.l rebuild WITH (DATA_COMPRESSION =none);
2> go
1> set statistics time on
2> go
1> BULK INSERT L FROM 'd:\soft\lineitem.tbl' WITH ( FIELDTERMINATOR = '|',ROWTERMINATOR = '|\n'); --用BULK insert大批量插入非压缩表
2> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 15 ms.

SQL Server Execution Times:
CPU time
= 1032969 ms,  elapsed time = 1130748 ms.
(
59986052 rows affected)
1> set statistics time off
2> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 0 ms.
1>  sp_spaceused 'L'
2>  go

name        rows     reserved           data               index_size         unused
-----   ----------  ------------------ ------------------ ------------------ ----------
l        59986052   8282152 KB         8280184 KB         40 KB              1928 KB


1> alter table tpch.dbo.l rebuild WITH (DATA_COMPRESSION =page); --用rebuild从非压缩表转为压缩

2> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 0 ms.

SQL Server Execution Times:
CPU time
= 475936 ms,  elapsed time = 60577 ms.
1> sp_spaceused 'L'
2> go
name        rows     reserved           data               index_size         unused
-----   ----------  ------------------ ------------------ ------------------ ----------
l        59986052   4404696 KB         4404392 KB         24 KB              280 KB

1> alter table tpch.dbo.l rebuild WITH (DATA_COMPRESSION =none);--用rebuild从压缩表转为非压缩
2> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 0 ms.

SQL Server Execution Times:
CPU time
= 367218 ms,  elapsed time = 52398 ms.

--用sp_estimate_data_compression_savings估计按行压缩后的表大小
1> sp_estimate_data_compression_savings 'DBO' ,'L',NULL,NULL,'ROW'
2> go
object_name
        schema_name
        partition_number size_with_current_compression_setting(KB) size_with_requested_compression_setting(KB)
        sample_size_with_current_compression_setting(KB) sample_size_with_requested_compression_setting(KB)
--------------------------------------------------
L
        DBO
                      
1                                   4406080                                     5450120
                                                  
41088                                              50824
2> set statistics time on
3> go
1> alter table tpch.dbo.l rebuild WITH (DATA_COMPRESSION =row)
2> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 3 ms.

SQL Server Execution Times:
CPU time
= 341969 ms,  elapsed time = 45206 ms.
1> set statistics time off
2> go
SQL Server parse
and compile time:
CPU time
= 0 ms, elapsed time = 0 ms.
1> sp_spaceused 'L'
2> go

name        rows     reserved           data               index_size         unused
-----   ----------  ------------------ ------------------ ------------------ ----------
l        59986052   5567656 KB         5567368 KB         40 KB              248 KB

   从测试结果可知,同样的LINEITEM表数据,在数据库中复制最快,而其中从非压缩表到非压缩表又比其他方式快,只要109秒,读压缩表写入非压缩表多了解压的操作,慢了100多秒,读非压缩表插入压缩表多了压缩的操作,又多了200多秒。如果用bulk insert用时大约1100多秒,插入非压缩表也比插入压缩表快100多秒,差距没有前面数据库内操作明显。对已有数据的表进行压缩或解压缩rebuild操作的速度都很快,从CPU时间和实际时间的比较可知,这个操作是并行处理的,因此比其他操作优势明显。用页方式压缩比行方式的压缩率高,但压缩的速度是行方式高,sp_estimate_data_compression_savings的抽样估计还是比较准确的。

  压缩对不同的表效果不同,下面是压缩前后TPC-H数据的对比。

  表1 各种压缩类型的大小比较,单位:KB

一、数据库安装

  表2 各种压缩类型的大小比较,单位:以原始未压缩大小为1的比率

一、数据库安装

  从上面表的占用空间可见,对于TPC-H数据,因为dbgen生成的数据比较随机,又是符合第3范式的,冗余较少,压缩的效果不太明显。最大压缩约为原来的1/2,压缩率较低,有些表如PARTSUPP,基本上没有被压缩。而且,如果向具有压缩属性的表导入数据,无论采用什么方式,导入时间都大大长于导入非压缩表,因此,像TPCH这样大量数据的导入,还是首先导入非压缩表,再用alter table rebuild命令压缩比较合适。

  聚集主键增加的空间非常少,这与它的存储和数据在一起有关,非聚集主键的存储和聚集主键有较大的区别,它是和数据表分离的,因此占用空间要超过聚集主键。

  5.数据查询

  由于SQL Server数据库默认采取并行查询的方式,因此不单独测试单进程查询,另一方面,SQL Server存在2种不同类型的索引:非聚集索引和聚集索引,这2种索引的结构有较大的差别,为了给出一个使用哪种索引更有利于分析查询的建议,专门分别进行测试。

  需要说明的是,SQL Server在创建主键后默认对主键列自动进行统计分析。其他列的统计分析可以用存储过程sp_createstats批量进行。原始tpch脚本dss.ri中创建外键的语法不符合SQL Server的格式要求,需要做相应的修改。为了便于便于在执行计划中辨认主键,对主键约束也采取命名方式。

F:\soft\1>osql -E -d tpch -i pkfk.sql -o F:\soft\1\crt_pkfk_comp.log
-- For table REGION
ALTER TABLE REGION ADD CONSTRAINT REGION_PK PRIMARY KEY (R_REGIONKEY);

-- For table NATION
ALTER TABLE NATION ADD CONSTRAINT NATION_PK PRIMARY KEY (N_NATIONKEY);

ALTER TABLE NATION ADD CONSTRAINT NATION_FK1 FOREIGN KEY (N_REGIONKEY) references REGION;


-- For table LINEITEM
ALTER TABLE LINEITEM ADD CONSTRAINT LINEITEM_FK1 FOREIGN KEY (L_ORDERKEY) references ORDERS;

ALTER TABLE LINEITEM ADD CONSTRAINT LINEITEM_FK2 FOREIGN KEY (L_PARTKEY,L_SUPPKEY) references PARTSUPP;

   为了比较不同条件下的查询结果,我们进行了7种组合的查询。分别是:无主键不压缩,聚集主键不压缩,非聚集主键不压缩,无主键行压缩,聚集主键行压缩,聚集主键页压缩,非聚集主键页压缩,每种测试都在更新统计信息sp_updatestats后执行2遍,取第2遍的结果。这样log文件中不包含SQL解释的时间,只留下查询耗费的时间。

  SQL Server不能在已有的聚集索引主键上进行更改转变为非聚集索引,必须删除后重新创建非聚集索引主键,方法是在PRIMARY KEY 后加关键字nonclustered。例如:

--删除已有的聚集主键
ALTER TABLE REGION DROP CONSTRAINT REGION_PK
go

--创建非聚集索引主键
ALTER TABLE REGION ADD CONSTRAINT REGION_PK PRIMARY KEY nonclustered (R_REGIONKEY);
go

   利用前面第一步产生的查询脚本,执行测试的命令行如下,查询结果输出到不同的log文件,以便于比较:注意在更改表存储和索引类型后,更新统计信息。

--创建统计分析
osql -E -d tpch -Q "sp_cretestats" -o stat.log
--原始
osql -E -i mssql_tpch.sql -o mssql_tpch_org.log
osql
-E -i mssql_tpch.sql -o mssql_tpch_org2.log
--聚集主键
--
创建主键外键
osql -E -i crt_pkrk_ci.sql
--更新统计分析
osql -E -d tpch -Q "sp_updatestats" -o stat.log
--查询
osql -E -i mssql_tpch.sql -o mssql_tpch_org_ci.log
--非聚集主键

osql
-E -i mssql_tpch.sql -o mssql_tpch_org_ni.log
--行压缩后

osql
-E -i mssql_tpch.sql -o mssql_tpch_rc.log
--行压缩后聚集主键

osql
-E -i mssql_tpch.sql -o mssql_tpch_rc_ci.log
--页压缩后聚集主键

osql
-E -i mssql_tpch.sql -o mssql_tpch_pc_ci.log
--页压缩后非聚集主键

osql
-E -i mssql_tpch.sql -o mssql_tpch_pc_ni.log

然后用find命令来得到各次运行的时间

find "CPU" mssql_tpch
*2.log

D:\soft
>find "CPU"tpch_os2.log

---------- F:\DF\TPCH_OS2.LOG
CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
CPU 时间
= 341390 毫秒,占用时间 = 5474 毫秒。
CPU 时间
= 16599 毫秒,占用时间 = 803 毫秒。
CPU 时间
= 52569 毫秒,占用时间 = 3066 毫秒。
CPU 时间
= 44187 毫秒,占用时间 = 2589 毫秒。
CPU 时间
= 58638 毫秒,占用时间 = 3781 毫秒。
CPU 时间
= 28173 毫秒,占用时间 = 1914 毫秒。
CPU 时间
= 56942 毫秒,占用时间 = 3199 毫秒。
CPU 时间
= 59749 毫秒,占用时间 = 3836 毫秒。
CPU 时间
= 296116 毫秒,占用时间 = 173939 毫秒。
CPU 时间
= 100656 毫秒,占用时间 = 4230 毫秒。
CPU 时间
= 18879 毫秒,占用时间 = 1127 毫秒。
CPU 时间
= 52138 毫秒,占用时间 = 2660 毫秒。
CPU 时间
= 246964 毫秒,占用时间 = 4120 毫秒。
CPU 时间
= 42353 毫秒,占用时间 = 2477 毫秒。
CPU 时间
= 39563 毫秒,占用时间 = 2577 毫秒。
CPU 时间
= 48124 毫秒,占用时间 = 1346 毫秒。
CPU 时间
= 762275 毫秒,占用时间 = 18992 毫秒。
CPU 时间
= 194923 毫秒,占用时间 = 7677 毫秒。
CPU 时间
= 86102 毫秒,占用时间 = 1565 毫秒。
CPU 时间
= 58629 毫秒,占用时间 = 2913 毫秒。
CPU 时间
= 122043 毫秒,占用时间 = 7432 毫秒。
CPU 时间
= 31362 毫秒,占用时间 = 1575 毫秒。

   从上述日志中可以看出,多次运行后SQL解析的时间为0,每个查询都有2个时间,一个是CPU时间,另一个是占用时间,前者约是后者的1倍或多倍,这说明SQL Server是在执行并行查询,因此实际运行时间大为缩短。

  下面是各组查询测试结果,取实际运行时间。

一、数据库安装
▲表1 TPC-H scale=10各种压缩和索引数据的测试对比,单位:毫秒

  从表1可知,如果没有主键约束,SQL Server仍能在大约20分钟运行完22个查询,这说明查询优化器的优化能力不错,而加上主键约束后,在表不压缩的情况下,大约2分钟就能执行完毕,非聚集主键和聚集主键的差距不大,结合前面给出的占用空间数据,自然是聚集主键更有优势。在压缩表的情况下,行压缩比页压缩的查询性能略好,对于同样的页压缩,非聚集主键比聚集主键的查询性能更好,接近非压缩表,这是因为非聚集主键不继承表的压缩属性,它本身是不压缩的,节省了解压的开销。

  令人惊异的,SQL Server压缩基本上发挥不了提高查询速度的作用,按理说减少了I/O物理读对整体查询应该有帮助的,但事实并非如此,可能跟SQL Server的压缩算法压缩比不高,但解压开销较大有关。

  从上述数据我们还可以得出以平均表现最好之一的聚集索引数据为基准的时间倍数。

一、数据库安装
▲表2 TPC-H scale=10各种压缩和索引数据的测试对比,单位:倍(以不压缩聚集索引为1)

  如上表所示,从合计时间看,聚集主键比不压缩最大提高了33倍,总体大约提高了9倍。非聚集主键的查询性能在不压缩表时和聚集主键总时间差异不大,但从单独的查询观察,大部分不如聚集主键,而个别却超过了聚集主键,而且提高幅度很大,比如第18个查询,看来SQL Server在处理这2种索引的方法上还是有区别的,具体差异原因要通过分析执行计划才能了解。

  6.性能调整和优化

  性能调整和优化,涵盖表结构设计、存储方式设计、查询设计、参数调整等方面,前文介绍的压缩和并行都是简单的参数调整手段,如果是实际的查询,而不是基准测试,我们就需要充分利用SQL Server数据库的功能,针对每个查询单独优化。

  (1)执行计划的查看

  要利用改写查询语句提高执行效率,首先要了解现有的执行计划。

  执行计划的查看,使用set showplan_text /showplan_all/showplan_xml开关,showplan_text显示比较简略的执行计划信息。另2个开关的使用参见文档。

  下面创建一个表,观察添加主键前后的执行计划。表扫描变成了聚集索引扫描。

1> create table t(c varchar(10) not null,c1 int,c2 varchar(10));
2> go
1> insert into t values(1,2,3);
2> insert into t values(2,21,31);
3> insert into t values(3,31,91);
4> go
(
1 行受影响)
(
1 行受影响)
(
1 行受影响)
1> set showplan_text on
2> go
1> select sum(c1) from t;
2> go
StmtText
-------------------------
select sum(c1) from t;

(
1 行受影响)
StmtText
-------------------------------------------------------------------------------------------
   |--Compute Scalar(DEFINE:([Expr1004]=CASE WHEN [Expr1005]=(0) THEN NULL ELSE [Expr1006] END))
        |--Stream Aggregate(DEFINE:([Expr1005]=COUNT_BIG([tpch].[dbo].[t].[c1]), [Expr1006]=SUM([tpch].[dbo].[t].[c1])))
             |--Table Scan(OBJECT:([tpch].[dbo].[t]))

(
3 行受影响)
1> set showplan_text off
2> go
1> alter table t add constraint t_pk  PRIMARY KEY(c);
2> go
1> set showplan_text on
2> go
1> select sum(c1) from t;
2> go
StmtText
-------------------------
select sum(c1) from t;

(
1 行受影响)
StmtText
--------------------------------------------------------------------------------------------
   |--Compute Scalar(DEFINE:([Expr1003]=CASE WHEN [Expr1004]=(0) THEN NULL ELSE [Expr1005] END))
        |--Stream Aggregate(DEFINE:([Expr1004]=COUNT_BIG([tpch].[dbo].[t].[c1]),
[Expr1005]=SUM([tpch].[dbo].[t].[c1])))
            
|--Clustered Index Scan(OBJECT:([tpch].[dbo].[t].[t_pk]))

(
3 行受影响)

   注意这个开关只是显示SQL语句的执行计划,并不实际执行该语句。另外,text显示的主要是访问路径,并不包含成本的估算值。

  (2) 统计信息收集和管理

  正确的统计信息对查询优化器得出较好的执行计划有十分重要的影响,在大量插入或更新数据以后,包括对表进行rebuild后,需要重新收集统计信息。前面已经使用了sp_createstats和sp_updatestats命令。下面再介绍另一个CREATE STATISTICS命令,可以收集指定的统计信息。语法如下:

CREATE STATISTICS <stats_name> ON <table_name> (column_list) WITH FULLSCAN

   比如,对上面第一步创建的t表的c列收集统计信息。

1> CREATE STATISTICS st_t_c on t(c) with fullscan;
2> go

   (3) 利用提示

  SQL Server也提供了人工添加提示来影响执行计划,这需要相当丰富的开发经验。

  提示主要分3种,联接提示、表提示和查询提示。

  联接提示:联接提示用于指定查询优化器在两个表之间强制执行联接策略。

<join_hint> ::=     { LOOP | HASH | MERGE | REMOTE }

   参数

  LOOP | HASH | MERGE

  指定查询中的联接应使用循环、哈希或合并。使用 LOOP |HASH | MERGE JOIN 在两个表之间强制执行特定联接。不能同时将 LOOP 与 RIGHT(或 FULL)指定为联接类型。

  REMOTE

  指定联接操作在右表处执行。这在左表是本地表而右表是远程表的情况下很有用。

  表提示:通过指定锁定方法、一个或多个索引、查询处理操作(如表扫描或索引查找)或其他选项,表提示可在数据操作语言 (DML) 语句执行期间覆盖查询优化器的默认行为。

WITH ( <table_hint> [ [ , ]...n ] )

<table_hint> ::=
[ NOEXPAND ] {
    
INDEX ( index_value [ ,...n ] ) | INDEX = ( index_value )
  
| FASTFIRSTROW
  
| FORCESEEK  --强制表扫描
  | HOLDLOCK
  
| NOLOCK
  
| NOWAIT
  
| PAGLOCK
  
| READCOMMITTED
  
| READCOMMITTEDLOCK
  
| READPAST
  
| READUNCOMMITTED
  
| REPEATABLEREAD
  
| ROWLOCK
  
| SERIALIZABLE
  
| TABLOCK   --锁定表
  | TABLOCKX
  
| UPDLOCK
  
| XLOCK
}

   查询提示:查询提示指定应在整个查询中使用指示的提示。查询提示影响到语句中的所有运算符。如果主查询中涉及 UNION,则只有涉及 UNION 运算符的最后一个查询才能有OPTION 子句。查询提示作为 OPTION 子句的一部分指定。如果一个或多个查询提示导致查询优化器不能生成有效计划,则引发 8622 错误。

<query_hint > ::=
{ { HASH
| ORDER } GROUP
  
| { CONCAT | HASH | MERGE } UNION
  
| { LOOP | MERGE | HASH } JOIN
  
| FAST number_rows
  
| FORCE ORDER
  
| MAXDOP number_of_processors
  
| OPTIMIZE FOR ( @variable_name { UNKNOWN | = literal_constant } [ , ...n ] )
  
| OPTIMIZE FOR UNKNOWN
  
| PARAMETERIZATION { SIMPLE | FORCED }
  
| RECOMPILE
  
| ROBUST PLAN
  
| KEEP PLAN
  
| KEEPFIXED PLAN
  
| EXPAND VIEWS
  
| MAXRECURSION number
  
| USE PLAN N'xml_plan'
  
| TABLE HINT ( exposed_object_name [ , <table_hint> [ [, ]...n ] ] )
}

   注意:由于 SQL Server 查询优化器通常会为查询选择非常好的执行计划,因此开发人员和数据库管理员只有在不得已时才可使用提示。

  下面是一个查询提示的例子。

  对上述t表删除聚集主键,改用非聚集索引以启用表扫描。

1> set showplan_text off
2> go
1> alter table t drop constraint t_pk;
2> go
1> alter table t add constraint t_pk  PRIMARY KEY nonclustered(c);
2> go
1> set showplan_text on
2> go
1> select * from t; --访问全部列则默认使用表扫描
2> go
StmtText
-------------------
select * from t;

(
1 行受影响)
StmtText
-------------------------------------------
   |--Table Scan(OBJECT:([tpch].[dbo].[t]))

(
1 行受影响)
1> select c from t; --访问主键列则默认使用索引扫描
2> go
StmtText
-------------------
select c from t;

(
1 行受影响)
StmtText
--------------------------------------------------
   |--Index Scan(OBJECT:([tpch].[dbo].[t].[t_pk]))

(
1 行受影响)
1> select c from t WITH (FORCESEEK); --访问主键列强制使用表扫描失败
2> go
消息
8622,级别 16,状态 1,服务器 WIN-NF10S6S7KTC,第 1
由于此查询中定义了提示,查询处理器未能生成查询计划。请重新提交查询,并且不要在查询中指定任何提示,也不要使用
SET FORCEPLAN。
1> select c from t with (index(t_pk)); --访问主键列强制使用索引扫描,这和默认选择一致
2> go
StmtText
--------------------------------------
select c from t with (index(t_pk));

(
1 行受影响)
StmtText
--------------------------------------------------
   |--Index Scan(OBJECT:([tpch].[dbo].[t].[t_pk]))

(
1 行受影响)
1> select * from t with (index(t_pk)); --访问全部列强制使用索引扫描,执行计划变化了
2> go
StmtText
--------------------------------------
select * from t with (index(t_pk));

(
1 行受影响)
StmtText
------------------------------------------------------------------------------------------
   |--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000]))
        |--Index Scan(OBJECT:([tpch].[dbo].[t].[t_pk]))
        |--RID Lookup(OBJECT:([tpch].[dbo].[t]), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD)

(
3 行受影响)
1>

   还有其他的优化手段,比如添加必要的索引,由于时间所限,没有进行测试。从对SQL Server的表添加主键的效果来看,应该也是很有效的。有兴趣的读者可以自行检验。SQL Server还有很多功能,如分析函数、分区、递归with查询,已经超出了tpc-h测试的范围,也留给读者测试。

  四、小结

  经过这次测试,我们对SQL Server数据库已经有了初步的印象,虽然安装比较复杂,安装包也体积庞大,但功能还是很强大,总体性能也比较好,比如数据文件导入和查询,在基本上相同的硬件配置下,性能在行存储数据库测试中目前仅次于Oracle,排名第2。其次,对于TPC-H这种分析型查询,不需要利用主外键约束也能产生较好的执行计划,能充分利用硬件资源,默认并行查询对查询性能提高较明显,对某些查询有数倍的提高。另外,测试过程中没有出现数据库意外崩溃的现象。

  存在的问题,首先,操作系统平台单一,也许是微软自家的产品,和操作系统结合得太过紧密,这可能对提高性能和利用操作系统扩展功能有帮助,但也限制了跨平台的发展,自动放弃了Linux和Unix平台的大量用户。其次,安装包的制作有问题,先决条件检查全满足,在一个新安装的Windows 2008 R2系统上安装也不容易一次成功,给出的错误信息不明确,log日志文件中也缺少明确信息,无法根据它们修正错误,至少应该在安装指南中指出杀毒软件的影响。

  还有一些功能上的问题。

  当你添加了主键,也就默认添加了一个聚集索引,而且自动进行了统计分析。即使在SQL语句中用提示全表扫描,也不能成功。只能让优化器自己决定,哪怕新的执行计划不如原来的。

  set statistics time计时工具,如果执行存储过程,会把过程中间每一个查询的时间都列出来,如果要单独显示一行,只能用编写存储过程的办法解决。

  执行计划的文本显示在查询比较复杂时不易辨认,如一行内容被拆分为多行。似乎也不能同时显示执行结果和执行计划。不过在Management Studio中就可以同时显示。

  最后提一下文档和支持,微软很重视文档的本地化工作,这点很有利于技术人员熟悉他们的产品,在官方技术网站http://msdn.microsoft.com/也有大量SQL Server中文技术文章和入门教材,这些都是很好的学习资源。

  总的来说,MS SQL Server是一个功能全面,性能优良,运行稳定的主流商用数据库。而对最终用户来说,本文没有提及的图形化的管理工具也是一个很好的工具,不必手工输入和记忆大量的管理SQL语句,就能监控数据库运行和进行日常维护工作。要真正用好SQL Server,就必须熟悉图形化工具的使用,会大幅度提高数据库开发和管理的效率。

0
相关文章