2006年12月29日

 下面就是完整的一个显示页面的例子:

<%@ LANGUAGE="VBScript" %>
<%
dim j
j=0
%>
<%
Function c2u(text)
  For i=1 to Len(text)
  c=Mid(text,i,1)
  c2u=c2u&"&#x" & Hex(AscW(c)) & ";"
  next
 End Function
%>
<%
 set conn=server.createobject("adodb.connection")
 conn.open"dsn=money;uid=;pwd=;database=money.mdb"
 sql0="update stock set temp=(price5-price4)/price4*100"
 set rs0=server.createobject("adodb.recordset") 
 rs0.open sql0,conn
 sql="select num,name,temp from stock where temp<0 order by temp ASC"
 set rs=server.createobject("adodb.recordset") 
 rs.open sql,conn 
%>
<% Response.ContentType="text/vnd.wap.wml" %><?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml"><wml>

<card id="ShowCurrent">
<p>
  <%=c2u("跌幅前五名:")%><br/>
<%
while j<5 and not rs.eof
%>
  <%=rs(0).Value%>-<%=c2u(rs(1).Value)%>&nbsp;<%=rs(2).Value%>%
<br/>
<%
rs.movenext
j=j+1
wend
%>
</p>
<do type="accept" label="<%=c2u("返回")%>">
 <go href="index.asp#Welcome"/>
</do>
<p align="center">
(c) 501 Studio, 2001<br/>
All rights reserved !
</p>
</card>
</wml> 
<%
rs.close
%>

  下面就是完整的一个显示页面的例子:

<%@ LANGUAGE="VBScript" %>
<%
dim j
j=0
%>
<%
Function c2u(text)
  For i=1 to Len(text)
  c=Mid(text,i,1)
  c2u=c2u&"&#x" & Hex(AscW(c)) & ";"
  next
 End Function
%>
<%
 set conn=server.createobject("adodb.connection")
 conn.open"dsn=money;uid=;pwd=;database=money.mdb"
 sql0="update stock set temp=(price5-price4)/price4*100"
 set rs0=server.createobject("adodb.recordset") 
 rs0.open sql0,conn
 sql="select num,name,temp from stock where temp<0 order by temp ASC"
 set rs=server.createobject("adodb.recordset") 
 rs.open sql,conn 
%>
<% Response.ContentType="text/vnd.wap.wml" %><?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml"><wml>

<card id="ShowCurrent">
<p>
  <%=c2u("跌幅前五名:")%><br/>
<%
while j<5 and not rs.eof
%>
  <%=rs(0).Value%>-<%=c2u(rs(1).Value)%>&nbsp;<%=rs(2).Value%>%
<br/>
<%
rs.movenext
j=j+1
wend
%>
</p>
<do type="accept" label="<%=c2u("返回")%>">
 <go href="index.asp#Welcome"/>
</do>
<p align="center">
(c) 501 Studio, 2001<br/>
All rights reserved !
</p>
</card>
</wml> 
<%
rs.close
%>

2006年09月01日

SQL Server 2005提供了一些新的特性,比如文件流支持(向第三代数据库迈出了可喜的一步)、T-SQL的改进(TOP子句等)、数据库镜像、透明的客户端重定向、新的基于架构的安全模型、内建的HTTP服务器等。

当然,价格也自然就比SQL Server 2000昂贵不少。

——————————————————————————–

作为微软耗资最大、开发周期最长的数据库里程碑,SQL Server 2005 定于2005年12月初发布,目前最后一个Beta试用版本已经推出。微软内部70多个核心应用系统(SAP, Siebel, MSW…)均已正式迁移到SQL 2005 Beta3。最新发布的Benchmark测试结果表明整体性能较SQL2000 大幅提升40%,部分组件的性能甚至提升3~10倍,在同等硬件环境下的压力测试性能已经超过Oracle 10g;外部几十家TAP客户如London Stock Exchange, USDA, Xerox的实际部署也已反映出这一特点。SQL Server 2005将是微软有史以来运行最快、最稳定的企业级数据管理与分析平台。请务必仔细阅读以下内容并将这些信息传递给您的客户, 对需要SQL 2005 Beta及资料的客户请按照下述方式提名…
Enterprise Credibility
•高可靠性 由于采用了先进的数据镜像技术,SQL Server 2005 的稳定性将提高到前所未有的秒级响应能力,在小于3 秒的时间内能够将数据冗灾应用迅速切换,大大提升的企业级应用的高可靠性。
•性能 TPC-C 处理性能较SQL 2000提升37%,每秒能够处理超过一百万笔交易,而TPC-H 对 1 TB 数据仓库处理性能较SQL 2000更提升162%;同样硬件环境下的TPC-H压力测试性能较Oracle快38%。对64位高性能运算和TB级海量数据存储的更好支持使系统可扩展性及性能达到了一个更高的水准,预计到年底产品正式发布前,整体产品性能还将有进一步的增长。
•安全性 超过260种新的安全功能,及数据加密、安全认证技术等来加强您数据系统的安全性

SQL Server 2005 Beta & EAP
为配合SQL Server 2005 Beta3,针对客户和合作伙伴的相关市场计划:
•如果您的客户或合作伙伴对SQL Server 2005 新版本感兴趣,并希望得到相关Beta光盘和资料进行试用,请您的客户访问http://www.microsoft.com/china/sql/2005/beta3.asp 进行注册。 我们会于近期将SQL2005相关资料和Beta版本直接寄送至客户处。
•对于愿意成为成功案例的Top 15家企业级客户,我们将提供如下支持:技术培训、现场支持服务,BIF & Fund, PR发布,产品发布会的市场宣传,具体提名请与我直接联系。
•对于希望采用SQL Server 2005作为其应用解决方案的Top 15家Solution Partner, 我们将提供技术培训、现场支持服务、BIF & Fund、产品发布会的市场推广,具体提名请与我联系。
•对于您的重要企业级客户或开发商, 如需要特别技术支持或培训,请发送Email至 v-shanxu@microsoft.com 进行汇总。我们将制定整体培训及支持计划。

Sales Readiness of SQL 2005
为了能够使客户简明扼要的了解SQL2005的特点,请仔细阅读附件PPT以及如下产品简介、价值表述和主要特性小结。
<>
SQL Server 2005 是微软5年来具有里程碑性质的企业级数据库产品。它在企业级支持,商业智能应用,管理开发效率等诸多方面,较SQL Server 2000均有质的飞跃,是集数据管理与商业智能分析于一体的,极具前瞻性的下一代数据管理与分析平台。
1分钟产品简介 3 分钟 产品价值表述 10 分钟 主要特性小结
安全、可靠、高效
–3秒内数据冗灾恢复
–64位高性能计算
–260多种安全新功能
–智能化数据管理 全面提升企业级应用系统的安全性、可靠性与性能
SQL Server 2005 将提供一个更安全、可靠和高效的数据管理平台,能够大幅降低企业级系统的运维风险和IT管理成本,增强企业客户的管理能力。由于使系统冗灾恢复时间达到前所未有的秒级响应能力,大幅提高了系统的性能、扩展性和64位运算能力,并加以更严格的安全控制,SQL Server 在支持现实中最苛刻的企业级系统的道路上迈出了极大的一步。
安全、可靠、高效的企业级数据管理平台
SQL Server 2005 将在企业级支持(安全性、高可靠性、性能、扩展性、可管理性) 方面有质的飞跃,不仅能够确保企业级数据业务的实时稳定运行、还能够大幅提升系统处理能力及管理效率、降低操作复杂度和运维成本。
 独特的数据库镜像可以极大地提升高可靠性,保证企业级系统冗灾恢复在3秒内切换
 超过260种新的安全功能,如数据加密、安全认证技术等来加强您数据系统的安全性
 整体处理性能较SQL Server 2000有了大幅增长37%,部分关键组件处理性能甚至有了3~10倍提升(如全文检索、数据整合、复制等…)
 对64位高性能运算和TB级海量数据存储的更好支持使系统可扩展性及性能达到了一个更高的水准,针对1TB数据仓库TPC-H压力测试表明性能较Oracle快38%
 实时在线管理、数据分区、快照隔离、备份/恢复以及复制等诸多功能的改进将会使得企业能够建立和部署更加稳定可靠的应用程序
 内置的数据集成服务以及各种自动化管理、调试和优化工具则进一步提升了系统可管理性,为您的IT管理工作带来全新的体验
商业智能一体化
–TB级海量数据支持
–最快的数据分析引擎
–实时商务智能
–丰富数据挖掘功能 推动企业管理的全面信息化,提升企业的商业洞察力
SQL Sever 2005 通过提供全新的一体化商业智能平台能够满足企业级客户对TB级海量业务数据的实时统计分析、动态报表、监控预测、风险管理等多种复杂业务需求,帮助客户综合分析来自多结构和异类数据源的不同商业数据并将其及时转化为辅助企业决策的商业信息,推动企业信息化管理和业务发展。
先进、一体化的商业智能平台
SQL Server 2005将提供一体化的商业智能平台,能够满足企业级客户对TB级海量数据的实时统计分析、动态报表、监控预测、风险管理等多种复杂业务需求,帮助客户迅速将数据转化为辅助企业决策的商业信息,推动企业信息化管理和业务发展。
 集成的商业智能套件包括数据仓库、数据分析、ETL(数据整合)、报表、数据挖掘等全系列设计开发管理工具,帮助客户快速构建部署各类商业智能应用
 全新设计的高速数据分析引擎和丰富数据挖掘算法将帮助客户更加高效灵活地对海量业务数据进行深入数据分析与数据挖掘,性能较SQL2000 快162%, 较Oracle快38%
 面向终端用户的报表设计及管理工具与Office、Portal的前端集成能够为企业搭建个性化的管理驾驶平台,提供丰富的报表展示和自由定制功能,降低IT管理负载
 企业级的数据整合工具能够高速处理各种异类数据和复杂数据流业务的整合互通,为企业提供一个整体分析平台,性能较SQL Server 2000提升600%
 特有的主动缓存(Proactive Cache)技术能够满足客户对实时性BI统计数据高性能访问的迫切需求
 统一维度模型(UDM),XML/A标准的支持使BI应用的扩展性大大提高
开放互联应用平台
–与.Net的完美结合
–企业级数据整合平台
–全面支持XML与Web Service 拓展业务应用空间,大幅提升管理开发效率,降低IT成本
SQL Server 2005将提供一个极具扩展性和灵活性的开发平台,.Net、XML、Web Service的紧密集成能够拓展您的应用空间,实现Internet数据业务互联,为您带来新的商业应用机遇。SQL Server 2005 同时支持在多个平台、程序和设备之间共享数据,更易于与内外部系统协调互通,客户可以从降低总体拥有成本和更快的开发时间中受益。 极具扩展性和灵活性的开发平台
SQL Server 2005 将提供集成的开发管理工具和各类新的开发特性,在大幅提高开发效率的同时,将进一步拓展您的应用空间,带来新的商业应用机遇。
 提供XML数据库和Xquery提升数据应用灵活性及在企业各系统之间的信息交互
 深入支持Web Service使Internet数据互联成为可能,进一步扩展数据应用范畴和异类系统的兼容性
 内置.Net集成扩展了数据应用开发空间,使应用性能与团队开发效率大幅提升
 集成的可视化开发调试环境则为数据库开发管理人员带来了激动人心的全新感受
 数据集成服务、服务代理(Service Broker)、异类数据库复制等可以帮助您无缝集成其它各类数据应用,实现数据共享
 各种新数据类型和T-SQL扩展带来了诸多开发灵活性

2006年08月21日

 

CREATE TABLE [dbo].[TGongwen] (    –TGongwen是红头文件表名

   [Gid] [int] IDENTITY (1, 1) NOT NULL ,
本表的id号,也是主键

   [title] [varchar] (80) COLLATE Chinese_PRC_CI_AS NULL , 
红头文件的标题

   [fariqi] [datetime] NULL ,
发布日期

   [neibuYonghu] [varchar] (70) COLLATE Chinese_PRC_CI_AS NULL ,
发布用户

   [reader] [varchar] (900) COLLATE Chinese_PRC_CI_AS NULL ,

需要浏览的用户。每个用户中间用分隔符“,”分开

) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

 

下面,我们来往数据库中添加1000万条数据:

declare @i int

set @i=1

while @i<=250000

begin

    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(‘2004-2-5‘,’通信科‘,’通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,治安支队,外事科‘,’这是最先的25万条记录‘)

    set @i=@i+1

end

GO

 

declare @i int

set @i=1

while @i<=250000

begin

    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(‘2004-9-16‘,’办公室‘,’办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科‘,’这是中间的25万条记录‘)

    set @i=@i+1

end

GO

 

declare @h int

set @h=1

while @h<=100

begin

declare @i int

set @i=2002

while @i<=2003

begin

declare @j int

        set @j=0

        while @j<50

            begin

declare @k int

            set @k=0

            while @k<50

            begin

    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(cast(@i as varchar(4))+’-8-15 3:’+cast(@j as varchar(2))+’:'+cast(@j as varchar(2)),’通信科‘,’办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科‘,’这是最后的50万条记录‘)

            set @k=@k+1

            end

set @j=@j+1

        end

set @i=@i+1

end

set @h=@h+1

end

GO

 

declare @i int

set @i=1

while @i<=9000000

begin

    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(‘2004-5-5‘,’通信科‘,’通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,治安支队,外事科‘,’这是最后添加的900万条记录‘)

    set @i=@i+1000000

end

GO

通过以上语句,我们创建了25万条由通信科于200425发布的记录,25万条由办公室于200496发布的记录,2002年和2003年各1002500条相同日期、不同分秒的由通信科发布的记录(共50万条),还有由通信科于200455发布的900万条记录,合计1000万条。

 

一、因情制宜,建立“适当”的索引

建立“适当”的索引是实现查询优化的首要前提。

索引(index)是除表之外另一重要的、用户定义的存储在物理介质上的数据结构。当根据索引码的值搜索数据时,索引提供了对数据的快速访问。事实上,没有索引,数据库也能根据SELECT语句成功地检索到结果,但随着表变得越来越大,使用“适当”的索引的效果就越来越明显。注意,在这句话中,我们用了“适当”这个词,这是因为,如果使用索引时不认真考虑其实现过程,索引既可以提高也会破坏数据库的工作性能。

(一)深入浅出理解索引结构

实际上,您可以把索引理解为一种特殊的目录。微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引、簇集索引)和非聚集索引(nonclustered index,也称非聚类索引、非簇集索引)。下面,我们举例来说明一下聚集索引和非聚集索引的区别:

其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。

我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。

如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。

我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。

通过以上例子,我们可以理解到什么是“聚集索引”和“非聚集索引”。

进一步引申一下,我们可以很容易的理解:每个表只能有一个聚集索引,因为目录只能按照一种方法进行排序。

(二)何时使用聚集索引或非聚集索引

下面的表总结了何时使用聚集索引或非聚集索引(很重要)。

动作描述

使用聚集索引

使用非聚集索引

列经常被分组排序

返回某范围内的数据

不应

一个或极少不同值

不应

不应

小数目的不同值

不应

大数目的不同值

不应

频繁更新的列

不应

外键列

主键列

频繁修改索引列

不应

事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个时间列,恰好您把聚合索引建立在了该列,这时您查询2004112004101之间的全部数据时,这个速度就将是很快的,因为您的这本字典正文是按日期进行排序的,聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可;而不像非聚集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到具体内容。

(三)结合实际,谈索引使用的误区

理论的目的是应用。虽然我们刚才列出了何时应使用聚集索引或非聚集索引,但在实践中以上规则却很容易被忽视或不能根据实际情况进行综合分析。下面我们将根据在实践中遇到的实际问题来谈一下索引使用的误区,以便于大家掌握索引建立的方法。

1、主键就是聚集索引

这种想法笔者认为是极端错误的,是对聚集索引的一种浪费。虽然SQL SERVER默认是在主键上建立聚集索引的。

通常,我们会在每个表中都建立一个ID列,以区分每条数据,并且这个ID列是自动增大的,步长一般为1。我们的这个办公自动化的实例中的列Gid就是如此。此时,如果我们将这个列设为主键,SQL SERVER会将此列默认为聚集索引。这样做有好处,就是可以让您的数据在数据库中按照ID进行物理排序,但笔者认为这样做意义不大。

显而易见,聚集索引的优势是很明显的,而每个表中只能有一个聚集索引的规则,这使得聚集索引变得更加珍贵。

从我们前面谈到的聚集索引的定义我们可以看出,使用聚集索引的最大好处就是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际应用中,因为ID号是自动生成的,我们并不知道每条记录的ID号,所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。其次,让每个ID号都不同的字段作为聚集索引也不符合“大数目的不同值情况下不应建立聚合索引”规则;当然,这种情况只是针对用户经常修改记录内容,特别是索引项的时候会负作用,但对于查询速度并没有影响。

在办公自动化系统中,无论是系统首页显示的需要用户签收的文件、会议还是用户进行文件查询等任何情况下进行数据查询都离不开字段的是“日期”还有用户本身的“用户名”。

通常,办公自动化的首页会显示每个用户尚未签收的文件或会议。虽然我们的where语句可以仅仅限制当前用户尚未签收的情况,但如果您的系统已建立了很长时间,并且数据量很大,那么,每次每个用户打开首页的时候都进行一次全表扫描,这样做意义是不大的,绝大多数的用户1个月前的文件都已经浏览过了,这样做只能徒增数据库的开销而已。事实上,我们完全可以让用户打开系统首页时,数据库仅仅查询这个用户近3个月来未阅览的文件,通过“日期”这个字段来限制表扫描,提高查询速度。如果您的办公自动化系统已经建立的2年,那么您的首页显示速度理论上将是原来速度8倍,甚至更快。

在这里之所以提到“理论上”三字,是因为如果您的聚集索引还是盲目地建在ID这个主键上时,您的查询速度是没有这么高的,即使您在“日期”这个字段上建立的索引(非聚合索引)。下面我们就来看一下在1000万条数据量的情况下各种查询的速度表现(3个月内的数据为25万条):

1)仅在主键上建立聚集索引,并且不划分时间段:

Select gid,fariqi,neibuyonghu,title from tgongwen

用时:128470毫秒(即:128秒)

2)在主键上建立聚集索引,在fariq上建立非聚集索引:

select gid,fariqi,neibuyonghu,title from Tgongwen

where fariqi> dateadd(day,-90,getdate())

用时:53763毫秒(54秒)

3)将聚合索引建立在日期列(fariqi)上:

select gid,fariqi,neibuyonghu,title from Tgongwen

where fariqi> dateadd(day,-90,getdate())

用时:2423毫秒(2秒)

虽然每条语句提取出来的都是25万条数据,各种情况的差异却是巨大的,特别是将聚集索引建立在日期列时的差异。事实上,如果您的数据库真的有1000万容量的话,把主键建立在ID列上,就像以上的第12种情况,在网页上的表现就是超时,根本就无法显示。这也是我摒弃ID列作为聚集索引的一个最重要的因素。

得出以上速度的方法是:在各个select语句前加:declare @d datetime

set @d=getdate()

并在select语句后加:

select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate())

2、只要建立索引就能显著提高查询速度

事实上,我们可以发现上面的例子中,第23条语句完全相同,且建立索引的字段也相同;不同的仅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查询速度却有着天壤之别。所以,并非是在任何字段上简单地建立索引就能提高查询速度。

从建表的语句中,我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同记录。在此字段上建立聚合索引是再合适不过了。在现实中,我们每天都会发几个文件,这几个文件的发文日期就相同,这完全符合建立聚集索引要求的:“既不能绝大多数都相同,又不能只有极少数相同”的规则。由此看来,我们建立“适当”的聚合索引对于我们提高查询速度是非常重要的。

3、把所有需要提高查询速度的字段都加进聚集索引,以提高查询速度

上面已经谈到:在进行数据查询时都离不开字段的是“日期”还有用户本身的“用户名”。既然这两个字段都是如此的重要,我们可以把他们合并起来,建立一个复合索引(compound index)。

很多人认为只要把任何字段加进聚集索引,就能提高查询速度,也有人感到迷惑:如果把复合的聚集索引字段分开查询,那么查询速度会减慢吗?带着这个问题,我们来看一下以下的查询速度(结果集都是25万条数据):(日期列fariqi首先排在复合聚集索引的起始列,用户名neibuyonghu排在后列)

1select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>’2004-5-5

查询速度:2513毫秒

2select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>’2004-5-5‘ and neibuyonghu=’办公室

查询速度:2516毫秒

3select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu=’办公室

查询速度:60280毫秒

从以上试验中,我们可以看到如果仅用聚集索引的起始列作为查询条件和同时用到复合聚集索引的全部列的查询速度是几乎一样的,甚至比用上全部的复合索引列还要略快(在查询结果集数目一样的情况下);而如果仅用复合聚集索引的非起始列作为查询条件的话,这个索引是不起任何作用的。当然,语句12的查询速度一样是因为查询的条目数一样,如果复合索引的所有列都用上,而且查询结果少的话,这样就会形成“索引覆盖”,因而性能可以达到最优。同时,请记住:无论您是否经常使用聚合索引的其他列,但其前导列一定要是使用最频繁的列。

(四)其他书上没有的索引使用经验总结

1、用聚合索引比用不是聚合索引的主键速度快

下面是实例语句:(都是提取25万条数据)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=’2004-9-16

使用时间:3326毫秒

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid<=250000

使用时间:4470毫秒

这里,用聚合索引比用不是聚合索引的主键速度快了近1/4

2、用聚合索引比用一般的主键作order by时速度快,特别是在小数据量情况下

select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi

用时:12936

select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid

用时:18843

这里,用聚合索引比用一般的主键作order by时,速度快了3/10。事实上,如果数据量很小的话,用聚集索引作为排序列要比使用非聚集索引速度快得明显的多;而数据量如果很大的话,如10万以上,则二者的速度差别不明显。

3、使用聚合索引内的时间段,搜索时间会按数据占整个数据表的百分比成比例减少,而无论聚合索引使用了多少个

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>’2004-1-1

用时:6343毫秒(提取100万条)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>’2004-6-6

用时:3170毫秒(提取50万条)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=’2004-9-16

用时:3326毫秒(和上句的结果一模一样。如果采集的数量一样,那么用大于号和等于号是一样的)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>’2004-1-1‘ and fariqi<’2004-6-6

用时:3280毫秒

4 、日期列不会因为有分秒的输入而减慢查询速度

下面的例子中,共有100万条数据,200411以后的数据有50万条,但只有两个不同的日期,日期精确到日;之前有数据50万条,有5000个不同的日期,日期精确到秒。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>’2004-1-1‘ order by fariqi

用时:6390毫秒

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi<’2004-1-1‘ order by fariqi

用时:6453毫秒

(五)其他注意事项

“水可载舟,亦可覆舟”,索引也一样。索引有助于提高检索性能,但过多或不当的索引也会导致系统低效。因为用户在表中每加进一个索引,数据库就要做更多的工作。过多的索引甚至会导致索引碎片。

所以说,我们要建立一个“适当”的索引体系,特别是对聚合索引的创建,更应精益求精,以使您的数据库能得到高性能的发挥。

当然,在实践中,作为一个尽职的数据库管理员,您还要多测试一些方案,找出哪种方案效率最高、最为有效。

 

二、改善SQL语句

很多人不知道SQL语句在SQL SERVER中是如何执行的,他们担心自己所写的SQL语句会被SQL SERVER误解。比如:

select * from table1 where name=’zhangsan’ and tID > 10000

和执行:

select * from table1 where tID > 10000 and name=’zhangsan’

一些人不知道以上两条语句的执行效率是否一样,因为如果简单的从语句先后上看,这两个语句的确是不一样,如果tID是一个聚合索引,那么后一句仅仅从表的10000条以后的记录中查找就行了;而前一句则要先从全表中查找看有几个name=’zhangsan’的,而后再根据限制条件条件tID>10000来提出查询结果。

事实上,这样的担心是不必要的。SQL SERVER中有一个“查询分析优化器”,它可以计算出where子句中的搜索条件并确定哪个索引能缩小表扫描的搜索空间,也就是说,它能实现自动优化。

虽然查询优化器可以根据where子句自动的进行查询优化,但大家仍然有必要了解一下“查询优化器”的工作原理,如非这样,有时查询优化器就会不按照您的本意进行快速查询。

在查询分析阶段,查询优化器查看查询的每个阶段并决定限制需要扫描的数据量是否有用。如果一个阶段可以被用作一个扫描参数(SARG),那么就称之为可优化的,并且可以利用索引快速获得所需数据。

SARG的定义:用于限制搜索的一个操作,因为它通常是指一个特定的匹配,一个值得范围内的匹配或者两个以上条件的AND连接。形式如下:

列名 操作符 <常数 或 变量>

<常数 或 变量> 操作符列名

列名可以出现在操作符的一边,而常数或变量出现在操作符的另一边。如:

Name=张三

价格>5000

5000<价格

Name=张三 and 价格>5000

如果一个表达式不能满足SARG的形式,那它就无法限制搜索的范围了,也就是SQL SERVER必须对每一行都判断它是否满足WHERE子句中的所有条件。所以一个索引对于不满足SARG形式的表达式来说是无用的。

介绍完SARG后,我们来总结一下使用SARG以及在实践中遇到的和某些资料上结论不同的经验:

1Like语句是否属于SARG取决于所使用的通配符的类型

如:name like % ,这就属于SARG

而:name like % ,就不属于SARG

原因是通配符%在字符串的开通使得索引无法使用。

2or 会引起全表扫描

Name=张三 and 价格>5000 符号SARG,而:Name=张三 or 价格>5000 则不符合SARG。使用or会引起全表扫描。

3、非操作符、函数引起的不满足SARG形式的语句

不满足SARG形式的语句最典型的情况就是包括非操作符的语句,如:NOT!=<>!<!>NOT EXISTSNOT INNOT LIKE等,另外还有函数。下面就是几个不满足SARG形式的例子:

ABS(价格)<5000

Name like %

有些表达式,如:

WHERE 价格*2>5000

SQL SERVER也会认为是SARGSQL SERVER会将此式转化为:

WHERE 价格>2500/2

但我们不推荐这样使用,因为有时SQL SERVER不能保证这种转化与原始表达式是完全等价的。

4IN 的作用相当与OR

语句:

Select * from table1 where tid in (2,3)

Select * from table1 where tid=2 or tid=3

是一样的,都会引起全表扫描,如果tid上有索引,其索引也会失效。

5、尽量少用NOT

6exists in 的执行效率是一样的

很多资料上都显示说,exists要比in的执行效率要高,同时应尽可能的用not exists来代替not in。但事实上,我试验了一下,发现二者无论是前面带不带not,二者之间的执行效率都是一样的。因为涉及子查询,我们试验这次用SQL SERVER自带的pubs数据库。运行前我们可以把SQL SERVERstatistics I/O状态打开。

1select title,price from titles where title_id in (select title_id from sales where qty>30)

该句的执行结果为:

’sales’。扫描计数 18,逻辑读 56 次,物理读 0 次,预读 0 次。

‘titles’。扫描计数 1,逻辑读 2 次,物理读 0 次,预读 0 次。

   

2select title,price from titles where exists (select * from sales where sales.title_id=titles.title_id and qty>30)

第二句的执行结果为:

’sales’。扫描计数 18,逻辑读 56 次,物理读 0 次,预读 0 次。

‘titles’。扫描计数 1,逻辑读 2 次,物理读 0 次,预读 0 次。

我们从此可以看到用exists和用in的执行效率是一样的。

7、用函数charindex()和前面加通配符%LIKE执行效率一样

前面,我们谈到,如果在LIKE前面加上通配符%,那么将会引起全表扫描,所以其执行效率是低下的。但有的资料介绍说,用函数charindex()来代替LIKE速度会有大的提升,经我试验,发现这种说明也是错误的:

select gid,title,fariqi,reader from tgongwen where charindex(‘刑侦支队‘,reader)>0 and fariqi>’2004-5-5

用时:7秒,另外:扫描计数 4,逻辑读 7155 次,物理读 0 次,预读 0 次。

select gid,title,fariqi,reader from tgongwen where reader like ‘%’ + ‘刑侦支队‘ + ‘%’ and fariqi>’2004-5-5

用时:7秒,另外:扫描计数 4,逻辑读 7155 次,物理读 0 次,预读 0 次。

8union并不绝对比or的执行效率高

我们前面已经谈到了在where子句中使用or会引起全表扫描,一般的,我所见过的资料都是推荐这里用union来代替or。事实证明,这种说法对于大部分都是适用的。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=’2004-9-16‘ or gid>9990000

用时:68秒。扫描计数 1,逻辑读 404008 次,物理读 283 次,预读 392163 次。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=’2004-9-16

union

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid>9990000

用时:9秒。扫描计数 8,逻辑读 67489 次,物理读 216 次,预读 7499 次。

看来,用union在通常情况下比用or的效率要高的多。

但经过试验,笔者发现如果or两边的查询列是一样的话,那么用union则反倒和用or的执行速度差很多,虽然这里union扫描的是索引,而or扫描的是全表。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=’2004-9-16‘ or fariqi=’2004-2-5

用时:6423毫秒。扫描计数 2,逻辑读 14726 次,物理读 1 次,预读 7176 次。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=’2004-9-16

union

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where  fariqi=’2004-2-5

用时:11640毫秒。扫描计数 8,逻辑读 14806 次,物理读 108 次,预读 1144 次。

9、字段提取要按照“需多少、提多少”的原则,避免“select *

我们来做一个试验:

select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc

用时:4673毫秒

select top 10000 gid,fariqi,title from tgongwen order by gid desc

用时:1376毫秒

select top 10000 gid,fariqi from tgongwen order by gid desc

用时:80毫秒

由此看来,我们每少提取一个字段,数据的提取速度就会有相应的提升。提升的速度还要看您舍弃的字段的大小来判断。

10count(*)不比count(字段)

某些资料上说:用*会统计所有列,显然要比一个世界的列名效率低。这种说法其实是没有根据的。我们来看:

select count(*) from Tgongwen

用时:1500毫秒

select count(gid) from Tgongwen

用时:1483毫秒

select count(fariqi) from Tgongwen

用时:3140毫秒

select count(title) from Tgongwen

用时:52050毫秒

从以上可以看出,如果用count(*)和用count(主键)的速度是相当的,而count(*)却比其他任何除主键以外的字段汇总速度要快,而且字段越长,汇总的速度就越慢。我想,如果用count(*) SQL SERVER可能会自动查找最小字段来汇总的。当然,如果您直接写count(主键)将会来的更直接些。

11order by按聚集索引列排序效率最高

我们来看:(gid是主键,fariqi是聚合索引列)

select top 10000 gid,fariqi,reader,title from tgongwen

用时:196 毫秒。 扫描计数 1,逻辑读 289 次,物理读 1 次,预读 1527 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc

用时:4720毫秒。 扫描计数 1,逻辑读 41956 次,物理读 0 次,预读 1287 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc

用时:4736毫秒。 扫描计数 1,逻辑读 55350 次,物理读 10 次,预读 775 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc

用时:173毫秒。 扫描计数 1,逻辑读 290 次,物理读 0 次,预读 0 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc

用时:156毫秒。 扫描计数 1,逻辑读 289 次,物理读 0 次,预读 0 次。

从以上我们可以看出,不排序的速度以及逻辑读次数都是和“order by 聚集索引列” 的速度是相当的,但这些都比“order by 非聚集索引列”的查询速度是快得多的。

同时,按照某个字段进行排序的时候,无论是正序还是倒序,速度是基本相当的。

12、高效的TOP

事实上,在查询和提取超大容量的数据集时,影响数据库响应时间的最大因素不是数据查找,而是物理的I/0操作。如:

select top 10 * from (

select top 10000 gid,fariqi,title from tgongwen

where neibuyonghu=’办公室

order by gid desc) as a

order by gid asc

这条语句,从理论上讲,整条语句的执行时间应该比子句的执行时间长,但事实相反。因为,子句执行后返回的是10000条记录,而整条语句仅返回10条语句,所以影响数据库响应时间最大的因素是物理I/O操作。而限制物理I/O操作此处的最有效方法之一就是使用TOP关键词了。TOP关键词是SQL SERVER中经过系统优化过的一个用来提取前几条或前几个百分比数据的词。经笔者在实践中的应用,发现TOP确实很好用,效率也很高。但这个词在另外一个大型数据库ORACLE中却没有,这不能说不是一个遗憾,虽然在ORACLE中可以用其他方法(如:rownumber)来解决。在以后的关于“实现千万级数据的分页显示存储过程”的讨论中,我们就将用到TOP这个关键词。

 

到此为止,我们上面讨论了如何实现从大容量的数据库中快速地查询出您所需要的数据方法。当然,我们介绍的这些方法都是“软”方法,在实践中,我们还要考虑各种“硬”因素,如:网络性能、服务器的性能、操作系统的性能,甚至网卡、交换机等。

 

三、实现小数据量和海量数据的通用分页显示存储过程

建立一个web 应用,分页浏览功能必不可少。这个问题是数据库处理中十分常见的问题。经典的数据分页方法是:ADO 纪录集分页法,也就是利用ADO自带的分页功能(利用游标)来实现分页。但这种分页方法仅适用于较小数据量的情形,因为游标本身有缺点:游标是存放在内存中,很费内存。游标一建立,就将相关的记录锁住,直到取消游标。游标提供了对特定集合中逐行扫描的手段,一般使用游标来逐行遍历数据,根据取出数据条件的不同进行不同的操作。而对于多表和大表中定义的游标(大的数据集合)循环很容易使程序进入一个漫长的等待甚至死机。

更重要的是,对于非常大的数据模型而言,分页检索时,如果按照传统的每次都加载整个数据源的方法是非常浪费资源的。现在流行的分页方法一般是检索页面大小的块区的数据,而非检索所有的数据,然后单步执行当前行。

    最早较好地实现这种根据页面大小和页码来提取数据的方法大概就是“俄罗斯存储过程”。这个存储过程用了游标,由于游标的局限性,所以这个方法并没有得到大家的普遍认可。

后来,网上有人改造了此存储过程,下面的存储过程就是结合我们的办公自动化实例写的分页存储过程:

CREATE procedure pagination1

(@pagesize int,  页面大小,如每页存储20条记录

@pageindex int   当前页码

)

as

set nocount on

begin

declare @indextable table(id int identity(1,1),nid int)  定义表变量

declare @PageLowerBound int  定义此页的底码

declare @PageUpperBound int  定义此页的顶码

set @PageLowerBound=(@pageindex-1)*@pagesize

set @PageUpperBound=@PageLowerBound+@pagesize

set rowcount @PageUpperBound

insert into @indextable(nid) select gid from TGongwen where fariqi >dateadd(day,-365,getdate()) order by fariqi desc

select O.gid,O.mid,O.title,O.fadanwei,O.fariqi from TGongwen O,@indextable t where O.gid=t.nid

and t.id>@PageLowerBound and t.id<=@PageUpperBound order by t.id

end

set nocount off

以上存储过程运用了SQL SERVER的最新技术――表变量。应该说这个存储过程也是一个非常优秀的分页存储过程。当然,在这个过程中,您也可以把其中的表变量写成临时表:CREATE TABLE #Temp。但很明显,在SQL SERVER中,用临时表是没有用表变量快的。所以笔者刚开始使用这个存储过程时,感觉非常的不错,速度也比原来的ADO的好。但后来,我又发现了比此方法更好的方法。

笔者曾在网上看到了一篇小短文《从数据表中取出第n条到第m条的记录的方法》,全文如下:

publish 表中取出第 n 条到第 m 条的记录:
SELECT TOP m-n+1 *
FROM publish
WHERE (id NOT IN
    
(SELECT TOP n-1 id
     FROM publish))

id publish 表的关键字

我当时看到这篇文章的时候,真的是精神为之一振,觉得思路非常得好。等到后来,我在作办公自动化系统(ASP.NET+ C#SQL SERVER)的时候,忽然想起了这篇文章,我想如果把这个语句改造一下,这就可能是一个非常好的分页存储过程。于是我就满网上找这篇文章,没想到,文章还没找到,却找到了一篇根据此语句写的一个分页存储过程,这个存储过程也是目前较为流行的一种分页存储过程,我很后悔没有争先把这段文字改造成存储过程:

CREATE PROCEDURE pagination2
(
 @SQL nVARCHAR(4000),    不带排序语句的SQL语句
 @Page int,              页码
 @RecsPerPage int,       每页容纳的记录数
 @ID VARCHAR(255),       需要排序的不重复的ID
 @Sort VARCHAR(255)      排序字段及规则
)
AS

DECLARE @Str nVARCHAR(4000)

SET @Str=’SELECT   TOP ‘+CAST(@RecsPerPage AS VARCHAR(20))+’ * FROM (‘+@SQL+’) T WHERE T.’+@ID+’NOT IN
(SELECT
   TOP ‘+CAST((@RecsPerPage*(@Page-1)) AS VARCHAR(20))+’ ‘+@ID+’ FROM (‘+@SQL+’) T9 ORDER BY ‘+@Sort+’) ORDER BY ‘+@Sort

PRINT @Str

EXEC sp_ExecuteSql @Str
GO

其实,以上语句可以简化为:

SELECT TOP 页大小 *

FROM Table1

WHERE (ID NOT IN

          (SELECT TOP 页大小*页数 id

         FROM

         ORDER BY id))

ORDER BY ID

但这个存储过程有一个致命的缺点,就是它含有NOT IN字样。虽然我可以把它改造为:

SELECT TOP 页大小 *

FROM Table1

WHERE not exists

(select * from (select top (页大小*页数) * from table1 order by id) b where b.id=a.id )

order by id

即,用not exists来代替not in,但我们前面已经谈过了,二者的执行效率实际上是没有区别的。

既便如此,用TOP 结合NOT IN的这个方法还是比用游标要来得快一些。

虽然用not exists并不能挽救上个存储过程的效率,但使用SQL SERVER中的TOP关键字却是一个非常明智的选择。因为分页优化的最终目的就是避免产生过大的记录集,而我们在前面也已经提到了TOP的优势,通过TOP 即可实现对数据量的控制。

在分页算法中,影响我们查询速度的关键因素有两点:TOPNOT INTOP可以提高我们的查询速度,而NOT IN会减慢我们的查询速度,所以要提高我们整个分页算法的速度,就要彻底改造NOT IN,同其他方法来替代它。

我们知道,几乎任何字段,我们都可以通过max(字段)min(字段)来提取某个字段中的最大或最小值,所以如果这个字段不重复,那么就可以利用这些不重复的字段的maxmin作为分水岭,使其成为分页算法中分开每页的参照物。在这里,我们可以用操作符“>”或“<”号来完成这个使命,使查询语句符合SARG形式。如:

Select top 10 * from table1 where id>200

于是就有了如下分页方案:

select top 页大小 *

from table1

where id>

      (select max (id) from

      (select top ((页码-1)*页大小) id from table1 order by id) as T

       )    

  order by id

在选择即不重复值,又容易分辨大小的列时,我们通常会选择主键。下表列出了笔者用有着1000万数据的办公自动化系统中的表,在以GIDGID是主键,但并不是聚集索引。)为排序列、提取gid,fariqi,title字段,分别以第11010050010001万、10万、25万、50万页为例,测试以上三种分页方案的执行速度:(单位:毫秒)

 

方案1

方案2

方案3

1

60

30

76

10

46

16

63

100

1076

720

130

500

540

12943

83

1000

17110

470

250

1

24796

4500

140

10

38326

42283

1553

25

28140

128720

2330

50

121686

127846

7168

从上表中,我们可以看出,三种存储过程在执行100页以下的分页命令时,都是可以信任的,速度都很好。但第一种方案在执行分页1000页以上后,速度就降了下来。第二种方案大约是在执行分页1万页以上后速度开始降了下来。而第三种方案却始终没有大的降势,后劲仍然很足。

在确定了第三种分页方案后,我们可以据此写一个存储过程。大家知道SQL SERVER的存储过程是事先编译好的SQL语句,它的执行效率要比通过WEB页面传来的SQL语句的执行效率要高。下面的存储过程不仅含有分页方案,还会根据页面传来的参数来确定是否进行数据总数统计。

获取指定页的数据

CREATE PROCEDURE pagination3

@tblName   varchar(255),       表名

@strGetFields varchar(1000) = ‘*’,  需要返回的列

@fldName varchar(255)=”,      排序的字段名

@PageSize   int = 10,          页尺寸

@PageIndex  int = 1,           页码

@doCount  bit = 0,   返回记录总数, 0 值则返回

@OrderType bit = 0,  设置排序类型, 0 值则降序

@strWhere  varchar(1500) = ”  查询条件 (注意: 不要加 where)

AS

declare @strSQL   varchar(5000)       主语句

declare @strTmp   varchar(110)        临时变量

declare @strOrder varchar(400)        排序类型

 

if @doCount != 0

  begin

    if @strWhere !=”

    set @strSQL = "select count(*) as Total from [" + @tblName + "] where "+@strWhere

    else

    set @strSQL = "select count(*) as Total from [" + @tblName + "]"

end 

以上代码的意思是如果@doCount传递过来的不是0,就执行总数统计。以下的所有代码都是@doCount0的情况

else

begin

 

if @OrderType != 0

begin

    set @strTmp = "<(select min"

set @strOrder = " order by [" + @fldName +"] desc"

如果@OrderType不是0,就执行降序,这句很重要!

end

else

begin

    set @strTmp = ">(select max"

    set @strOrder = " order by [" + @fldName +"] asc"

end

 

if @PageIndex = 1

begin

    if @strWhere != ”  

    set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "  from [" + @tblName + "] where " + @strWhere + " " + @strOrder

     else

     set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "  from ["+ @tblName + "] "+ @strOrder

如果是第一页就执行以上代码,这样会加快执行速度

end

else

begin

以下代码赋予了@strSQL以真正执行的SQL代码

set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "  from ["

    + @tblName + "] where [" + @fldName + "]" + @strTmp + "(["+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["+ @fldName + "] from [" + @tblName + "]" + @strOrder + ") as tblTmp)"+ @strOrder

 

if @strWhere != ”

    set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "  from ["

        + @tblName + "] where [" + @fldName + "]" + @strTmp + "(["

        + @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["

        + @fldName + "] from [" + @tblName + "] where " + @strWhere + " "

        + @strOrder + ") as tblTmp) and " + @strWhere + " " + @strOrder

end

end  

exec (@strSQL)

GO

上面的这个存储过程是一个通用的存储过程,其注释已写在其中了。

在大数据量的情况下,特别是在查询最后几页的时候,查询时间一般不会超过9秒;而用其他存储过程,在实践中就会导致超时,所以这个存储过程非常适用于大容量数据库的查询。

笔者希望能够通过对以上存储过程的解析,能给大家带来一定的启示,并给工作带来一定的效率提升,同时希望同行提出更优秀的实时数据分页算法。

 

四、聚集索引的重要性和如何选择聚集索引

在上一节的标题中,笔者写的是:实现小数据量和海量数据的通用分页显示存储过程。这是因为在将本存储过程应用于“办公自动化”系统的实践中时,笔者发现这第三种存储过程在小数据量的情况下,有如下现象:

1、分页速度一般维持在1秒和3秒之间。

2、在查询最后一页时,速度一般为5秒至8秒,哪怕分页总数只有3页或30万页。

虽然在超大容量情况下,这个分页的实现过程是很快的,但在分前几页时,这个13秒的速度比起第一种甚至没有经过优化的分页方法速度还要慢,借用户的话说就是“还没有ACCESS数据库速度快”,这个认识足以导致用户放弃使用您开发的系统。

笔者就此分析了一下,原来产生这种现象的症结是如此的简单,但又如此的重要:排序的字段不是聚集索引!

本篇文章的题目是:“查询优化及分页算法方案”。笔者只所以把“查询优化”和“分页算法”这两个联系不是很大的论题放在一起,就是因为二者都需要一个非常重要的东西――聚集索引。

在前面的讨论中我们已经提到了,聚集索引有两个最大的优势:

1、以最快的速度缩小查询范围。

2、以最快的速度进行字段排序。

1条多用在查询优化时,而第2条多用在进行分页时的数据排序。

而聚集索引在每个表内又只能建立一个,这使得聚集索引显得更加的重要。聚集索引的挑选可以说是实现“查询优化”和“高效分页”的最关键因素。

但要既使聚集索引列既符合查询列的需要,又符合排序列的需要,这通常是一个矛盾。

笔者前面“索引”的讨论中,将fariqi,即用户发文日期作为了聚集索引的起始列,日期的精确度为“日”。这种作法的优点,前面已经提到了,在进行划时间段的快速查询中,比用ID主键列有很大的优势。

但在分页时,由于这个聚集索引列存在着重复记录,所以无法使用maxmin来最为分页的参照物,进而无法实现更为高效的排序。而如果将ID主键列作为聚集索引,那么聚集索引除了用以排序之外,没有任何用处,实际上是浪费了聚集索引这个宝贵的资源。

为解决这个矛盾,笔者后来又添加了一个日期列,其默认值为getdate()。用户在写入记录时,这个列自动写入当时的时间,时间精确到毫秒。即使这样,为了避免可能性很小的重合,还要在此列上创建UNIQUE约束。将此日期列作为聚集索引列。

有了这个时间型聚集索引列之后,用户就既可以用这个列查找用户在插入数据时的某个时间段的查询,又可以作为唯一列来实现maxmin,成为分页算法的参照物。

经过这样的优化,笔者发现,无论是大数据量的情况下还是小数据量的情况下,分页速度一般都是几十毫秒,甚至0毫秒。而用日期段缩小范围的查询速度比原来也没有任何迟钝。

聚集索引是如此的重要和珍贵,所以笔者总结了一下,一定要将聚集索引建立在:

1、您最频繁使用的、用以缩小查询范围的字段上;

2、您最频繁使用的、需要排序的字段上。

2006年08月16日

页面顶部加
startime=timer()
尾部加
endtime=timer()
response.write "页面执行时间:"&FormatNumber((endtime-startime)*1000,3)&"毫秒"

2006年08月02日

实现小数据量和海量数据的通用分页显示存储过程

  建立一个web 应用,分页浏览功能必不可少。这个问题是数据库处理中十分常见的问题。经典的数据分页方法是:ADO 纪录集分页法,也就是利用ADO自带的分页功能(利用游标)来实现分页。但这种分页方法仅适用于较小数据量的情形,因为游标本身有缺点:游标是存放在内存中,很费内存。游标一建立,就将相关的记录锁住,直到取消游标。游标提供了对特定集合中逐行扫描的手段,一般使用游标来逐行遍历数据,根据取出数据条件的不同进行不同的操作。而对于多表和大表中定义的游标(大的数据集合)循环很容易使程序进入一个漫长的等待甚至死机。

  更重要的是,对于非常大的数据模型而言,分页检索时,如果按照传统的每次都加载整个数据源的方法是非常浪费资源的。现在流行的分页方法一般是检索页面大小的块区的数据,而非检索所有的数据,然后单步执行当前行。

  最早较好地实现这种根据页面大小和页码来提取数据的方法大概就是“俄罗斯存储过程”。这个存储过程用了游标,由于游标的局限性,所以这个方法并没有得到大家的普遍认可。

  后来,网上有人改造了此存储过程,下面的存储过程就是结合我们的办公自动化实例写的分页存储过程:

CREATE procedure pagination1

(@pagesize int, –页面大小,如每页存储20条记录

@pageindex int –当前页码

)

as

set nocount on

begin

declare @indextable table(id int identity(1,1),nid int) –定义表变量

declare @PageLowerBound int –定义此页的底码

declare @PageUpperBound int –定义此页的顶码

set @PageLowerBound=(@pageindex-1)*@pagesize

set @PageUpperBound=@PageLowerBound+@pagesize

set rowcount @PageUpperBound

insert into @indextable(nid) select gid from TGongwen where fariqi >dateadd(day,-365,getdate()) order by fariqi desc

select O.gid,O.mid,O.title,O.fadanwei,O.fariqi from TGongwen O,@indextable t where O.gid=t.nid

and t.id>@PageLowerBound and t.id<=@PageUpperBound order by t.id

end

set nocount off

  以上存储过程运用了SQL SERVER的最新技术――表变量。应该说这个存储过程也是一个非常优秀的分页存储过程。当然,在这个过程中,您也可以把其中的表变量写成临时表:CREATE TABLE #Temp。但很明显,在SQL SERVER中,用临时表是没有用表变量快的。所以笔者刚开始使用这个存储过程时,感觉非常的不错,速度也比原来的ADO的好。但后来,我又发现了比此方法更好的方法。

  笔者曾在网上看到了一篇小短文《从数据表中取出第n条到第m条的记录的方法》,全文如下:

从publish 表中取出第 n 条到第 m 条的记录:
SELECT TOP m-n+1 *
FROM publish
WHERE (id NOT IN
    (SELECT TOP n-1 id
     FROM publish))

id 为publish 表的关键字

  我当时看到这篇文章的时候,真的是精神为之一振,觉得思路非常得好。等到后来,我在作办公自动化系统(ASP.NET+ C#+SQL SERVER)的时候,忽然想起了这篇文章,我想如果把这个语句改造一下,这就可能是一个非常好的分页存储过程。于是我就满网上找这篇文章,没想到,文章还没找到,却找到了一篇根据此语句写的一个分页存储过程,这个存储过程也是目前较为流行的一种分页存储过程,我很后悔没有争先把这段文字改造成存储过程:

CREATE PROCEDURE pagination2
(
@SQL nVARCHAR(4000), –不带排序语句的SQL语句
@Page int, –页码
@RecsPerPage int, –每页容纳的记录数
@ID VARCHAR(255), –需要排序的不重复的ID号
@Sort VARCHAR(255) –排序字段及规则
)
AS

DECLARE @Str nVARCHAR(4000)

SET @Str=’SELECT TOP ‘+CAST(@RecsPerPage AS VARCHAR(20))+’ * FROM (‘+@SQL+’) T WHERE T.’+@ID+’NOT IN
(SELECT TOP ‘+CAST((@RecsPerPage*(@Page-1)) AS VARCHAR(20))+’ ‘+@ID+’ FROM (‘+@SQL+’) T9 ORDER BY ‘+@Sort+’) ORDER BY ‘+@Sort

PRINT @Str

EXEC sp_ExecuteSql @Str
GO

  其实,以上语句可以简化为:

SELECT TOP 页大小 *

FROM Table1

WHERE (ID NOT IN

(SELECT TOP 页大小*页数 id

FROM 表

ORDER BY id))

ORDER BY ID

  但这个存储过程有一个致命的缺点,就是它含有NOT IN字样。虽然我可以把它改造为:

SELECT TOP 页大小 *

FROM Table1

WHERE not exists

(select * from (select top (页大小*页数) * from table1 order by id) b where b.id=a.id )

order by id

  即,用not exists来代替not in,但我们前面已经谈过了,二者的执行效率实际上是没有区别的。

  既便如此,用TOP 结合NOT IN的这个方法还是比用游标要来得快一些。

  虽然用not exists并不能挽救上个存储过程的效率,但使用SQL SERVER中的TOP关键字却是一个非常明智的选择。因为分页优化的最终目的就是避免产生过大的记录集,而我们在前面也已经提到了TOP的优势,通过TOP 即可实现对数据量的控制。

  在分页算法中,影响我们查询速度的关键因素有两点:TOP和NOT IN。TOP可以提高我们的查询速度,而NOT IN会减慢我们的查询速度,所以要提高我们整个分页算法的速度,就要彻底改造NOT IN,同其他方法来替代它。

  我们知道,几乎任何字段,我们都可以通过max(字段)或min(字段)来提取某个字段中的最大或最小值,所以如果这个字段不重复,那么就可以利用这些不重复的字段的max或min作为分水岭,使其成为分页算法中分开每页的参照物。在这里,我们可以用操作符“>”或“<”号来完成这个使命,使查询语句符合SARG形式。如:

Select top 10 * from table1 where id>200

  于是就有了如下分页方案:

select top 页大小 *

from table1

where id>

(select max (id) from

(select top ((页码-1)*页大小) id from table1 order by id) as T

)

order by id

  在选择即不重复值,又容易分辨大小的列时,我们通常会选择主键。下表列出了笔者用有着1000万数据的办公自动化系统中的表,在以GID(GID是主键,但并不是聚集索引。)为排序列、提取gid,fariqi,title字段,分别以第1、10、100、500、1000、1万、10万、25万、50万页为例,测试以上三种分页方案的执行速度:(单位:毫秒)

页 码
方案1
方案2
方案3

1
60
30
76

10
46
16
63

100
1076
720
130

500
540
12943
83

1000
17110
470
250

1万
24796
4500
140

10万
38326
42283
1553

25万
28140
128720
2330

50万
121686
127846
7168

  从上表中,我们可以看出,三种存储过程在执行100页以下的分页命令时,都是可以信任的,速度都很好。但第一种方案在执行分页1000页以上后,速度就降了下来。第二种方案大约是在执行分页1万页以上后速度开始降了下来。而第三种方案却始终没有大的降势,后劲仍然很足。

  在确定了第三种分页方案后,我们可以据此写一个存储过程。大家知道SQL SERVER的存储过程是事先编译好的SQL语句,它的执行效率要比通过WEB页面传来的SQL语句的执行效率要高。下面的存储过程不仅含有分页方案,还会根据页面传来的参数来确定是否进行数据总数统计。

– 获取指定页的数据

CREATE PROCEDURE pagination3

@tblName varchar(255), — 表名

@strGetFields varchar(1000) = ‘*’, — 需要返回的列

@fldName varchar(255)=”, — 排序的字段名

@PageSize int = 10, — 页尺寸

@PageIndex int = 1, — 页码

@doCount bit = 0, — 返回记录总数, 非 0 值则返回

@OrderType bit = 0, — 设置排序类型, 非 0 值则降序

@strWhere varchar(1500) = ” — 查询条件 (注意: 不要加 where)

AS

declare @strSQL varchar(5000) — 主语句

declare @strTmp varchar(110) — 临时变量

declare @strOrder varchar(400) — 排序类型

if @doCount != 0

begin

if @strWhere !=”

set @strSQL = "select count(*) as Total from [" + @tblName + "] where "+@strWhere

else

set @strSQL = "select count(*) as Total from [" + @tblName + "]"

end

–以上代码的意思是如果@doCount传递过来的不是0,就执行总数统计。以下的所有代码都是@doCount为0的情况

else

begin

if @OrderType != 0

begin

set @strTmp = "<(select min"

set @strOrder = " order by [" + @fldName +"] desc"

–如果@OrderType不是0,就执行降序,这句很重要!

end

else

begin

set @strTmp = ">(select max"

set @strOrder = " order by [" + @fldName +"] asc"

end

if @PageIndex = 1

begin

if @strWhere != ”

set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from [" + @tblName + "] where " + @strWhere + " " + @strOrder

else

set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["+ @tblName + "] "+ @strOrder

–如果是第一页就执行以上代码,这样会加快执行速度

end

else

begin

–以下代码赋予了@strSQL以真正执行的SQL代码

set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["

+ @tblName + "] where [" + @fldName + "]" + @strTmp + "(["+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["+ @fldName + "] from [" + @tblName + "]" + @strOrder + ") as tblTmp)"+ @strOrder

if @strWhere != ”

set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["

+ @tblName + "] where [" + @fldName + "]" + @strTmp + "(["

+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["

+ @fldName + "] from [" + @tblName + "] where " + @strWhere + " "

+ @strOrder + ") as tblTmp) and " + @strWhere + " " + @strOrder

end

end

exec (@strSQL)

GO

  上面的这个存储过程是一个通用的存储过程,其注释已写在其中了。

  在大数据量的情况下,特别是在查询最后几页的时候,查询时间一般不会超过9秒;而用其他存储过程,在实践中就会导致超时,所以这个存储过程非常适用于大容量数据库的查询。

  笔者希望能够通过对以上存储过程的解析,能给大家带来一定的启示,并给工作带来一定的效率提升,同时希望同行提出更优秀的实时数据分页算法。

2006年07月26日

听说处理海量数据还是Oracle最NB

通过索引,分当前和历史表可以提高下性能,但这么大数据量建议用Oracle

选择海量操作的数据库试试…SQL中,一张表过了N百万之后,,感觉效率有些问题…
这方面SQL确实不如Oracle,但是你要考虑Oracle的实施费用…

db数据库

读前先看;无中生有”兄这篇
http://blog.csdn.net/cnlamar/archive/2004/06/30/30641.aspx

这个思路也是他想出来的。

硬件:
———–
P4 2.4 800HZ
DDR400 1G
华硕865
金钻120G 7200

软件:
———–
Windows2003 Server
Sqlserver Sp3
.Net 1.1

测试性能:
————
1000W条 8字段, 每条数据30汉字左右
第一次执行时间: 30~40秒
第二次执行时间: 0~20 ms
第三次执行时间: 0~20 ms
翻至尾页执行时间: 0~20 ms
内存消耗: 220MB 左右

—————————————————————

因为他说的不太明确,就由我再来补充一下。

首先是.net的Cache(缓存),说实话我一直没有用过Cache,但用了之后他的性能越越高出我想象中N倍

从上面测试性能上就能看出,第一次是很慢,已经有足够时间让你去泡咖啡了,不过以后执行的速度

就是以ms来计算,在我的测试机上刷新10次有9次都是0ms,呵呵刚开始我以为计算时间代码写错了。

入正题

——————————-

〈script runat="SERVER"〉
Public Sub RemovedCallback(k As String, v As Object, r As CacheItemRemovedReason)
Cache("page") = Nothing
End Sub
〈/script〉

Dim al As New ArrayList

If Cache("page") Is Nothing Then

   onRemove = New CacheItemRemovedCallback(AddressOf RemovedCallback)

   sql = "select id from content order by id desc"

   cm = New SqlCommand(sql,conn)

   dr = cm.ExecuteReader()

   Do While dr.Read

   al.Add(dr("id"))

   Loop

   dr.Close

   cm.Dispose()

   Cache.Add("page",al,New CacheDependency(Server.MapPath("cache.txt")),DateTime.Now.AddYears(1),TimeSpan.Zero,CacheItemPriority.High,onRemove)
Else

   al = Cache("page")
End If

——————————-

上面只是一个很简单的代码,从数据量读取所有id数据放入 arraylist 中,再将 arraylist缓存起来,由

于数据库是1000W的记录,运行起来相当之慢,而且cache是占用内存的,所以第一次运行不仅速度慢而且还

很费内存。ID字符是从 1 – 1000W 的,在内存中存这么大的数库真是要命啊,没有512M的内存就别干这事了。

If Cache("page") Is Nothing Then 从这句可以看出第一次加载数据后一直到cache过期或应用程序结束之前都不

会再次加载代码了。那么cache中存那些id有什么用呢。 实际上你分页的时候就是读取cache中的id然后再去

数据库中查找相对应的记录。如果你以前没做过海量分页,你大可在1000W数据上count一下,我反正是用了35

秒才统计完成的,所以cache运用在这里的好处就显而易见。

如果记算分页呢: 从上面的代码来看 al 是一个 arrarlist,那么我们这样来认算他的数据总数,al.count。好了思路

和一些代码提供给大家,剩下的要自已解决了。数据同步的问题写触发器也不行,因为一虽改动cache每次都要

去加载那1000W条数据,这个时间就是第一次运行的时间,无中生有的方法是更新数据的时候直接更新 arraylist,

也就是对arraylist执行 add 这些操作,不过如果直接改运数据库的话程序就不知道发生了这个事情,那么数据也

不完整,我跟他讨论过这个事情,不过他太;”;”忙;”;”了:() 哎!!!………….哈哈

说到这也差不多了。 good luck

posted on 2004-06-30 20:59 GXBLOG 阅读(1807) 评论(16) 编辑 收藏

评论
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
1、为什么不用;插入代码”来格式化代码呢?
2、这样一条条dr.read,速度我有所保留,我应该可以写一个速度快一些的代码。
2004-06-30 21:10 | unruledboy(灵感之源)
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
我认为你这样做不好

你想想看,要是客户第一次查询时,等待的时间可以泡一杯咖啡,在结果出来后,那这个客户还会继续查询吗?

其实在实际应用中,大量数据的显示,客户一般也只会看前几页的,我认为应该提高显示速度,可以先读取前几页数据,先让客户阅读
2004-06-30 22:40 | aierong
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
呵呵。第一次就是第一次。。以后就是飞速。

.net第一次运行还不是慢。。。客户访问的时候那肯定不是第一次了。

to:灵感之源 你说的是什么意思,我不懂啊。能否说清楚一点
2004-06-30 23:49 | GXBLOG
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
我是说,你这样操作来记录ID,速度是不算快的,我应该可以写一个速度提高50%的。
2004-07-01 09:03 | unruledboy(灵感之源)
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
初始化Cache的动作可以放在Application Start里面吧

另外你只缓存ID,就没所谓害怕数据更新的问题了,因为ID 一般不会改变的吧?
2004-07-01 09:14 | ServerForAllCoder
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
to 灵感之源,你有好的想法不妨拿出来分享啊。
2004-07-01 12:10 | gxboy
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
话是不错,可是1000w条数据我怎么知道我的要的那一条在第几页???

2004-07-01 12:16 | 33
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
这个我也试过,因为两个问题不好解决就放弃了。
1,排序问题
2,查找问题
2004-07-01 12:56 | billy
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
排序问题 怎么不好解决??easy啊

查找?这个还没办法,有办法效率也不高。
2004-07-01 16:23 | gxboy
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
你先参看一下:http://www.codeproject.com/aspnet/PagingLarge.asp
2004-07-01 19:59 | unruledboy(灵感之源)
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
我最近实在太忙,没有时间写代码
2004-07-02 09:21 | unruledboy(灵感之源)
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
我看没有啥意思,也不太实用!还不如都在数据库里处理效果好。
2004-07-02 10:13 | dannyr
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
在Application_Start时加载到Application对象中好像是不错的主意。
2004-07-15 11:27 | begincsdn
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
强大的服务器是.net应用程序之首选。
在玩.net之前。你先得学习数据库的相关要领。
要点就在减少客户端与服务器之间的交换数据。加强数据库的自处理能力。
.net程序会自动在服务器上建立高速缓存。只要服务器够快,CPU缓存较多。别说是1000条记录,就是上万条记录也是瞬间完成。只是在编译后首次运行较慢。
少使用前台代码。建议建立.net程序,而不是aspx页面。

祝.net之旅愉快!!!!!!!!!!!!!!!!!!!
偶然到此。若要交流,QQ:4639589
2004-09-07 23:30 | 墨人
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
上面??墨人??是不是绵阳的那个哦。我是个性。

我说的是1000万。不是1000条。
2004-11-15 03:15 | GXBLOG
# re: Asp.Net 海量数据分页 (已测1000W),之学习笔记(一)
配合AspNetPager分页控件,在Asp.net程序中分页是很轻松的事情:http://www.webdiyer.com

最近做某项目的数据库分析,要实现对海量数据的导入问题,就是最多把200万条数据一次导入sqlserver中,如果使用普通的insert语句进行写出的话,恐怕没个把小时完不成任务,先是考虑使用bcp,但这是基于命令行的,对用户来说友好性太差,实际不大可能使用;最后决定使用BULK INSERT语句实现,BULK INSERT也可以实现大数据量的导入,而且可以通过编程实现,界面可以做的非常友好,它的速度也很高:导入100万条数据不到20秒中,在速度上恐怕无出其右者。
但是使用这种方式也有它的几个缺点:
1.需要独占接受数据的表
2.会产生大量的日志
3.从中取数据的文件有格式限制
但相对于它的速度来说,这些缺点都是可以克服的,而且你如果愿意牺牲一点速度的话,还可以做更精确的控制,甚至可以控制每一行的插入。
对与产生占用大量空间的日志的情况,我们可以采取在导入前动态更改数据库的日志方式为大容量日志记录恢复模式,这样就不会记录日志了,导入结束后再恢复原来的数据库日志记录方式。
具体的一个语句我们可以这样写:
alter database taxi
set RECOVERY BULK_LOGGED

BULK INSERT taxi..detail FROM ‘e:\out.txt’
WITH (
?? DATAFILETYPE = ‘char’,
?? FIELDTERMINATOR = ‘,’,
?? ROWTERMINATOR = ‘\n’,
?TABLOCK
)

alter database taxi
set RECOVERY FULL

这个语句将从e:\out.txt导出数据文件到数据库taxi的detail表中。

如果你的硬盘空间小,并且不想设置数据库的日志为最小(因为希望其他正常的日志希望仍然记录),而且对速度要求比较高,并清除所有的数据建议你用turncate table1,因为truncate 是DDL操作,不产生rollback,不写日志速度快一些,然后如果有自增的话,恢复到1开始,而delete会产生rollback,如果删除大数据量的表速度会很慢,同时会占用很多的rollback segments,同时还要记录下G级别的日志 ;当然如果有条件删除比如where time<’2006-3-10′ 怎么办,能不能不记录日志用delete,回答是不行的,SQL Server 引擎在设计上就会对 Delete 操作进行日志记录。至今没有办法强制制定某一些语句不记录到日志中,如果在执行 Delete Table1 where Time < ‘2006-3-10′ 由于涉及的记录比较多,所以日志记录也相应很大(3-4G),如果可行,我建议用以下方式:

选出您所需要保留的记录到新的表。如果您使用 Full Recovery Mode
根据SELECT INTO的记录数,日志可能会比较大
Select * into Table2 From Table1 Where Time > = ‘2006-03-10′

然后直接Truncate Table1。无论何种恢复模式都不会进行日志记录
Truncate table Table1

最后对Table2进行改名为Table1
EC sp_rename ‘Table2′, ‘Table1′

现有问题:
1, 所有记录在一张表上。没有分类
2, 开发时,由于没有考虑这么大量的数据。查询语句放在程序中执行,造成速度过慢
3, 根据关系型数据库的插入过程原理,每插入一次,建一次索引查询,那么,将占用大量的内存与CPU资源,速度也将大大降低。在表中有100条记录的情况下插入与在10000条记录的情况下插入,速度与效率是完全不一样的!
4, 插入与查询是在同一张表里。并发处理数可能峰值有1000多。
5, 根据关系型数据库的查询原理,如果有人要查询记录表,将会是这样的一个数学表达式

一条记录 <=1K

总共100,000条记录,每天2万的增长速度

如果不知条件,任意查询,那将会是这样:
(1K * 100,000)/1024 = 10M

1个人是10M。如果是200个人同时查,那将会是这样:
200*10M = 2000M (约2G)

这样大的数据被数据库中取出来。并下载到本机查看,本来就是很庞大的。
6, 各输入网点的网速很多还是“猫”上网,速度肯定跟不上。
7,服务器中还存放着其它的数据等等。
7, 服务器带宽只是专线8M,就算服务器的CPU能计算得过来,数据也送不出去,就被挤塞了!

由于上述问题,出现的情况如下:
1, 网站服务器硬盘物理烧毁一块。
2, 网站带宽被完全占用,基本难以访问。
3, 网站页面速度极其慢,数据传输效率低。
4, 有些个输入单位由于网速无法响应他的操作,送出的数据包无法返回,已无法完成记录输入。

解决办法(思路):
1, 服务器更新。(硬件上)
2, 网络带宽增加。(硬件上)
3, 把查询放在数据库中进行,使用存储过程,但在百兆网速下,存储过程的应用基本与程序查询没什么明显区别。(软件上)。
4, 插入记录时,使用缓冲表,每10分钟,将缓冲表向主记录表倒一次数据。这样可以缓解主记录表的压力。让主记录表专门应对查询动作(软件上)
5, 查询时,使用文本读出记录,因为基于系统底层的指计移动,查询效率将会提高100倍。但是需要FileObjectSystem组件支持。安全性要考虑。(软件上)

如果不采取措施,会引起的问题:

数据库不堪重负,硬盘会再次烧毁。
服务器CPU一直处在100%满负荷下运行。
程序系统完全崩溃。
数据无法即时插入,无法即时反应。
无法统计与追踪。

各网站无法正常运行。