存档

2010年2月 的存档,文章数:12

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 #

在sybase表上建立聚集索引可以提高键列的检索速度。这是索引的主要功能所在。

可是,聚集索引对于统计表上的行数count(*)有没有改善呢? 答案是否定的。

请看我下面的测试代码!

建立一张临时表test3

1
create table test3(id int not null,name varchar(30) null)

向表中插入测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
insert into test3
select 1,'liu'
go
insert into test3
select 2,'zhang'
go
insert into test3
select 3,'wang'
go
insert into test3
select 4,'li'
go
循环插入
insert into test4
select count(*)+id,name from test4
go 18
1> select count(*) from  test4
2> go
-----------
524288
(1 row affected)
循环插入了524288条记录!

打开查询计划和统计查询计划时间的选项

1
2
3
4
set showplan on
go
set statistics time on
go

表上没有加任何索引的情况下。

select count(*) from test4 的查询计划为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1> select count(*) from test4
2> go
QUERY PLAN FOR STATEMENT 1 (at line 1).
STEP 1
The type of query is SELECT.
2 operator(s) under root
|ROOT:EMIT Operator (VA = 2)
|
|   |SCALAR AGGREGATE Operator (VA = 1)
|   |  Evaluate Ungrouped COUNT AGGREGATE.
|   |
|   |   |SCAN Operator (VA = 0)
|   |   |  FROM TABLE
|   |   |  test4
|   |   |  Table Scan.
|   |   |  Forward Scan.
|   |   |  Positioning at start of table.
|   |   |  Using I/O Size 32 Kbytes for data pages.
|   |   |  With MRU Buffer Replacement Strategy for data pages.
Parse and Compile Time 0.
Adaptive Server cpu time: 0 ms.
-----------
524288
Execution Time 1.
Adaptive Server cpu time: 100 ms.  Adaptive Server elapsed time: 156 ms.
(1 row affected)

select count(1) from test4 的查询计划为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1> select count(1) from test4
2> go
QUERY PLAN FOR STATEMENT 1 (at line 1).
STEP 1
The type of query is SELECT.
2 operator(s) under root
|ROOT:EMIT Operator (VA = 2)
|
|   |SCALAR AGGREGATE Operator (VA = 1)
|   |  Evaluate Ungrouped COUNT AGGREGATE.
|   |
|   |   |SCAN Operator (VA = 0)
|   |   |  FROM TABLE
|   |   |  test4
|   |   |  Table Scan.
|   |   |  Forward Scan.
|   |   |  Positioning at start of table.
|   |   |  Using I/O Size 32 Kbytes for data pages.
|   |   |  With MRU Buffer Replacement Strategy for data pages.
Parse and Compile Time 0.
Adaptive Server cpu time: 0 ms.
-----------
524288
Execution Time 1.
Adaptive Server cpu time: 100 ms.  Adaptive Server elapsed time: 220 ms.

可以看出,count(*) 和count(1) 的执行计划是相同的。都执行了表扫描。

由于表上没有任何索引可供使用,select count(id) 和 select count(name) 都是执行了表扫描。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
1> select count(id) from test4
2> go
QUERY PLAN FOR STATEMENT 1 (at line 1).
STEP 1
The type of query is SELECT.
2 operator(s) under root
|ROOT:EMIT Operator (VA = 2)
|
|   |SCALAR AGGREGATE Operator (VA = 1)
|   |  Evaluate Ungrouped COUNT AGGREGATE.
|   |
|   |   |SCAN Operator (VA = 0)
|   |   |  FROM TABLE
|   |   |  test4
|   |   |  Table Scan.
|   |   |  Forward Scan.
|   |   |  Positioning at start of table.
|   |   |  Using I/O Size 32 Kbytes for data pages.
|   |   |  With MRU Buffer Replacement Strategy for data pages.
Parse and Compile Time 0.
Adaptive Server cpu time: 0 ms.
-----------
524288
Execution Time 1.
Adaptive Server cpu time: 100 ms.  Adaptive Server elapsed time: 140 ms.
(1 row affected)
1> select count(name) from test4
2> go
QUERY PLAN FOR STATEMENT 1 (at line 1).
STEP 1
The type of query is SELECT.
2 operator(s) under root
|ROOT:EMIT Operator (VA = 2)
|
|   |SCALAR AGGREGATE Operator (VA = 1)
|   |  Evaluate Ungrouped COUNT AGGREGATE.
|   |
|   |   |SCAN Operator (VA = 0)
|   |   |  FROM TABLE
|   |   |  test4
|   |   |  Table Scan.
|   |   |  Forward Scan.
|   |   |  Positioning at start of table.
|   |   |  Using I/O Size 32 Kbytes for data pages.
|   |   |  With MRU Buffer Replacement Strategy for data pages.
Parse and Compile Time 0.
Adaptive Server cpu time: 0 ms.
-----------
524288
Execution Time 1.
Adaptive Server cpu time: 100 ms.  Adaptive Server elapsed time: 236 ms.
(1 row affected)
1>

下面考虑加入主键(聚集索引)pk_test4_id

1
2
alter table test4 add constraint pk_test4_id primary key (id)
go

再次执行select count(*) from test4 和 select count(1) from test4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
1> select count(*) from test4
2> go
QUERY PLAN FOR STATEMENT 1 (at line 1).
STEP 1
The type of query is SELECT.
2 operator(s) under root
|ROOT:EMIT Operator (VA = 2)
|
|   |SCALAR AGGREGATE Operator (VA = 1)
|   |  Evaluate Ungrouped COUNT AGGREGATE.
|   |
|   |   |SCAN Operator (VA = 0)
|   |   |  FROM TABLE
|   |   |  test4
|   |   |  Table Scan.
|   |   |  Forward Scan.
|   |   |  Positioning at start of table.
|   |   |  Using I/O Size 32 Kbytes for data pages.
|   |   |  With MRU Buffer Replacement Strategy for data pages.
Parse and Compile Time 0.
Adaptive Server cpu time: 0 ms.
-----------
524288
Execution Time 2.
Adaptive Server cpu time: 200 ms.  Adaptive Server elapsed time: 736 ms.
(1 row affected)
1> select count(1) from test4
2> go
QUERY PLAN FOR STATEMENT 1 (at line 1).
STEP 1
The type of query is SELECT.
2 operator(s) under root
|ROOT:EMIT Operator (VA = 2)
|
|   |SCALAR AGGREGATE Operator (VA = 1)
|   |  Evaluate Ungrouped COUNT AGGREGATE.
|   |
|   |   |SCAN Operator (VA = 0)
|   |   |  FROM TABLE
|   |   |  test4
|   |   |  Table Scan.
|   |   |  Forward Scan.
|   |   |  Positioning at start of table.
|   |   |  Using I/O Size 32 Kbytes for data pages.
|   |   |  With MRU Buffer Replacement Strategy for data pages.
Parse and Compile Time 0.
Adaptive Server cpu time: 0 ms.
-----------
524288
Execution Time 0.
Adaptive Server cpu time: 0 ms.  Adaptive Server elapsed time: 500 ms.
(1 row affected)
1>

由上可以看出,聚集索引对于select count(*) 几乎没有扫描影响。堆表和聚集索引表上的count是没有什么区别的,甚至于聚集索引表上的IO还要多2(这是因为多了两个聚集索引的数据块造成的)。其 实聚集索引并没有单独的保留所有索引列的信息,而只是将表中的行的物理顺序按照聚集索引列的顺序整理了一下,因此对聚集索 引的扫描和对堆表的扫描是一样的,没有什么本质上的区别。

添加id列上的非聚集索引idx_test4_id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
1> create index idx_test4_id on test4(id)
2> go
1> sp_help test4
2> go
Name  Owner Object_type Create_date
----- ----- ----------- -------------------
test4 dbo   user table  Feb 25 2010  3:44PM
(1 row affected)
Column_name Type    Length Prec Scale Nulls Default_name Rule_name
Access_Rule_name Computed_Column_object Identity
----------- ------- ------ ---- ----- ----- ------------ ---------
---------------- ---------------------- ----------
id          int          4 NULL  NULL     0 NULL         NULL
NULL             NULL                            0
name        varchar     30 NULL  NULL     1 NULL         NULL
NULL             NULL                            0
Object has the following indexes
index_name   index_keys index_description index_max_rows_per_page
index_fillfactor index_reservepagegap index_created       index_local
------------ ---------- ----------------- -----------------------
---------------- -------------------- ------------------- ------------
pk_test4_id   id        clustered, unique                       0
0                    0 Feb 25 2010  4:04PM Global Index
idx_test4_id  id        nonclustered                            0
0                    0 Feb 25 2010  4:52PM Global Index
(2 rows affected)
index_ptn_name          index_ptn_seg
----------------------- -------------
pk_test4_id_1399673003  default
idx_test4_id_1399673003 default
(2 rows affected)
No defined keys for this object.
name  type       partition_type partitions partition_keys
----- ---------- -------------- ---------- --------------
test4 base table roundrobin              1 NULL
(1 row affected)
partition_name   partition_id pages row_count segment create_date
---------------- ------------ ----- --------- ------- -------------------
test4_1399673003   1399673003  2132    524288 default Feb 25 2010  4:04PM
Partition_Conditions
--------------------
NULL
Avg_pages   Max_pages   Min_pages   Ratio(Max/Avg)
Ratio(Min/Avg)
----------- ----------- ----------- ---------------------------
---------------------------
2132        2132        2132                    1.000000
1.000000
Lock scheme Allpages
The attribute 'exp_row_size' is not applicable to tables with allpages lock
scheme.
The attribute 'concurrency_opt_threshold' is not applicable to tables with
allpages lock scheme.
exp_row_size reservepagegap fillfactor max_rows_per_page identity_gap
ascinserts
------------ -------------- ---------- ----------------- ------------
-----------
0              0          0                 0            0
0
(1 row affected)
concurrency_opt_threshold optimistic_index_lock dealloc_first_txtpg
------------------------- --------------------- -------------------
0                     0                   0
(return status = 0)

此时再次执行select count(*) 和select count(1)。查询计划如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
1> select count(*) from test4
2> go
QUERY PLAN FOR STATEMENT 1 (at line 1).
STEP 1
The type of query is SELECT.
2 operator(s) under root
|ROOT:EMIT Operator (VA = 2)
|
|   |SCALAR AGGREGATE Operator (VA = 1)
|   |  Evaluate Ungrouped COUNT AGGREGATE.
|   |
|   |   |SCAN Operator (VA = 0)
|   |   |  FROM TABLE
|   |   |  test4
|   |   |  Index : idx_test4_id
|   |   |  Forward Scan.
|   |   |  Positioning at index start.
|   |   |  Index contains all needed columns. Base table will not be
read.
|   |   |  Using I/O Size 32 Kbytes for index leaf pages.
|   |   |  With MRU Buffer Replacement Strategy for index leaf pages.
Parse and Compile Time 0.
Adaptive Server cpu time: 0 ms.
-----------
524288
Execution Time 0.
Adaptive Server cpu time: 0 ms.  Adaptive Server elapsed time: 703 ms.
(1 row affected)
1> select count(1) from test4
2> go
QUERY PLAN FOR STATEMENT 1 (at line 1).
STEP 1
The type of query is SELECT.
2 operator(s) under root
|ROOT:EMIT Operator (VA = 2)
|
|   |SCALAR AGGREGATE Operator (VA = 1)
|   |  Evaluate Ungrouped COUNT AGGREGATE.
|   |
|   |   |SCAN Operator (VA = 0)
|   |   |  FROM TABLE
|   |   |  test4
|   |   |  Index : idx_test4_id
|   |   |  Forward Scan.
|   |   |  Positioning at index start.
|   |   |  Index contains all needed columns. Base table will not be
read.
|   |   |  Using I/O Size 32 Kbytes for index leaf pages.
|   |   |  With MRU Buffer Replacement Strategy for index leaf pages.
Parse and Compile Time 0.
Adaptive Server cpu time: 0 ms.
-----------
524288
Execution Time 1.
Adaptive Server cpu time: 100 ms.  Adaptive Server elapsed time: 93 ms.
(1 row affected)

可以看出查询引擎使用了非聚集索引idx_test4_id ,执行时间明显减少。因为计算行数这个操作对于全表扫描或是非聚集索引的扫描结果是一样的,而相对来说非聚集索引的数据量是肯定会比表的数据量小很多的,同样的做一次全部扫描所花费的IO也就要少很多了。

select count(id) 也是利用了非聚集索引 idx_test4_id。

结论:

count(*)和count(1)执行的效率是完全一样的。
如果是对特定的列做count的话建立这个列的非聚集索引能对count有很大的帮助。

————————————————————————————————————
——— 本文为andkylee个人原创,请在尊重作者劳动成果的前提下进行转载;
——— 转载务必注明原始出处 : http://www.dbainfo.net
——— 关键字: sybase 聚簇索引 非聚簇索引 查询计划 行数  count
————————————————————————————————————

随着当今数据库的容量越来越快的朝着在大型数据库或超大型数据库的发展,对于数据库中的大型表以及具有各种访问模式的表的可伸缩性和可管理性运行环境变得 尤为重要,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的专栏

SQL Server安装出错问题解决汇总

先把SQL Server卸载,再把安装时产生的“Microsoft SQL Server”文件夹删掉,在运行注册表,把HKEY_CURRENT_USER\\Software\\Microsoft\\Microsoft SQL Server,和HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SQL Server全部删掉,(注意要把Microsoft SQL Server文件夹整个删掉),然后重起。
再装就应该没有问题了!

Win2k 装不上 SQL Server 2000 问题很常见.
// (1)配置服务器时中断.
// (2)注册 ActiveX 时中断.
// (3)显示到100%的时候中断.
当然重新安装 Windows 2000 完全可以安装 SQL Server 2000.

安全起见:
1>先备份您的注册表.
2>备份您的数据[进不了SQL Server 2000,
可以备份 Program Files\\Microsoft SQL Server\\MSSQL\\Data 文件夹的文件.]

您必须知道:
Windows 2000 Server 可以安装SQL Server 2000的任何版本.
Windows 2000 Professional 仅可以安装SQL Server 2000的个人
版.

有两种办法:
1:先卸载您的 SQL Server 2000,必要的时候删除 Program
Files\\Microsoft SQL Server 文件夹.
2:放入 SQL Server 2000 光盘.
3:在"开始"--"运行"键入 "F:\\x86\\setup.exe k=dbg" (F是光盘)
[此命令的意思单步运行安装 SQL Server 2000]
4:98%安装不成功,没有任何的提示.可能是:
(1)配置服务器时中断.
(2)注册 ActiveX 时中断.
(3)显示到100%的时候中断.

这样一来,您只能使用下面的办法了!否则,您只有 FORMAT了!
1 打开注册表
在"开始"--"运行"键入 "regedit"
2 按下列顺序点击打开
+ HKEY_LOCAL_MACHINE
+ SOFTWART
+ Microsoft
+ Windows
+ CurrentVersion
+ Setup
+ ExceptionComponents
3 将 ExceptionComponents 下面的文件夹全部删除!
如 {60BFF50D-FB2C-4498-A577-C9548C390BB9}
{60BFF50D-FB2C-4498-A577-C9548C390BB9}
{60BFF50D-FB2C-4498-A577-C9548C390BB9}
{60BFF50D-FB2C-4498-A577-C9548C390BB9}
.......

4 重新启动:
5 重新安装 SQL Server 2000
=======================================================

以前的某个程序安装已在安装计算机上创建挂起的文件操作。运行安装之前,必须重新启动计算机

a、重启机器,再进行安装,如果发现还有该错误,请按下面步骤
b、在开始->运行中输入regedit
c、到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager 位置
d、选择文件->倒出,保存
e、在右边窗口右击PendingFileRenameOperations,选择删除,然后确认
f、重启安装,问题解决

如果还有同样问题,请检查其它注册表中是否有该值存在,如有请删掉。

=========================================

修改注册表,找到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager项,把ObjectDirectories的值清空即可。

1、先停止服务,退出,再到控制面板正常卸载;

2、删除安装路径下的所有目录文件夹,包括注册表,如果担心删除不到位,可以用查找命令,将所有*SQL*的逐一查出,然后再一一确认删除;(如果安装SQL时,不是按默认路径安装,除了删除安装路径下的所有目录文件夹外,主系统盘里的程序组里的相应文件夹也要删除)

3、删除的时候还应考虑到C:\Program Files\Common Files\Microsoft Shared\web server extensions\40\bin\FPMMC.MSC;

4、管理工具中,将SQLSERVERAGENT服务停止服务;此服务程序存放在D:\Program Files\Microsoft SQL Server\MSSQL\Binn\sqlagent.exe -i MSSQLSERVER

5、将C:\WINDOWS目录下的sql.mif、sqlsp.log、sqlstp.log、sqlsrv32.rll、sqlwid.dll、 sqlwoa.dll、dbmsqlgc.dll、SQLSRV32.dll、sqlclnt.rsp、sqlunirl.dll、 SQLSERVR.EXE-18F260D8.pf、SQLMANGR.EXE-0150BA62.pf等清除;(偶机器SQL是打过SP4的)

6、删除时还应将C:\Program Files\Common Files\Microsoft Shared\SQL Debugging\sqldbreg2.exe,sqldbreg.exe
,sqldbg.dll也清除掉;

7、如果安装不成功,记得要将C:\WINDOWS\Temp\SQLSETUP\BIN\SCM.EXE一并清除;

8、卸载干净后,如果怕再次安装出错,那就建议按默认路径安装;

9、SQL在安装过程中如果出错,系统会锁定文件,当然,卸载程序也会,但可以通过修改注册表,就不用重新启动,再尝试安装了。(建议装完SQL后还是重新启动一下机器,就避免修改注册表了)
避免重启,注册表修改:(1)运行REGEDIT;(2)注册表中选择HKEY_LOCAL_MACHINE\SYSTEM \CurrentControlSet\Control\Session Manager,将光标定位于此;(3)选择文件->导出,再次保存(安全起见);(4)在右边窗中右击PendingFile Rename Operations,选择删除,最后确认;

10、如果安装不成功,还可以通过C:\WINDOWS\sqlstp.log查看安装不成功的原因,这个是最利于解决问题的了;

11、同时需要将C:\Documents and Settings\XXX\Local Settings\Temp\所有关于*SQL*.*全部清除;例如将 sqlsp.out,SQLSERVR.DBD,sqlmangr.exe.mdmp,sqlmangr.exe.hdmp清除即可。

安装SQL Server 遇到错误提示:

以前的某个程序安装已在安装计算机上创建挂起的文件操作。运行安装程序之前必须重新启动计算机!。
打开注册表编辑器,在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager
中找到 PendingFileRenameOperations 项目,并删除它。这样就可以清除安装暂挂项目。

注意:不用重起电脑,继续安装即可!

提示“文件挂起”,SQL Server 无法安装

安装SQL Server 2000个人版用作开发,可是每次安装都提示“以前的某个程序安装已在安装计算计上创建挂起的文件操作。运行安装程序之前必须重新启动计算机。”重新启动了计算机之后,问题却丝毫没有解决,依然提示这样的话。

解决方法:

先把SQL Server卸载,再把安装时产生的“Microsoft SQL Server”文件夹删掉,在运行注册表,把HKEY_CURRENT_USER\\Software\\Microsoft\\Microsoft SQL Server,和HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SQL Server全部删掉,(注意要把Microsoft SQL Server文件夹整个删掉),然后重起。
再装就应该没有问题了!

Win2k 装不上 SQL Server 2000 问题很常见.
// (1)配置服务器时中断.
// (2)注册 ActiveX 时中断.
// (3)显示到100%的时候中断.
当然重新安装 Windows 2000 完全可以安装 SQL Server 2000.

安全起见:
1>先备份您的注册表.
2>备份您的数据[进不了SQL Server 2000,可以备份 Program
Files\\Microsoft SQL Server\\MSSQL\\Data 文件夹的文件.]

您必须知道:
Windows 2000 Server 可以安装SQL Server 2000的任何版本.
Windows 2000 Professional 仅可以安装SQL Server 2000的个人
版.

有两种办法:
1:先卸载您的 SQL Server 2000,必要的时候删除 Program
Files\\Microsoft SQL Server 文件夹.
2:放入 SQL Server 2000 光盘.
3:在"开始"--"运行"键入 "F:\\x86\\setup.exe k=dbg" (F是光盘)
[此命令的意思单步运行安装 SQL Server 2000]
4:98%安装不成功,没有任何的提示.可能是:
(1)配置服务器时中断.
(2)注册 ActiveX 时中断.
(3)显示到100%的时候中断.

这样一来,您只能使用下面的办法了!否则,您只有 FORMAT了!
1 打开注册表
在"开始"--"运行"键入 "regedit"
2 按下列顺序点击打开
+ HKEY_LOCAL_MACHINE
+ SOFTWART
+ Microsoft
+ Windows
+ CurrentVersion
+ Setup
+ ExceptionComponents
3 将 ExceptionComponents 下面的文件夹全部删除!
如 {60BFF50D-FB2C-4498-A577-C9548C390BB9}
{60BFF50D-FB2C-4498-A577-C9548C390BB9}
{60BFF50D-FB2C-4498-A577-C9548C390BB9}
{60BFF50D-FB2C-4498-A577-C9548C390BB9}
.......

4 重新启动:
5 重新安装 SQL Server 2000

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

转自:http://yk216.blog.51cto.com/252445/119131

SQL Server的补丁版本检查不如Windows 补丁版本检查直接,一个系统管理员,如果不了解SQL Server版本对应的补丁号,可能也会遇到一点麻烦,因此在这说明一下,通过这样的办法判别机器是安全的办法,不会对系统产生任何影响。
 
1、用Isql或者SQL查询分析器登录到SQL Server,如果是用Isql,请在cmd窗口输入isql -U sa,然后输入密码,进入;如果是用SQL查询分析器,请从程序中启动,输入sa和密码(也可以用windows验证)。
 
2、在ISQL中输入:
Select @@Version;
go
或者SQL查询分析器中输入(其实如果不想输入,只要打开帮助的关于就可以了:))
Select @@Version;
然后按执行;
这时会返回SQL的版本信息,如下:
Microsoft SQL Server 2000 - 8.00.760 (Intel X86) Dec 17 2002 14:22:05 Copyright (c) 1988-2003 Microsoft Corporation Enterprise Edition on Windows NT 5.0 (Build 2195: Service Pack 3)
其中的8.00.760就是SQL Server的版本和补丁号。对应关系如下:
8.00.194 -——————SQL Server 2000 RTM
8.00.384 -——————(SP1)
8.00.534 -——————(SP2)
8.00.760 -——————(SP3)

8.00.2039---------------------(sp4)
这样我们就能看到SQL Server的正确版本和补丁号了。

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

转自:http://yk216.blog.51cto.com/252445/119130