Inside T-SQL Index (2)

Inside T-SQL Index(2)

一、前言

T-SQL索引原理级别的知识不熟悉的朋友可以先看看http://www.cnblogs.com/bhtfg538/archive/2009/08/31/1557225.html,那么本文主要在第一篇文章讲述的知识基础上进一步学习有关IndexDDL语句.所谓信理论+实践,得永生.

二、创建索引

记得我刚开始学数据库的时候,一直以为主键就是聚集索引,就会在ID上建立聚集索引,后来才发现自己的无知,只不过是MSSQL会在创建逐渐的时候,默认创建聚集索引在主键上.我们来看看几种创建主键以及索引的方式:

USE tempdb;

GO

 

IF OBJECT_ID('dbo.T') IS NOT NULL

    DROP TABLE dbo.T

GO

 

--第一种方式,默认为逐渐的话,自动创建了聚集索引

CREATE TABLE dbo.T

(

TID INT IDENTITY(1,1) PRIMARY KEY,

Col1 NVARCHAR(20)  

);

 

--查看dbo.T的聚集索引,上面的索引是随机取的名字

SELECT A.[name],A.type_desc FROM sys.indexes A INNER JOIN sys.tables B ON A.object_id=B.object_id WHERE B.[name]=N'T'

 

 

IF OBJECT_ID('dbo.T') IS NOT NULL

    DROP TABLE dbo.T

GO

 

--第一种方式,默认为逐渐的话,自动创建了聚集索引,彼且我们强制他的聚集索引名

CREATE TABLE dbo.T

(

TID INT IDENTITY(1,1) CONSTRAINT [PK_dbo.T_TID] PRIMARY KEY,

Col1 NVARCHAR(20)  

);

 

--查看dbo.T的聚集索引,上面的索引是我们自定义的名字

SELECT A.[name],A.type_desc FROM sys.indexes A INNER JOIN sys.tables B ON A.object_id=B.object_id WHERE B.[name]=N'T'

 

 

--其实主键不一定是聚集的

IF OBJECT_ID('dbo.T') IS NOT NULL

    DROP TABLE dbo.T

GO

 

--第一种方式,默认为逐渐的话,自动创建了聚集索引,彼且我们强制他的聚集索引名,因为主键要求其唯一行,所以是唯一索引

CREATE TABLE dbo.T

(

TID INT IDENTITY(1,1) CONSTRAINT [PK_dbo.T_TID] PRIMARY KEY NONCLUSTERED,

Col1 NVARCHAR(20)  

);

 

SELECT A.[name],A.type_desc FROM sys.indexes A INNER JOIN sys.tables B ON A.object_id=B.object_id WHERE B.[name]=N'T'

 

 

IF OBJECT_ID('dbo.T') IS NOT NULL

    DROP TABLE dbo.T

GO

 

--建立非聚集索引

CREATE TABLE dbo.T

(

TID INT IDENTITY(1,1)PRIMARY KEY,

Col1 NVARCHAR(20)  

);

 

CREATE INDEX IX_T_Col1 ON dbo.T(Col1)

 

SELECT A.[name],A.type_desc FROM sys.indexes A INNER JOIN sys.tables B ON A.object_id=B.object_id WHERE B.[name]=N'T'

 

 

IF OBJECT_ID('dbo.T') IS NOT NULL

    DROP TABLE dbo.T

GO

 

--建立唯一非聚集索引

CREATE TABLE dbo.T

(

TID INT IDENTITY(1,1)PRIMARY KEY,

Col1 NVARCHAR(20)  

);

 

CREATE UNIQUE INDEX IX_T_Col1 ON dbo.T(Col1)

 

SELECT A.[name],A.type_desc FROM sys.indexes A INNER JOIN sys.tables B ON A.object_id=B.object_id WHERE B.[name]=N'T'

 

INSERT INTO dbo.T SELECT '1'

INSERT INTO dbo.T SELECT '1'

 

SELECT * FROM dbo.T

 

IF OBJECT_ID('dbo.T') IS NOT NULL

    DROP TABLE dbo.T

GO

 

--建立唯一非聚集索引,忽略重复键

CREATE TABLE dbo.T

(

TID INT IDENTITY(1,1)PRIMARY KEY,

Col1 NVARCHAR(20)  

);

CREATE UNIQUE INDEX IX_T_Col1 ON dbo.T(Col1) WITH(IGNORE_DUP_KEY=ON--/OFF,自己测试了

)

 

INSERT INTO dbo.T SELECT '1'

INSERT INTO dbo.T SELECT '1'

 

SELECT * FROM dbo.T

 

 

IF OBJECT_ID('dbo.T') IS NOT NULL

    DROP TABLE dbo.T

GO

 

--建立唯一非聚集索引,如果存在当前索引,就先删除了

CREATE TABLE dbo.T

(

TID INT IDENTITY(1,1)PRIMARY KEY,

Col1 NVARCHAR(20)  

);

CREATE UNIQUE INDEX IX_T_Col1 ON dbo.T(Col1) WITH(DROP_EXISTING=ON)

CREATE UNIQUE INDEX IX_T_Col1 ON dbo.T(Col1) WITH(DROP_EXISTING=ON)

 

SELECT A.[name],A.type_desc FROM sys.indexes A INNER JOIN sys.tables B ON A.object_id=B.object_id WHERE B.[name]=N'T'

 

--建立覆盖索引,包含一个非键列Col1,至于覆盖索引的作用,可以看第一篇文章啦

CREATE TABLE dbo.T

(

TID INT IDENTITY(1,1)PRIMARY KEY,

Col1 NVARCHAR(MAX) 

);

CREATE NONCLUSTERED INDEX IX_T_TID ON dbo.T(TID) INCLUDE(Col1)

 

SELECT A.[name],A.type_desc FROM sys.indexes A INNER JOIN sys.tables B ON A.object_id=B.object_id WHERE B.[name]=N'T'

 

至于索引的一些其他选项,大家可以看看:

ms-help://MS.MSDNQTR.v90.chs/udb9/html/7969af33-e94c-41f7-ab89-9d9a2747cd5c.htm

顺带说一句,如果您使用的是企业版本的数据库,可以搜索ONLINE这个关键词在INDEX中的使用,其实ONLINE选项就是为了在对索引的一系列操作中的一个并发访问的选项,如果是企业库还是选择ON.对操作索引时候带来的锁定数据就不用那么痛苦了.

三、优化索引

正如我们知道的Win操作系统,会出现文件碎片之类的东西,当出现非连续行的文件碎片以后,那么访问数据的时候可能磁头老人家都走几步,自然老人家到达目的的的时间也就大大的增长了,必要的情况下我们会移动一些碎片,重写组织碎片,甚至可能重新创建文件,让他们物理上连续起来.

同样的,索引使用久了,由于每时每刻的CRUD,所以OLTP,索引碎片也是影响索引速率的原因之一吧.

下面的例子告诉我们如何去查询指定表的索引碎片,关键是访问sys.dm_db_index_physical_stats

 

USE AdventureWorks;

GO

SELECT a.index_id, name, avg_fragmentation_in_percent

FROM sys.dm_db_index_physical_stats (DB_ID(), OBJECT_ID(N'Production.Product'),

     NULL, NULL, NULL) AS a

    JOIN sys.indexes AS b ON a.object_id = b.object_id AND a.index_id = b.index_id;

GO

说明

avg_fragmentation_in_percent

逻辑碎片(索引中的无序页)的百分比。

fragment_count

索引中的碎片(物理上连续的叶页)数量。

avg_fragment_size_in_pages

索引中一个碎片的平均页数。

 

知道碎片程度后,可以使用下表确定修复碎片的最佳方法。

avg_fragmentation_in_percent

修复语句

> 5% < = 30%

ALTER INDEX REORGANIZE

> 30%

ALTER INDEX REBUILD WITH (ONLINE = ON)*

上面的红色部分就是企业版本的MSSQL 2005才支持的.

因此我随便按照上面的Production.Product做了一个优化数据库中表的索引的例子:

--我这里是模仿一个表,大家也可以通过sys.tables 的所有表来优化索引啦,放两个游标就O,我这里不是通用的,大家根据自己的实际情况,操作吧.呵呵,对了ALTER INDEXONLINE 选项只支持企业版本的哦

 

USE AdventureWorks;

GO

--哥哥些,先看看自己的版本哈.不是企业的去掉WITH(ONLINE=ON)的选项

SELECT @@VERSION

 

DECLARE @SQL NVARCHAR(MAX),@IndexName NVARCHAR(200),@Percent FLOAT(16);

SET @SQL=N'';

 

DECLARE Cur CURSOR FAST_FORWARD FOR

SELECT [name],avg_fragmentation_in_percent FROM sys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('Production.Product'),NULL,NULL,NULL) AS A JOIN sys.indexes AS B ON A.index_id=B.index_id AND A.object_id=B.object_id;

 

OPEN Cur;

FETCH NEXT FROM Cur INTO @IndexName,@Percent

IF @Percent >= 30

    SET @SQL=N'ALTER INDEX '+@IndexName+N' ON Production.Product REBUILD WITH(ONLINE=ON);'

ELSE IF @Percent BETWEEN 5 AND 30

    SET @SQL=N'ALTER INDEX '+@IndexName+N' ON Production.Product REORGANIZE;'

ELSE

    SET @SQL=@SQL+N''

 

WHILE(@@FETCH_STATUS = 0)

BEGIN

    FETCH NEXT FROM Cur INTO @IndexName,@Percent

    IF @Percent >= 30

    SET @SQL=@SQL+N'ALTER INDEX '+@IndexName+N' ON Production.Product REBUILD WITH(ONLINE=ON);'

ELSE IF @Percent BETWEEN 5 AND 30

    SET @SQL=@SQL+N'ALTER INDEX '+@IndexName+N' ON Production.Product REORGANIZE;'

ELSE

    SET @SQL=@SQL+N''

END

 

CLOSE Cur;

DEALLOCATE Cur;

 

EXEC(@SQL)

填充因子是啥呢,我小的时候,一直搜百度,听过很多答案也有很多误解,有说提高性能啊等等,这里就随便说说:

提供填充因子选项是为了优化索引数据存储和性能。当创建或重新生成索引时,填充因子值可确定每个叶级页上要填充数据的空间百分比(*),以便保留一定百分比的可用空间供以后扩展索引。

*:叶级别在B树结构中,是最底层的节点了,如果你看了上一篇文章应该知道.在聚集索引中叶级别的节点指向数据行,在非聚集索引中叶级别的节点指向的是聚集索引的键引用或者是堆的RID.所以当我们新添加数据库的时候,自然对B数要在底层扩展节点访问数据.

MSDN:如果向已满的索引页添加新行,数据库引擎将把大约一半的行移到新页中,以便为该新行腾出空间。这种重组称为页拆分。页拆分可为新记录腾出空间,但是执行页拆分可能需要花费一定的时间,此操作会消耗大量资源。此外,它还可能造成碎片,从而导致 I/O 操作增加。正确选择填充因子值可提供足够的空间以便随着向基础表中添加数据而扩展索引,从而减少页拆分可能性。(*)

*:这里有点不明白,数据库为什么会把新页的数据行移动,我觉得应该是创建新页,然后填充后来的数据,如果存在前页数据的B树节点中,应该在新页的页头防止插入节点信息的申明之类的.具体不知道为什么这样做了,等待高人分析

尽管采用较低的填充因子值(非 0)可减少随着索引增长而拆分页的需求,但是索引将需要更多的存储空间,并且会降低读取性能。即使对于面向许多插入和更新操作的应用程序,数据库读取次数一般也超过数据库写入次数的 5 10 倍。因此,指定一个不同于默认值的填充因子会降低数据库的读取性能,而降低量与填充因子设置的值成反比。例如,当填充因子的值为 50 时,数据库的读取性能会降低两倍。读取性能降低是因为索引包含较多的页,因此增加了检索数据所需的磁盘 I/O 操作。

*:这里也迷糊呢,如果是设置0的话,索引自动增长吗?当数据库超过一个8KB页的时候呢?难道不创建新页,还是将数据向我们之前说的分页方式吗?还是?无论如何数据上来了,8kb页不满足的时候,是要自动创建页的,我不明白0填充因素的时候的方式是怎么样的?当然可以肯定的是页越多那么IO操作自然就多了.也许0填充因素将索引数据,仍到OVERFLOW_DATA也不一定呢,呵呵高人快来

 

今天就到这里吧,最近很忙下次把后续补上分区索引这快吧,大家多去MSDN学习啦.呵呵.然后再实战.

 PS:/Files/bhtfg538/1.txt
/Files/bhtfg538/2.txt

posted @ 2009-09-07 16:33  Sai~  阅读(...)  评论(...编辑  收藏