[原创] 数据表的物理优化方案.

华芸智森 2008-05-22 06:06:18
面对一个总量过亿的数据库,如何优化?虽然优化了主键,建立了索引,优化了查询,可是,它为什么还是那么慢呢? 更恶劣的情况是,在月结,转帐,统计汇总时,它总是超时.
这的确是让很多人头痛的事.更多人提出的解决方案是:换硬件吧,换硬件真的那么有效吗?就算快一倍,从60秒变成了30秒,你的用户一样是无法忍受.
两三年前,我也遇到过这个问题,也被这个问题拆磨了好几天,后来,在优化数据库的过程中.我发现,对大型超大型的数据库来说,软优化是远远不够的.就算换硬件,好象效果也不

怎么好.在经过一系列动作之后,你算是用尽了全身解数了.速度还没有质的改变,抓狂吗? 我为这个问题抓狂过.
哈哈,说了一通废话.开始我今天的真正目的:
经过了必要的软优化,在同样的硬件条件下,如何让你的数据库快起来.下面的过程,就是实现这个目的,对你的表进行物理优化.
过程不长,但收到的效果可以说是立竿见影的,如果你尝试过一切都无效的话,那我建议你试试它.
优化的原理很简单,打个比方吧,数据库中的记录,类似一个结构体,一个数据表,类似一个链表.主键,虽然是B树,本质也是一个指针.无论你对数据库怎样操作,最后增加的记录都

是记录在数据库文件的未尾.唯一改变的,就是记录的指针.虽然,数据文件设置了主键,但它在磁盘上的实体却是无序的.当查找某条记录时,它只能按指针的指向去跳,这要磁盘去移

动,去寻道.我们的效率就消耗在这里.我们要做的,就是重新让数据实体按主键的方向排序.让磁头能迅速地找到我们所要的数据. :)
下面基本是这个解决方案的全部,也是从实质的库中修改过来的.当然,由于每个人的习惯不一样,可能有些出入,但总体的原理,还是一样的.修改一下,即可使用.
使用方法,建立一个作业,每周的周六晚上执行 EXECUTE dt_optimize_all_table 即可.过程 dt_optimize_all_table 有一个阀值,如果某表的改变的记录超过了某个值(默认

是5万),那它就执行整理.所以,如果你如果是按日期来表的话,你的历史表是不会被整理的.再详细的情况就不说了,过程里的说明都有.

注:这个过程其实是有点危险的,在正式应用前,一定要经过几次的测试.另,如果你将本过程用于你的生产中,所产生的任何问题,与我无关.

--------------------------------------
--数据表的物理优化方案.
-- MSTOP
--------------------------------------

-----------------------------------------------
--表优化记录.
-----------------------------------------------
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 'BL_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

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_DBPATH=LEFT(NVR_TABLEGROUPPATH, CHARINDEX('\' + NVR_TABLEGROUPNAME ,NVR_TABLEGROUPPATH)-1 ) ---这里要特别留意
FROM SY_Table_Optimize WHERE NVR_DBNAME=@NVR_DBNAME AND NVR_TABLENAME=@NVR_TABLENAME;

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
END

GO
...全文
1059 109 打赏 收藏 转发到动态 举报
写回复
用AI写文章
109 条回复
切换为时间正序
请发表友善的回复…
发表回复
hiho 2012-10-04
  • 打赏
  • 举报
回复
想问一下, 对于主键上使用order by, 对性能的影响有多大?
superdiablo 2009-12-25
  • 打赏
  • 举报
回复
马克
friendlyFour 2008-06-02
  • 打赏
  • 举报
回复
up
prcgolf 2008-05-28
  • 打赏
  • 举报
回复
up
bigrock 2008-05-28
  • 打赏
  • 举报
回复
mark...
只爱浓茶 2008-05-28
  • 打赏
  • 举报
回复
mark
卖水果的net 2008-05-28
  • 打赏
  • 举报
回复
我自认为SQL Server 的水平不错,但是,LZ这个贴子没的看明白!
华芸智森 2008-05-26
  • 打赏
  • 举报
回复
[Quote=引用 97 楼 Herb2 的回复:]
下面的看不懂

SQL codeSET @NVR_CMD='
CREATE TABLE [' + @NVR_TMPTABLENAME + '] (
[BIG_DATAAUTOID] [int] NOT NULL ,
[BIG_AREAAUTOID] [int] NOT NULL ,
[BIG_EnterTypeAutoID] [int] NOT NULL ,
[VAR_DATE] [varchar] (9) COLLATE Chinese_PRC_CI_AS NOT NULL ,
[FLO_VALUE] [float] NULL ,
CONSTRAINT [PK_' + @NVR_TMPTABLENAME + '] PRIMARY KEY
(
[BIG_DATAAUTOID],
[BIG_AREAAUTOID],
[BIG…
[/Quote]
与你原表对应的建表语句.当然,你可以改为直接从系统取建表语句.然后 REPLACE 相关的字符.
华芸智森 2008-05-26
  • 打赏
  • 举报
回复
[Quote=引用 85 楼 Herb2 的回复:]
-- --在表上建立一个日期的索引.如果有索引的话.在此添加.
-- SET @NVR_CMD='CREATE INDEX IX_' + @NVR_TMPTABLENAME + '_DATE ON ' + @NVR_TMPTABLENAME + ' (BIG_DATE)'
-- EXECUTE(@NVR_CMD)

--将数据移到新的表.
SET @NVR_CMD='INSERT INTO ' + @NVR_TMPTABLENAME + ' SELECT * FROM ' + @NVR_TABLENAME --在此视你实质情况而定,可加入主键一至的 ORDER BY
EXECUTE(@NVR_CMD)
----------…
[/Quote]
在39楼我已作了更正.
Herb2 2008-05-26
  • 打赏
  • 举报
回复
下面的看不懂
SET @NVR_CMD=' 
CREATE TABLE [' + @NVR_TMPTABLENAME + '] (
[BIG_DATAAUTOID] [int] NOT NULL ,
[BIG_AREAAUTOID] [int] NOT NULL ,
[BIG_EnterTypeAutoID] [int] NOT NULL ,
[VAR_DATE] [varchar] (9) COLLATE Chinese_PRC_CI_AS NOT NULL ,
[FLO_VALUE] [float] NULL ,
CONSTRAINT [PK_' + @NVR_TMPTABLENAME + '] PRIMARY KEY
(
[BIG_DATAAUTOID],
[BIG_AREAAUTOID],
[BIG_EnterTypeAutoID],
[VAR_DATE]
) ON [' + @NVR_TMPTABLENAME + ']
) ON [' +@NVR_TMPTABLENAME + ']'
EXECUTE(@NVR_CMD)

xjjx1911 2008-05-26
  • 打赏
  • 举报
回复
mark
华芸智森 2008-05-26
  • 打赏
  • 举报
回复
上面这句话打错了:
--先建立一个主键,索引的表.
应该是:
--先建立一个无主键,无索引的表.
华芸智森 2008-05-26
  • 打赏
  • 举报
回复
对有主键,索引的表,大家有兴趣可以试一下. :)
华芸智森 2008-05-26
  • 打赏
  • 举报
回复

就楼上朋友的一些问题做了一个测试:

------------------------------
-- 1.主键是否可以与数据分离?
------------------------------
USE PUBS

GO

ALTER DATABASE [pubs] ADD FILEGROUP [TEST_LX]
GO
ALTER DATABASE [pubs] ADD FILE(NAME = N'TEST_LX', FILENAME = N'E:\TESTLX\TEST_LX.NDF' , SIZE = 1, FILEGROWTH = 10%) TO FILEGROUP [TEST_LX]
GO
ALTER DATABASE [pubs] ADD FILEGROUP [TEST_LX_KEY]
GO
ALTER DATABASE [pubs] ADD FILE(NAME = N'TEST_LX_KEY', FILENAME = N'E:\TESTLX\TEST_LX_KEY.NDF' , SIZE = 1, FILEGROWTH = 10%) TO FILEGROUP [TEST_LX_KEY]
GO



CREATE TABLE [TEST_LX] (
VAR_F1 VARCHAR(4) NOT NULL,
VAR_F2 VARCHAR(4) NOT NULL,
VAR_F3 VARCHAR(4) NOT NULL,
VAR_F4 VARCHAR(4) NOT NULL,
INT_F5 INT NULL
CONSTRAINT [PK_TEST_LX] PRIMARY KEY CLUSTERED
(
[VAR_F1],
[VAR_F2],
[VAR_F3]
) ON [TEST_LX_KEY]
) ON [TEST_LX]

GO
TRUNCATE TABLE TEST_LX

GO

-----------------------------
--向表中填充数据.
-----------------------------
DECLARE @VAR_F1 VARCHAR(32)
DECLARE @VAR_F2 VARCHAR(32)
DECLARE @VAR_F3 VARCHAR(32)
DECLARE @VAR_F4 VARCHAR(32)

DECLARE @INT_I INT
SET @INT_I=0

DECLARE @TAB_TMP TABLE (INT_V INT)
WHILE @INT_I<1000 BEGIN
INSERT INTO @TAB_TMP VALUES(RAND())
SET @INT_I=@INT_I+1
END

SET @INT_I=1
WHILE @INT_I<10000 BEGIN
INSERT INTO TEST_LX
SELECT LEFT(NEWID(),4),LEFT(NEWID(),4),LEFT(NEWID(),4),LEFT(NEWID(),4),[INT_V] FROM @TAB_TMP

SET @INT_I=@INT_I+1
END

GO

DECLARE @NVR_CMD VARCHAR(4000)
SET @NVR_CMD='DUMP TRANSACTION PUBS WITH NO_LOG'
EXECUTE(@NVR_CMD)

SET @NVR_CMD='DBCC SHRINKFILE(2, 0)'
EXECUTE(@NVR_CMD)

----------------------------------------------------------------
-- 在操作的过程中,观察文件变化
-- TEST_LX.NDF 还是 1 M , TEST_LX_KEY 却不断变大.
--结论 : 主键并没有生成一个副本,而是与数据文件一起.它不能与数据分件分离.
----------------------------------------------------------------

GO

----------------------------------------------
-- 2.记录在什么情况下会被追加到数据表的未尾?
----------------------------------------------
DROP TABLE TEST_LX

GO

--先建立一个主键,索引的表.
CREATE TABLE TEST_LX ( F1 INT,F2 INT)

GO

INSERT INTO TEST_LX VALUES(1,3)
INSERT INTO TEST_LX VALUES(2,2)
INSERT INTO TEST_LX VALUES(3,1)

GO

SELECT * FROM TEST_LX

/****************
结果:
1 3
2 2
3 1
*****************/

GO

--删除两条记录.
DELETE FROM TEST_LX WHERE F1<3

GO

--重新插入两条记录
INSERT INTO TEST_LX VALUES(5,3)
INSERT INTO TEST_LX VALUES(6,2)

GO
SELECT * FROM TEST_LX

/*****************************
结果:
5 3
6 2
3 1
结论:数据此时并没有被追加到表的未尾.
******************************/

GO

--再插入两条数据.
INSERT INTO TEST_LX VALUES(9,8)
INSERT INTO TEST_LX VALUES(10,9)

GO

SELECT * FROM TEST_LX
/*****************************
结果:
5 3
6 2
3 1
9 8
10 9
结论:此时数据被追加到了表尾.
******************************/

GO

--用UPDATE修改记录.
UPDATE TEST_LX SET F1=20 WHERE F1=5

GO

SELECT * FROM TEST_LX
/*****************************
结果:
20 3
6 2
3 1
9 8
10 9
结论:UPDATE并没有改变原来的表数据顺序.
******************************/

--以上结论仅化针对数字.下面做一个针对VARCHAR的测试.

GO

DROP TABLE TEST_LX

GO

--先建立一个主键,索引的表.
CREATE TABLE TEST_LX ( F1 VARCHAR(32),F2 VARCHAR(32) )

GO

INSERT INTO TEST_LX VALUES('1','3')
INSERT INTO TEST_LX VALUES('2','2')
INSERT INTO TEST_LX VALUES('3','1')


GO

--删除两条记录.
DELETE FROM TEST_LX WHERE F1<3

GO

--重新插入两条记录.注意,此时数据的长度与原来是一致的.
INSERT INTO TEST_LX VALUES('5','3')
INSERT INTO TEST_LX VALUES('6','2')

GO

SELECT * FROM TEST_LX

/*******************************
5 3
6 2
3 1
******************************/

GO

--再删除两条数据.
DELETE FROM TEST_LX WHERE F1>=5

GO
--插入字符长度比原来大的字符.

INSERT INTO TEST_LX VALUES('5aaaaaaaaaaaaa','3bbbbbbbbb')
INSERT INTO TEST_LX VALUES('6aaaaaaaaaaaaa','2bbbbbbbbbb')

GO

SELECT * FROM TEST_LX

/*******************************
结果:
5aaaaaaaaaaaaa 3bbbbbbbbb
6aaaaaaaaaaaaa 2bbbbbbbbbb
3 1
顺序未变.
******************************/

GO

--再插入两条.
INSERT INTO TEST_LX VALUES('Taaaaaaaaaaaaa','3bbbbbbbbb')
INSERT INTO TEST_LX VALUES('Yaaaaaaaaaaaaa','2bbbbbbbbbb')

GO

SELECT * FROM TEST_LX

/*******************************
5aaaaaaaaaaaaa 3bbbbbbbbb
6aaaaaaaaaaaaa 2bbbbbbbbbb
3 1
Taaaaaaaaaaaaa 3bbbbbbbbb
Yaaaaaaaaaaaaa 2bbbbbbbbbb
数据被加到了未尾.
******************************/

GO

--结论:无论数字型还是字符型.SQL会对NULL的空间进行填充.
--如果原来NULL的空间不够的情况下,记录会被追加到表尾.


















Herb2 2008-05-26
  • 打赏
  • 举报
回复
忘了,如果仅仅是重建聚集索引则和重新导数据是不一样的。
还要重建其它的索引,才和重新导数据是一样的。
Herb2 2008-05-26
  • 打赏
  • 举报
回复
[Quote=引用 90 楼 mengmou 的回复:]
1 我觉得重新倒数据的效果跟重建聚集索引一样
2 我觉得在新文件组上重建表跟在原文件组上建一样
[/Quote]
除了磁头移动的不一样带来性能不一样以外,其它性能是一样滴
mengmou 2008-05-26
  • 打赏
  • 举报
回复
1 我觉得重新倒数据的效果跟重建聚集索引一样
2 我觉得在新文件组上重建表跟在原文件组上建一样


华芸智森 2008-05-26
  • 打赏
  • 举报
回复
[Quote=引用 82 楼 Haiwer 的回复:]
还关心一个问题:
对于一个上亿数据量的表,做一次这样的优化需要多少时间。
[/Quote]
这个没有观察过...
测试时500万要十分钟左右.这与你字段和表结构,硬件等都有关.
华芸智森 2008-05-26
  • 打赏
  • 举报
回复
[Quote=引用 86 楼 mengmou 的回复:]
我觉得应该这样

1 整理磁盘碎片
2 重建聚集索引
[/Quote]

磁盘碎片 相对于 索引,数据内部的碎片 所造成的影响,基本可以被忽略.
华芸智森 2008-05-26
  • 打赏
  • 举报
回复
[Quote=引用 85 楼 Herb2 的回复:]
-- --在表上建立一个日期的索引.如果有索引的话.在此添加.
-- SET @NVR_CMD='CREATE INDEX IX_' + @NVR_TMPTABLENAME + '_DATE ON ' + @NVR_TMPTABLENAME + ' (BIG_DATE)'
-- EXECUTE(@NVR_CMD)

--将数据移到新的表.
SET @NVR_CMD='INSERT INTO ' + @NVR_TMPTABLENAME + ' SELECT * FROM ' + @NVR_TABLENAME --在此视你实质情况而定,可加入主键一至的 ORDER BY
EXECUTE(@NVR_CMD)
----------…
[/Quote]
加载更多回复(89)

27,579

社区成员

发帖
与我相关
我的任务
社区描述
MS-SQL Server 应用实例
社区管理员
  • 应用实例社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧