SQLSERVER数据表的记录重排物理优化方案

 该方案产生于2005年,当时我接手了某产业库的设计.我们在开发这个产品时,在数据库中引入了100万条记录作为开发版数据库.开发过程非常顺利,前台程序的测试速度也非常好.但我们在后来将当前的5亿条数据库追加到开发库进行测试时,发现无论什么情况,都无法从数据库中取出记录,发现在读取数据库时,磁盘灯一直在亮,而性能性能监视器里的CPU使用率很底.此时的SQLSERVER服务器会呈的假死状态.
   后来我想我们的数据库为优化而预留的空间被填满后,数据会被追加到数据库文件的最后页.会不会是因为这个原因,导至了磁盘的磁头寻道,读取的效率下降?而造成了一个高I/O的假象?基于这个想法,我再次到查询分析器中运行了前台取值存储过程,然后用WIDNOWS的性能监视器观察,发现数据读取的值真的是很低.
   基于这个原因,所以,我认为如果将数据库的表按聚集索引的方向重排一次再回写,一定会大大减少磁盘的寻道,读取时间.从而大大提高数据库的读取效率.下面的过程就是基于这个原因而写的.
   首先说明,下面这个存储过程具有一定的危险性,而且,我们已经对以年度为序,纵向分表.对年度数据表,名称都是有规则的,如这里的是 MC_CP_DATA_####(四位的年).在使用这个过程时,一定要结合你当前的实际情况.

--------------------------------------
-- 数据表的数据重组物理优化方案.
--                 MSTOP 2005/5/2
--------------------------------------

-----------------------------------------------
--表优化记录.
-----------------------------------------------

--分段截取函数
IF EXISTS(SELECT [NAME] FROM SYSOBJECTS WHERE [NAME]='Fun_SplitStr') BEGIN
 DROP FUNCTION Fun_SplitStr
END
GO

CREATE FUNCTION DBO.Fun_SplitStr(
 @S VARCHAR(8000),      --包含多个数据项的字符串
 @POS INT,              --要获取的数据项的位置
 @SPLIT VARCHAR(10)     --数据分隔符
)RETURNS VARCHAR(512)
AS
BEGIN
 IF @S IS NULL RETURN(NULL)
 DECLARE @SPLITLEN INT
 SELECT @SPLITLEN=LEN(@SPLIT+'A')-2
 WHILE @POS>1 AND CHARINDEX(@SPLIT,@S+@SPLIT)>0
  SELECT @POS=@POS-1,
   @S=STUFF(@S,1,CHARINDEX(@SPLIT,@S+@SPLIT)+@SPLITLEN,'')
 RETURN  LEFT(@S,CHARINDEX(@SPLIT,@S+@SPLIT)-1)
END

GO
-----------------------------------------
-- 文件路径.
-----------------------------------------
IF EXISTS(SELECT [NAME] FROM SYSOBJECTS WHERE [NAME]='Prc_FilePath') BEGIN
 DROP PROC Prc_FilePath
END

GO

CREATE Proc Prc_FilePath(
 @Var_Path VarChar(4000),
 @Var_String VarChar(4000) OutPut
)  As Begin
 Declare @Tab_Tmp Table (Int_ID Int,Var_Str VarChar(128) )
 Declare @Var_S1 Varchar(4000)
 Declare @Int_ID Int

 SET @Var_Path=REPLACE(@Var_Path,'\\','\')
 SET @Int_ID=1
 SET @Var_S1=dbo.FUN_SplitStr(@Var_Path,@Int_ID,'\')
 WHILE LEN(@Var_S1)>0 BEGIN
  INSERT INTO @Tab_Tmp VALUES(@Int_ID,@Var_S1)
  SET @Int_ID=@Int_ID+1
  SET @Var_S1=dbo.FUN_SplitStr(@Var_Path,@Int_ID,'\')
 END
 DELETE FROM @Tab_Tmp WHERE Int_ID = (SELECT MAX(Int_ID) FROM @Tab_Tmp)
 SET @Var_String=''
 SELECT @Var_String=@Var_String + Var_Str + '\' From @Tab_Tmp Order By Int_ID
 IF RIGHT(@Var_String,1)<>'\' AND LEN(@Var_String)>0 BEGIN
  SET @Var_String=@Var_String + '\'
 END
End

GO

--------------------------------------
--记录需优化表的表信息.
--------------------------------------

IF EXISTS(SELECT [NAME] FROM SYSOBJECTS WHERE [NAME]='SY_Table_Optimize') BEGIN
 DROP TABLE SY_Table_Optimize
END

GO

CREATE TABLE SY_Table_Optimize (
 NVR_DBNAME  NVARCHAR(128)  NOT NULL,
 NVR_TABLENAME  NVARCHAR(128)  NOT NULL,
 NVR_TABLEGROUPNAME NVARCHAR(128)  NOT NULL,
 NVR_TABLEGROUPPATH NVARCHAR(256)  NULL,
 INT_UpRowCount  INT   NULL,
 DAT_UpDate  DATETIME  Null
CONSTRAINT [PK_SY_Table_Optimize] PRIMARY KEY  CLUSTERED ([NVR_DBNAME],[NVR_TABLENAME]))

GO

-----------------------------------------------------------------------------------
-- 初始化 SY_Table_Optimize  ,
-- 组名和文件名必须是同名.如:文件名是 ABC.NDF ,则组名是: ABC ,并且,一个文件一个组一个表.
-----------------------------------------------------------------------------------
INSERT INTO SY_Table_Optimize
SELECT DB_NAME(),A.[NAME],C.groupname,D.[FILENAME],B.[ROWS],GETDATE()
FROM SYSOBJECTS AS A inner join SYSINDEXES AS B 
ON  A.ID=B.ID AND A.XTYPE='U' AND B.INDID<2 AND A.[NAME] LIKE 'MC_CP_DATA%'
INNER JOIN sysfilegroups AS C ON B.GROUPID=C.GROUPID
INNER JOIN sysfiles AS D ON C.GROUPID=D.GROUPID
 
GO

---------------------------------
-- 物理优化所有的表.
-- 在作业里加一个作业:
-- EXECUTE dt_optimize_all_table
---------------------------------
IF EXISTS(SELECT [NAME] FROM SYSOBJECTS WHERE [NAME]='dt_optimize_all_table') BEGIN
 DROP PROC dt_optimize_all_table
END

GO

CREATE PROC dt_optimize_all_table(
 @INT_VALVE INT=50000  --数据库优化阀值.
) WITH ENCRYPTION AS BEGIN

 DECLARE @NVR_DBNAME  NVARCHAR(64) --数据库名.
 DECLARE @NVR_DBPATH  NVARCHAR(256) --数据库所在路径.
 DECLARE @INT_ROWCOUNT  INT  --当前表的总行数.
 DECLARE @INT_NEWROWCOUNT INT
 DECLARE @NVR_TABLENAME  NVARCHAR(256) --表名.
 DECLARE @NVR_OLEGROUPNAME NVARCHAR(256)
 DECLARE @NVR_NEWGOUPNAME NVARCHAR(256)
 DECLARE @NVR_CMD  NVARCHAR(4000)
 DECLARE @INT_ROW  INT
 DECLARE @NVR_TABLEGROUPPATH NVARCHAR(4000)

 SET @NVR_DBNAME=DB_NAME();
 SELECT @INT_ROW=MAX(ABS(T2.[ROWS]-T1.INT_UpRowCount)) FROM
  ( 
   SELECT NVR_TABLENAME,INT_UpRowCount
   FROM SY_Table_Optimize WHERE NVR_DBNAME=@NVR_DBNAME
  ) AS T1 INNER JOIN
  (
   SELECT A.[NAME],B.[ROWS]
   FROM SYSOBJECTS AS A,SYSINDEXES AS B 
   WHERE A.ID=B.ID AND A.XTYPE='U' AND B.INDID<2
  ) AS T2 ON T1.NVR_TABLENAME=T2.[NAME]

 IF @INT_ROW>=@INT_VALVE BEGIN
  --------------------------------------------
  --先清理一次日志.因为处理要需要大量的磁盘空间.
  --------------------------------------------
  SET @NVR_CMD='DUMP TRANSACTION ' + @NVR_DBNAME +  ' WITH NO_LOG'
  EXECUTE(@NVR_CMD)
 
  SET @NVR_CMD='DBCC SHRINKFILE(2, 0)'
  EXECUTE(@NVR_CMD)
 
  --断开所有相关连接.要一个个断开.
  DECLARE @INT_SPID INT
  SET @NVR_CMD=''
  SELECT @INT_SPID=MIN(SPID) FROM MASTER.DBO.SYSPROCESSES WHERE DBID=DB_ID() AND SPID<>@@spid
  SET @INT_SPID=ISNULL(@INT_SPID,-1)
  WHILE @INT_SPID>0 BEGIN
   SET @NVR_CMD=N' KILL ' + RTRIM(@INT_SPID) + ';' 
   EXECUTE SP_EXECUTESQL @NVR_CMD
   SELECT @INT_SPID=MIN(SPID) FROM MASTER.DBO.SYSPROCESSES WHERE DBID=DB_ID() AND SPID>@INT_SPID  AND SPID<>@@spid
   SET @INT_SPID=ISNULL(@INT_SPID,-1)
  END
  -----------------------------------------

  SELECT @NVR_TABLENAME=MIN(NVR_TABLENAME) FROM SY_Table_Optimize WHERE NVR_DBNAME=@NVR_DBNAME;
  WHILE LEN(@NVR_TABLENAME)>0 BEGIN
  
   -----------------------------------------------------

   SELECT  @NVR_OLEGROUPNAME=NVR_TABLEGROUPNAME,
    @INT_ROWCOUNT=INT_UpRowCount,
    @NVR_TABLEGROUPPATH=REPLACE(NVR_TABLEGROUPPATH,'\\','\')
   FROM SY_Table_Optimize  WHERE NVR_DBNAME=@NVR_DBNAME AND  NVR_TABLENAME=@NVR_TABLENAME;

    --路径要从系统表来.
   SELECT  @NVR_TABLEGROUPPATH=REPLACE(D.[FILENAME],'\\','\') 
   FROM SYSOBJECTS AS A inner join SYSINDEXES AS B 
   ON  A.ID=B.ID AND A.XTYPE='U' AND B.INDID<2 AND A.[NAME]=@NVR_TABLENAME
   INNER JOIN sysfilegroups AS C ON B.GROUPID=C.GROUPID
   INNER JOIN sysfiles AS D ON C.GROUPID=D.GROUPID

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

   EXECUTE Prc_FilePath @NVR_TABLEGROUPPATH,@NVR_DBPATH OUTPUT

   SELECT @INT_NEWROWCOUNT=B.[ROWS]
   FROM SYSOBJECTS AS A,SYSINDEXES AS B 
   WHERE A.ID=B.ID AND A.XTYPE='U' AND B.INDID<2 AND A.[NAME]=@NVR_TABLENAME;
 
   --如果当前行数改变大于某个值,则优化.
   IF ABS(@INT_NEWROWCOUNT-@INT_ROWCOUNT)>=@INT_VALVE BEGIN
    EXECUTE dt_optimize_table @NVR_DBNAME,@NVR_DBPATH,@NVR_TABLENAME,@NVR_OLEGROUPNAME,@NVR_NEWGOUPNAME OUTPUT;
    --更新优化记录.
    UPDATE SY_Table_Optimize SET NVR_TABLEGROUPNAME=@NVR_NEWGOUPNAME,INT_UpRowCount=@INT_NEWROWCOUNT,DAT_UpDate=GETDATE() WHERE NVR_DBNAME=@NVR_DBNAME AND  NVR_TABLENAME=@NVR_TABLENAME;
   END
   SELECT @NVR_TABLENAME=MIN([NVR_TABLENAME]) FROM SY_Table_Optimize WHERE  NVR_DBNAME=@NVR_DBNAME AND NVR_TABLENAME>@NVR_TABLENAME;
   SET @NVR_TABLENAME=ISNULL(@NVR_TABLENAME,'');
  END
 END

 --查看,是否有新的表..
 INSERT INTO SY_Table_Optimize
 SELECT @NVR_DBNAME,A.[NAME],C.groupname,D.[FILENAME],B.[ROWS],GETDATE()
 FROM SYSOBJECTS AS A inner join SYSINDEXES AS B 
 ON  A.[ID]=B.[ID] AND A.XTYPE='U' AND B.INDID<2 AND A.[NAME] LIKE 'MC_CP_DATA%' AND A.[NAME] NOT IN ( SELECT NVR_TABLENAME FROM SY_Table_Optimize )
 INNER JOIN sysfilegroups AS C ON B.GROUPID=C.GROUPID
 INNER JOIN sysfiles AS D ON C.GROUPID=D.GROUPID

END

GO

---------------------------------
-- 对指定的表进行物理优化.
---------------------------------
IF EXISTS(SELECT [NAME] FROM SYSOBJECTS WHERE [NAME]='dt_optimize_table') BEGIN
 DROP PROC dt_optimize_table
END

GO

CREATE PROC dt_optimize_table (
 @NVR_DBNAME  NVARCHAR(64),  --数据库名.
 @NVR_DBPATH  NVARCHAR(256), --数据库所在路径.
 @NVR_TABLENAME  NVARCHAR(256), --表名.
 @NVR_OLEGROUPNAME NVARCHAR(256), --上一个文件组的名称.
 @NVR_NEWGOUPNAME NVARCHAR(256) OUTPUT --新的组文件名.
)  WITH ENCRYPTION  AS BEGIN
 
 DECLARE @NVR_NEWID  NVARCHAR(16)  --新的表文件编号.
 DECLARE @NVR_CMD  NVARCHAR(4000)--命令
 DECLARE @NVR_TMPTABLENAME NVARCHAR(256) --暂时表名
 DECLARE @INT_TRANSACTION INT

 SET @NVR_NEWID= LEFT(REPLACE(NEWID(),'-',''),16)
 SET @NVR_TMPTABLENAME=@NVR_TABLENAME + '_' + @NVR_NEWID
 SET @NVR_NEWGOUPNAME=@NVR_TMPTABLENAME

 --添加一个文件组.
 SET @NVR_CMD='ALTER DATABASE [' + @NVR_DBNAME + '] ADD FILEGROUP [' + @NVR_TMPTABLENAME + ']'
 EXECUTE(@NVR_CMD)

 --向文件组中添加一个文件
 SET @NVR_CMD='
  ALTER DATABASE [' + @NVR_DBNAME + '] ADD FILE(
   NAME = N''' + @NVR_TMPTABLENAME + ''',
   FILENAME = N''' + @NVR_DBPATH + @NVR_TMPTABLENAME + '.NDF'' ,
   SIZE = 3,
   FILEGROWTH = 10%)
  TO FILEGROUP [' +@NVR_TMPTABLENAME + ']'
 EXECUTE(@NVR_CMD)

 --------------------------------------------
 --事务段
 --------------------------------------------
 SET @INT_TRANSACTION=1
 BEGIN TRANSACTION
 --------------------------------------------
 --在该文件中添加表. **这里要手工修改**.
 --------------------------------------------
 SET @NVR_CMD='
CREATE TABLE [' + @NVR_TMPTABLENAME + '] (
 [VAR_GOODSID] [varchar](8) NOT NULL ,
 [VAR_AREAID] [varchar](8) NOT NULL ,
 [BIG_DATE] [int]  NOT NULL ,
 [INT_INDEXID] [int]  NOT NULL ,
 [FLO_VALUE] [float]  NULL ,
 CONSTRAINT [PK_' + @NVR_TMPTABLENAME + '] PRIMARY KEY 
 (
  [VAR_GOODSID],
  [VAR_AREAID],
  [BIG_DATE],
  [INT_INDEXID]
 )  ON [' + @NVR_TMPTABLENAME + ']
) ON [' + @NVR_TMPTABLENAME + ']'
   EXECUTE(@NVR_CMD)

 IF @@ERROR<>0 BEGIN
  SET @INT_TRANSACTION=-1
  GOTO RollTRANSACTION
 END

 --将数据移到新的表.
 SET @NVR_CMD='INSERT INTO ' + @NVR_TMPTABLENAME
  + ' SELECT * FROM ' + @NVR_TABLENAME +  ' ORDER BY VAR_GOODSID,VAR_AREAID,BIG_DATE,INT_INDEXID'
 EXECUTE(@NVR_CMD)

--  --在表上建立一个日期的索引.
--  SET @NVR_CMD='CREATE INDEX IX_' + @NVR_TMPTABLENAME + '_DATE ON ' + @NVR_TMPTABLENAME + ' (BIG_DATE)'
--  EXECUTE(@NVR_CMD)

 IF @@ERROR<>0 BEGIN
  SET @INT_TRANSACTION=-1
  GOTO RollTRANSACTION
 END

 --删除原表.
 SET @NVR_CMD='DROP TABLE ' + @NVR_TABLENAME
 EXECUTE(@NVR_CMD)

 IF @@ERROR<>0 BEGIN
  SET @INT_TRANSACTION=-1
  GOTO RollTRANSACTION
 END
 --提交事务.
 COMMIT TRANSACTION
 SET @INT_TRANSACTION=0
 --------------------------------------------
 --事务段
 --------------------------------------------

 --将表名改为原来表名.
 SET @NVR_CMD='exec sp_rename ''' + @NVR_TMPTABLENAME + ''', ''' + @NVR_TABLENAME + ''''
 EXECUTE(@NVR_CMD)

 --回滚事务.
RollTRANSACTION:
 IF @INT_TRANSACTION=-1 BEGIN
  ROLLBACK TRANSACTION
 END ELSE BEGIN
  --删除原来的表文件.
  SET @NVR_CMD='
  ALTER DATABASE [' + @NVR_DBNAME + ']
  REMOVE FILE ' + @NVR_OLEGROUPNAME
  EXECUTE(@NVR_CMD)

  SET @NVR_CMD='
  ALTER DATABASE [' + @NVR_DBNAME + ']
  REMOVE FILEGROUP ' + @NVR_OLEGROUPNAME
  EXECUTE(@NVR_CMD)
 END

 --------------------------------------------
 --清理日志
 --------------------------------------------
 SET @NVR_CMD='DUMP TRANSACTION ' + @NVR_DBNAME + ' WITH NO_LOG'
 EXECUTE(@NVR_CMD)
 
 SET @NVR_CMD='DBCC SHRINKFILE(2, 0)'
 EXECUTE(@NVR_CMD)

END


GO

原文地址:https://www.cnblogs.com/xx_cs/p/1998948.html