您的位置:首页 > 数据库

MS SQL Server常见经典问题汇集整理

2009-11-17 22:56 295 查看
MS SQL Server常见经典问题汇集整理

SQL Server 2000中全文检索的使用

引言

微软的SQL Server数据库是一个在中低端企业应用中占有广泛市场的关系型数据库系统,它以简单、方便、易用等特性深得众多软件开发人员和数据库管理人员的钟爱。但SQL Server 7.0以前的数据库系统由于没有全文检索功能,致使无法提供像文本内容查找此类的服务,成为一个小小的遗憾。从SQL Server 7.0起,到如今的SQL Server 2000终于具备了全文检索功能,使用户可以高效地检索存储在数据库char、varchar、text、ntext、nchar、nvarchar等数据类型列中的文本数据。

建立全文索引

在进行全文检索之前,必须先建立和填充数据库全文索引。为了支持全文索引操作,SQL Server 7.0新增了一些存储过程和Transact-SQL语句。使用这些存储过程创建全文索引的具体步骤如下(括号内为调用的存储过程名称):

1. 启动数据库的全文处理功能(sp_fulltext_

database);;

2. 建立全文检索目录(sp_fulltext_catalog);

3.在全文检索目录中注册需要全文索引的表(sp_fulltext_table);

4. 指出表中需要全文检索的列名(sp_fulltext_

column);;

5. 为表创建全文索引(sp_fulltext_table);;

6. 填充全文检索目录(sp_fulltext_catalog)。

下面举例说明如何创建全文索引,在本例中,对Test数据库Book表中Title列和Notes列建立全文索引。

use test //打开数据库

//打开全文索引支持,启动SQL Server的全文搜索服务

execute sp_fulltext_database ‘enable’

//建立全文检索目录ft_test

execute sp_fulltext_catalog ‘ft_test’, ‘create’

为Title列建立全文索引数据元,pk_title为Book表中由主键所建立的唯一索引,这个参数是必需的。

execute sp_fulltext_table ‘book’,‘create’, ‘ft_test’,‘pk_title’

//设置全文索引列名

execute sp_fulltext_column ‘book’, ‘title’, ‘add’

execute sp_fulltext_column ‘book’,‘notes’, ‘add’

//建立全文索引

execute sp_fulltext_table ‘book’, ‘activate’

//填充全文索引目录

execute sp_fulltext_catalog ‘ft_test’, ‘start_full’

至此,全文索引建立完毕。

进行全文检索

SQL Server 2000提供的全文检索语句主要有CONTAINS和FREETEXT。CONTAINS语句的功能是在表的所有列或指定列中搜索:一个字或短语;一个字或短语的前缀;与一个字相近的另一个字;一个字的派生字;一个重复出现的字。

CONTAINS语句的语法格式为:

CONTAINS({column | *}), <contains_search

_condition> )

其中,column是搜索列,使用“*”时说明对表中所有全文索引列进行搜索。Contains_search_

condition 说明CONTAINS语句的搜索内容,其语法格式为:

{<simple_term>|<prefix_term>|<proximity_term>|<generation_term>|<weighted_term>}[{{AND|AND NOT|OR}<contains_search_condition>}] [...n]

下面就simple_term和prefix_term参数做简要说明:

simple_term是CONTAINS语句所搜索的单字或短语,当搜索的是一个短语时,必须使用双引号作为定界符。其格式为:

{‘word’|“ phrase”}

prefix_term说明CONTAINS语句所搜索的字或短语前缀,其格式为:

{“word*” | “phrase*”}

例如,下面语句检索Book表的Title列和Notes列中包含“database”或“computer”字符串的图书名称及其注释信息:

select title, notes

from book

where contains(tilte, ‘database’) or contains(notes,‘database’)

or contains(title,‘computer’) or contains(notes,‘computer’)

FREETEXT语句的功能是在一个表的所有列或指定列中搜索一个自由文本格式的字符串,并返回与该字符串匹配的数据行。所以,FREETEXT语句所执行的功能又称做自由式全文查询。

FREETEXT语句的语法格式为:FREETEXT({column | * },‘freetext_string’)

其中,column是被搜索列,使用“*”时说明对表中的所有全文索引列进行搜索。Freetext_string参数指出所搜索的自由文本格式字符串。

例如,下面语句使用FREETEXT语句搜索Book表中包含“Successful Life”字符串的数据行:

select title, notes

from book

where freetext(*,‘Successful Life’)

SQL Server数据库的六种数据移动方法

1. 通过工具DTS的设计器进行导入或导出
DTS的设计器功能强大,支持多任务,也是可视化界面,容易操作,但知道的人一般不多,如果只是进行SQL Server数据库中部分表的移动,用这种方法最好,当然,也可以进行全部表的移动。在SQL Server Enterprise Manager中,展开服务器左边的+,选择数据库,右击,选择All tasks/Import Data...(或All tasks/Export Data...),进入向导模式,按提示一步一步走就行了,里面分得很细,可以灵活的在不同数据源之间复制数据,很方便的。而且可以另存成DTS包,如果以后还有相同的复制任务,直接运行DTS包就行,省时省力。也可以直接打开DTS设计器,方法是展开服务器名称下面的Data Transformation Services,选Local Packages,在右边的窗口中右击,选New Package,就打开了DTS设计器。值得注意的是:如果源数据库要拷贝的表有外键,注意移动的顺序,有时要分批移动,否则外键主键,索引可能丢失,移动的时候选项旁边的提示说的很明白,或者一次性的复制到目标数据库中,再重新建立外键,主键,索引。
其实建立数据库时,建立外键,主键,索引的文件应该和建表文件分开,而且用的数据文件也分开,并分别放在不同的驱动器上,有利于数据库的优化。
2. 利用Bcp工具
这种工具虽然在SQL Server7的版本中不推荐使用,但许多数据库管理员仍很喜欢用它,尤其是用过SQL Server早期版本的人。Bcp有局限性,首先它的界面不是图形化的,其次它只是在SQL Server的表(视图)与文本文件之间进行复制,但它的优点是性能好,开销小,占用内存少,速度快。有兴趣的朋友可以查参考手册。
3. 利用备份和恢复
先对源数据库进行完全备份,备份到一个设备(device)上,然后把备份文件复制到目的服务器上(恢复的速度快),进行数据库的恢复操作,在恢复的数据库名中填上源数据库的名字(名字必须相同),选择强制型恢复(可以覆盖以前数据库的选项),在选择从设备中进行恢复,浏览时选中备份的文件就行了。这种方法可以完全恢复数据库,包括外键,主键,索引。
4. 直接拷贝数据文件
把数据库的数据文件(*.mdf)和日志文件(*.ldf)都拷贝到目的服务器,在SQL Server Query Analyzer中用语句进行恢复:
EXEC sp_attach_db @dbname = 'test',
@filename1 = 'd:/mssql7/data/test_data.mdf',
@filename2 = 'd:/mssql7/data/test_log.ldf'
这样就把test数据库附加到SQL Server中,可以照常使用。如果不想用原来的日志文件,可以用如下的命令:
EXEC sp_detach_db @dbname = 'test'
EXEC sp_attach_single_file_db @dbname = 'test',
@physname = 'd:/mssql7/data/test_data.mdf'
这个语句的作用是仅仅加载数据文件,日志文件可以由SQL Server数据库自动添加,但是原来的日志文件中记录的数据就丢失了。

5. 在应用程序中定制
可以在应用程序(PB、VB)中执行自己编写的程序,也可以在Query Analyzer中执行,这种方法比较灵活,其实是利用一个平台连接到数据库,在平台中用的主要时SQL语句,这种方法对数据库的影响小,但是如果用到远程链接服务器,要求网络之间的传输性能好,一般有两种语句:
1> select ... into new_tablename where ...
2> insert (into) old_tablename select ... from ... where ...
区别是前者把数据插入一个新表(先建立表,再插入数据),后者是把数据插入已经存在的一个表中,我个人喜欢后者,因为在编程的结构上,应用的范围上,第二条语句强于前者。
6. SQL Server的复制功能
SQL Server提供了强大的数据复制功能,也是最不易掌握的,具体应用请参考相关资料,值得注意的是要想成功进行数据的复制工作,有些条件是必不可少的:
1>SQL Server Agent必须启动,MSDTC必须启动。
2>所有要复制的表必须有主键。
3>如果表中有text或image数据类型,必须使用with log选项,不能使用with no_log选项。
另外max text repl size选项控制可以复制的文本和图像数据的最大规模,超过这个限制的操作将失败。
4>在要进行复制的计算机上,应该至少是隐含共享,即共享名是C$或D$…。
5>为SQL Server代理使用的Windows NT帐号不能是一个本地的系统帐号,因为本地的系统帐号不允许网络存取。
6>如果参与复制的服务器在另外的计算机域中,必须在这些域之间建立信任关系。

Sql Server数据库备份的另类解决方案

一、背景
一旦系统正常运行以后,系统维护最主要工作就是数据安全与可恢复性。本方案(以下提到的数据库均指微软的Sql Server7.0或以上数据库)主要探讨数据库备份与恢复。
一般的数据备份解决方案无非是以下三种:(1)、磁带备份;(2)、双机热备份;(3)、手工备份。作为一般的中小型政府部门和企业采用磁带备份,代价太高,性能价格比不高;普遍采用的可能是双机热备份方案,但是用户可能依然不放心,还需要手工备份,把数据存放到一个与外界断绝联系的可控环境中,这种情况是普通存在的。所以作为双机热备份方案的辅助方案或者在条件限制的情况下,作为双机热备份的替代方案,有必要整理出一套手工备份方案。

二、设计思路
Sql Server数据库本身提供非常方便强大的备份功能(DTS),可以以向导的方式引导用户备份到本地局域网的机器或者远程的机器上,但是现在出现一个问题:就是一旦数据库大了的话,本地局域网备份速度可以接受,可是远程备份,尤其是拨号上网,速度就可能慢,一旦时间过长,网络可能断掉,又得重新备份,能否提出一种方案充分利用Sql Server数据库本身已有的备份功能(DTS),同时又解决备份速度慢的问题,考虑到数据库备份文件的可压缩比率非常高,可以直接对备份文件进行压缩操作,是否更有效率?
下面是设计思路,最后定型取决于两种方式效率的高低。

第一步:利用Sql Server本身带有的备份功能(DTS)把数据库全部或者差额定时备份到某个目录,一旦备份成功,这时候在指定的备份目录下有.bak文件存在;
第二步:利用公司自开发的解压缩组件RichZip把.bak文件压缩成另一个文件.zip文件,RichZip的压缩比等同于WinZip;
第三步:通过Http协议下载.zip文件到本地,按照不同的项目和日期保存;
第四步:如果需要恢复,把.zip文件解压缩成.bak文件,然后再用Sql Server的工具把备份文件恢复;

需要实验解决的几个问题:
1、在同一环境下,直接使用Sql Server的备份工具与这种方案所需要的时间哪一个更长?是否在不同量级里面有不同的结果?
2、是自己利用组件开发一个基于http协议的下载程序,还是直接采用其它的共享下载工具是否更有效率?比如说NetAnts(网络蚂蚁)或者其它下载工具。

说明:
1、该方案只在微软平台上做过实际操作:操作系统Window NT4.0或者以上(推荐使用Win2000),数据库Sql Server6.5或者以上(推荐使用Sql7.0)。

三、实际情况

3.1 实验结果

3.1.1 实验一结果:无论在哪种连接环境下,局域网还是拨号上网,直接使用Sql Server的备份工具与本方案的效率差距都比较大,只是由于数据库小时,直接使用Sql Server的备份工具比本方案方便一些。

以下是一些简单的、不完整的实际操作数据,仅供参考。

操作环境:联想56K调制解调器,上网速度52K,通过SysGate上网,平均3K左右。

库名     备份文件大小 压缩文件大小      下载时间
              Sql备份工具 本方案
SoftEnterPrise 27970Kb   2109Kb(13.26倍)  没有耐心等待,强行中断 12分钟
SoftProduct   10265Kb   739Kb(13.89倍) 18分钟 3分钟
SoftProductHz  11948Kb   930Kb(12.85倍)   25分钟 5分钟

实际操作过程中是使用网络蚂蚁下载的,三个库的备份文件一共50MB,压缩后一共不到4MB;使用Sql备份工具,至少需要一个小时左右,而使用要方案最多不超过20分钟,这中间的效率是不可比拟的,还不包括在使用Sql备份工具时如果断网造成的延时。

3.1.2 实验二结果:建议使用专门的下载工具,如网络蚂蚁或者其它下载工具,是基于如下考虑:A、专门的下载工具功能强大,提供断点续传、多线程、定时下载等许多功能;B、许多用户都会使用,而且非常熟练,不需要再培训;C、比较稳定,如果自己要开发下载程序的话,一个是功能不强大,另外需要一段相当长的测试时间,需要投入时间与精力,不合算。如果是因为集成或者产品化的原因,可以考虑做一个相对简单的下载程序,与其它应用结合,或者开发一个管理备份文件的程序,管理起来比较方便。

3.2 服务器配置及源码
3.2.1 服务器端配置
3.2.1.1 Sql Server的配置:
-先建立Device(设备);
-然后备份具体的数据库到Device(设备)中,可以选择备份的时间及备份的方式;
重复上述操作,直到做好所有需要备份的数据库配置。
注意事项:
1、 备份文件存在的目录不要让用户能通过Http协议访问到;
2、 根据实际需要选择全额备份还是差额备份以及定时操作;
3、 如果系统备份以后,备份目录下就会出现.bak文件。
3.2.1.2 虚拟目录的配置
-为备份系统建立一个虚拟目录,如BackUp,一定要加上挑战反应,不允许匿名访问,这样访问时就需要输入系统管理员的用户名和密码,增强安全性。
3.3 源码
实现思路:为了保护数据的安全性,在3.2.1.2中要把备份系统的虚拟目录设成不允许匿名访问,需要系统管理员的密码;另外在3.2.1.1注意事项中1提到的不让备份文件存在的目录让用户能通过Http协议访问到,需要在生成下载文件后临时生成一个虚拟目录,下载完毕后再删除掉,确保安全。
一共包括四个文件,BackSet.asp、BuildvDir.asp、DelvDir.asp、BackList.asp。
-BackSet.asp:选择要建立临时下载虚拟目录的站点,同时临时给定下载虚拟目录的名称;
- BuildvDir.asp:根据BackSet.asp文件给定的站点和虚拟目录名称,建立虚拟目录;
-DelvDir.asp:删除在BackSet.asp文件给定的站点中的虚拟目录;
-BackList.asp:压缩备份文件,建立对应的被压缩后备份文件的下载链接;,
3.3.1 BackSet.asp文件解释
3.3.1.1 源码

<body bgcolor="#EEEEEC">
<center>
<font size=4>生成或者删除**项目备份数据库虚拟目录</font>
</center>
<hr size=1>
<br>

<center>
<form name="BackForm" method="Post">
<table border=1 width=70%>
<tr>
<td width=40% align=center>
<font size=2>
请选择要使用的站点名称:
</font>
</td>
<td>
<select Name="WebSiteIndex">
<%
dim ObjServer
dim obj3W
dim sServerName
set ObjService=GetObject("IIS://" & Request.ServerVariables("SERVER_NAME") & "/W3SVC")
for each obj3w in objservice
if IsNumeric(obj3w.Name) then
sServerName=Obj3w.ServerComment
if Instr(sServerName,"管理 WEB 站点") =0 then
sServerName=obj3w.Name & "-" & Obj3w.ServerComment
%>
<option value="<%=obj3w.Name%>"><%=sServerName%>
<%
end if
end if
next
set ObjService=Nothing
%>
</select>
</td>
</tr>
<tr>
<td width=40% align=center>
<font size=2>
请输入备份操作的虚拟目录名称:
</font>
</td>
<td>
<input Type=text name="vDir" value="SrrdBackUp" size=42>
</td>
</tr>
</table>
<br>
<br>
<input type=button Name="Build" value="生成虚拟目录">
<input type=button Name="Del" value="删除虚拟目录">
</form>
</center>
</body>
</html>

<script language=vbs>
Sub Build_Onclick()
'生成临时虚拟目录
if Trim(document.BackForm.vDir.value)="" then
msgbox "请输入虚拟目录名称!",16,"错误"
exit sub
else
document.BackForm.action="BuildvDir.asp"
document.BackForm.submit
end if
end sub
Sub Del_Onclick()
'删除临时生成的虚拟目录
if Trim(document.BackForm.vDir.value)="" then
msgbox "请输入虚拟目录名称!",16,"错误"
exit sub
else
document.BackForm.action="DelvDir.asp"
document.BackForm.submit
end if
end sub
</script>


SQL脚本生成的一些BUG

Sql Server 的脚本生成有不少漏洞,经常由它生成的脚本运行起来却有错误。下面举例说明:

1、并没有根据sysdenpends的依赖关系生成SQL代码,而是根据“优先级”(呵呵,所谓的优先级)来生成。
比如:他认为view的优先级就要比function高。
那么,我写了下面的测试程序,形成如下的依赖关系:fnT1 <-- vwT1 <-- fnT2
就是,view vwT1处于依赖的中间。
------------------------------------
Create function fnT1()
Returns Integer
As
begin
Return 123
end
go

Create view vwT1
As
Select aa=dbo.fnT1()

Go

Create function fnT2()
Returns Table
As
Return (Select * From vwT1)
Go
-------------------------------------
运行到数据库之后,用Enterprise生成SQL代码。(选项不一样,会有所不同,我没有选数据库和用户的)
-----------------------------------------------------------------------------------
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[fnT1]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[fnT1]
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[fnT2]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[fnT2]
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[vwT1]') and OBJECTPROPERTY(id, N'IsView') = 1)
drop view [dbo].[vwT1]
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

Create view vwT1
As
Select aa=dbo.fnT1()

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

Create function fnT1()
Returns Integer
As
begin
Return 123
end

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

Create function fnT2()
Returns Table
As
Return (Select * From vwT1)

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
-----------------------------------------------------------------------------
呵呵,一眼你就可以看出来了,建立view要比建立function先。而不是根据依赖关系建立……
毫无疑问,将会得到如下的错误:(这个错误可真严重!害得我好惨……)
---------------------------------------------------
服务器: 消息 208,级别 16,状态 1,过程 vwT1,行 4
对象名 'dbo.fnT1' 无效。
服务器: 消息 208,级别 16,状态 1,过程 fnT2,行 5
对象名 'vwT1' 无效。
---------------------------------------------------
2、作业脚本。

这个我就不说了,bug还不是很严重,主要是中文“--”注释符的问题,英文版我没有测试过,不过猜想应当没有这个bug。
大家可以试试看。

3、还有一个SP的问题。

大家看过我的精华里面有spGetIDStr和spAnalyseStrList了吧,关系是后者依赖于前者。可是spGetIDStr我并没有调用任何的表。
因此,每当运行Sql Server生成的脚本的时候,总是报告(大概是这样的信息):
------------------------------------------------------------------------
spGetIDStr并不存在,无法在sysdepends里建立依赖关系,存储过程spAnalyseStrList仍然建立。
--------------------------------------------------------------------------
无论我手工修改他的建立顺序还是什么的,用它生成的脚本就是有错。呵呵,这个破微软!
这里,再看看第三个bug,看我下面的测试程序:
(原理:当sp没有对表或视图等数据库对象有依赖关系的时候,sp被别的sp引用的时候也将无法建立依赖关系)
形成依赖关系:spB1 <--- spA1
------------------------------------------------------------------------
Create Proc spB1
As
Return 11

Go

Create proc spA1
As
Begin
Declare @i int
Exec @i=spB1
Return @i*2
End
Go
-------------------------------------------------------------------------
生成的脚本就为:
-------------------------------------------------------------------------

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[spA1]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[spA1]
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[spB1]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[spB1]
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

Create proc spA1
As
Begin
Declare @i int
Exec @i=spB1
Return @i*2
End

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

Create Proc spB1
As
Return 11

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

视图分可更新视图与不可更新视图,
如果视图是从一个表得来的,一般来说各种大型数据库都支持,
如果视图是从多个表得来的,一次只能修改其中的一个表MSSQL支持,ORACLE也可以。

修改视图一般使用instead of触发器。
create table a1(id int,name varchar(20))
craete table a2(id int,age tinyint)
go
create view v_a1_a2(id,name,age)
as
select a.id,a.name,b.age from a1 a,a2 b where a1.id=a2.id
go
create trigger tri_ins_v1
instead of insert
as
begin
insert into a1 select id,name from inserted
insert into a2 select id,age from inserted
end

触发器的几种应用

摘 要 列举了触发器的几种代表性应用:数据分散—集中式模型的设计,历史数据的导出,应用系统间的数据接口。并对如何设计这些触发器进行了探讨。
  关键词 触发器,数据分散—集中模型,历史数据导出,数据接口

1 引言
  在大型数据库设计中,会经常用到触发器。它的特点是:一旦被定义,就存在于后台数据库系统(server,服务器方)中,并会在相应条件下自动地隐式执行,从而使得它的设计既与前台(client,客户机方)的平台无关,又免除了前台相关的数据操作设计。
  在文献[1]中,列举了触发器的几种应用:审计;复杂的完整性约束;复杂的安全性授权;事件登录;列值导出;分布式数据库中表复制。

2 触发器的另外几种应用
2.1 数据分散——集中式模型设计
  在实际开发过程中,经常遇到这样的数据维护要求:单位由多个部门组成,要求各部门只能维护本部门的数据,但另一方面,又需要将分散到各部门的数据集中起来进行汇总,得到本单位的汇总数据。如一个学校有多个系,学校需要各系的成绩汇总;一个工厂有多个生产车间,工厂需要各车间的产量汇总;一个公司有多个销售部门,公司需要各部门的销量汇总等等。
  在这种情况下,如果不使用触发器的话,数据库设计就存在困难:
  . 如果为每个部门都建立一个表,显然难以得到汇总的数据(在这种情况下,无法利用视图机制);
  . 如果所有的部门都共享一个表的话(这时,这张表中的数据实际就是汇总的数据),因为每个部门需要维护数据,所以都对这个表有修改权,因此在数据安全上难以控制。
  使用触发器的话,上述问题便可迎刃而解:为每个部门建立一个表(该部门的所有权限只限于对此表有修改权),再为汇总数据也建立一个表,然后在每个部门表上建立触发器,使得部门表上有数据更新时,便会对应地更改汇总表中的相关数据(见图1)。

图1 触发器应用于数据分散——集中式模型

  在这种模型中,要注意设计好部门表相关字段的完整性约束,使各部门表内的数据是唯一的,以防止不同部门表出现相同的数据记录,从而导致在汇总表中出现混乱。

2.2 历史数据导出
  数据库中的表只记载最新的数据,而不记载历史数据。但在很多情况下,历史数据的记载与分析反而比现实数据更有意义(这也正是数据仓库与数据库的区别之一),比如学校中学号的变动,工厂定额的更改,公司产品和原材料价格的变化、股票的升跌等等,它们都需要记录历史数据。
  如何使数据库也能记载历史数据呢?使用触发器可以解决这类问题。
  建立这类触发器的步骤是:建立数据表后,再建立对应的历史表(一般而言,历史表在字段组成上是数据表的超集,即在原数据表字段上再增加有关时间的字段),然后在两者之间设立触发器(见图2)。这样,每当数据表有数据变动,触发器便将变动的数据记入历史数据表中,从而达到自动记录历史数据的目的。

图2 历史数据的导出模式

2.3 应用系统间的数据接口
  一个完整的信息系统的建设一般不是一步到位的,往往是分期分批完成,而不同期次的系统往往又会有数据传递,然而由于需求发生变化或是其他原因,不同期次系统的数据库设计在表结构甚至字段上的设计都可能会互不一致(即使是在同一期的开发过程中,由于总体设计或数据字典方面的偏差或不足,或者需要集成多家系统,这种现象也会经常出现)。在不可能重建这些系统的情况下,它们之间的数据能无缝传递吗?换言之,它们之间能够做到无缝连接吗?

  在这种情况下,触发器可以是一种较好的解决方式:建立中间表,中间表的设计符合需方应用系统的设计格式,而它的数据又与供方应用系统的数据保持一致。(见图3)

图3 中间作为不同应用系统间的数据接口

  要注意的一点是:图示应用系统间的数据是单向流动的(即数据传递);如果数据需要双向流动(即数据交换),那么在触发器设计中应有退出机制,以避免发生触发器的递归。

3 结语
  触发器对数据库开发过程中遇到的问题,往往会有独到的解决方法。触发器能使数据库的设计变得简洁和高效。文中的3个例子,代表了触发器3个方面的典型应用

ODBC中的同步与异步执行模式

1.引言
近年来,随着计算机局域网技术的不断发展,计算机体系结构已经发展到复杂而开放的客户机/服务器模式。对于客户机/服务器应用的开发,现在常用的前端开发工具有:VisualBasic、Delphi、PowerBuilder等。它们可通过ODBC接口访问服务器的SQLServer数据库服务器。
VisualBasic、Delphi、PowerBuilder等开发工具在使用ODBC2.0来编写程序时,通常会提供三种方法来进行数据库应用程序的方案设计:
·使用数据控制项
·使用数据库对象变量进行编程
·直接调用ODBC2.0API
在客户机/服务器模式下进行数据库应用程序设计时,仅用前两种方法往往是不够的。因为采用前两种方法,其执行模式对于程序员是透明的,而ODBC2.0访问数据库时存在同步与异步执行模式之分,故容易因设计不当,发生系统死锁。因此,在实际编程序时,我们需要采用第三种方法来解决由同步和异步执行模式所造成的问题。

2.同步和异步执行模式
ODBC2.0访问数据库时,有同步执行模式与异步执行模式之分。
所谓同步执行模式,是指语句在同步执行模式下,将始终保持对程序流的控制,直至程序结束。例如查询操作,客户机上的应用程序在向服务器发出查询操作的指令后,将一直等待服务器将查询结果返回客户机端后,才继续进行下一步操作,如图1所示。
图1同步执行模式
所谓异步执行模式,是指语句在异步执行模式下,各语句执行结束的顺序与语句执行开始的顺序并不一定相同。例如查询操作,客户机上的应用程序在向服务器发出了查询操作的指令后,将立刻执行查询语句的下一条语句,而不需要等到服务器将查询结果返回客户机端后,才继续进行下一步操作。如图2所示。
图2异步执行模式
在一些应用程序开发工具中,在其提供使用数据控制项和数据库对象变量进行编程的同时,并没有很好地考虑到同步执行模式与异步执行模式的重要区别。为了使程序运行速度更快,其语句执行的缺省模式为异步模式。对于一般程序员来说,如果他对同步执行模式与异步执行模式不了解的话,他往往会在对服务器发出一个操作语句(查询或读取一条记录等操作)后,立刻引用服务器返回的执行结果,或者对该结果进行下一步操作;在异步执行模式下,客户机上的后续语句是在该操作语句发出后接着执行的,但由于各种原因,服务器不一定能执行完该操作语句,并在后续语句执行前将结果返回客户机。因此,后续语句在引用前一操作语句的执行结果时,往往会因为该执行结果并不存在而引用了错误的值,造成系统错误或死锁。

3.解决方案
解决上面所提到的问题,可以采取以下两种方案:
①利用ODBC2.0API,将语句执行状态设置为同步执行模式。ODBC2.0API中,函数SQLSetStmtOption()的功能是设置同步或异步执行模式。我们可以采用以下语句,将语句执行状态设置为同步执行模式:iRetCodeΚSQLSetStmtOption(hStmt,SQL-ASYNC-ENABLE,0)
其中,hStmt是一有效的语句句柄,常数SQL-ASYNC-ENABLE是所要设置的选项,参数0表示该选项(即异步执行模式)关闭。如果iRetCode返回SQL-SUCCESS,则表示语句执行状态已被设置为同步执行模式。
②利用ODBC2.0API,将语句执行状态设置为异步执行模式,然后在程序中不断查询一个操作语句是否已经执行完毕。
ODBC2.0API中共有20多个函数支持异步执行,如上页表所示。
这些函数第一次调用后,将返回值SQL-STILL-EXECUTING,这时应用程序将继续执行后续语句。过了一段时间后,应该再次调用原函数,而且要注意:实参数应传入与第一次调用时相同的语句句柄,其他参数也应一样(但会被忽略)。如果函数返回值为SQL-SUCCESS,则表明该语句已经执行完毕;如果函数返回SQL-STILL-EXECUTING,则表明该语句仍在执行中。
我们可以用一个简单的例子说明如下:
iRetCodeΚSQLSetStmtOption(hStmt,SQL-ASYNC-ENABLE,1)
′置语句执行模式为异步执行模式
iRetCodeΚSQLExecDirect(hStmt,″SELECT*FROMemployees″,23)
......′执行其他操作
iRetCodeΚSQLExecDirect(hStmt,″SELECT*FROMemployees″,23)
′判断SQLExecDirect()是否已执行完毕
If(iRetCodeΚSQL-STILL-EXECUTING)Then
......′该语句未执行完,继续执行其他操作
Else
If(iRetCodeΚSQL-SUCCESS)Then
......′该语句已执行完,可对语句操作结果进行处理
EndIf
EndIf
同步执行模式可以简化程序编制的复杂性,对ODBC2.0API不十分熟悉的程序员,可以不用过多地了解比较复杂的ODBC2.0API,而只需使用数据控制项和数据库对象变量来编写应用程序,使开发效率大大提高,但程序运行速度比不上异步执行模式的速度。
异步执行模式虽然在编程序时十分复杂,但在这种模式下可以进行多任务并行执行,使执行效率大大提高。
我们在编制应用程序时,应根据自身的情况,对这两种模式的使用进行划分,以便既提高程序运行的安全可靠性,又提高程序执行的效率。
大型数据库设计原则

一个好的数据库产品不等于就有一个好的应用系统,如果不能设计一个合理的数据库模型,不仅会增加客户端和服务器段程序的编程和维护的难度,而且将会影响系统实际运行的性能。一般来讲,在一个MIS系统分析、设计、测试和试运行阶段,因为数据量较小,设计人员和测试人员往往只注意到功能的实现,而很难注意到性能的薄弱之处,等到系统投入实际运行一段时间后,才发现系统的性能在降低,这时再来考虑提高系统性能则要花费更多的人力物力,而整个系统也不可避免的形成了一个打补丁工程。笔者依据多年来设计和使用数据库的经验,提出以下一些设计准则,供同仁们参考。

命名的规范
---- 不同的数据库产品对对象的命名有不同的要求,因此,数据库中的各种对象的命名、后台程序的代码编写应采用大小写敏感的形式,各种对象命名长度不要超过30个字符,这样便于应用系统适应不同的数据库。
游标(Cursor)的慎用
---- 游标提供了对特定集合中逐行扫描的手段,一般使用游标逐行遍历数据,根据取出的数据不同条件进行不同的操作。尤其对多表和大表定义的游标(大的数据集合)循环很容易使程序进入一个漫长的等特甚至死机,笔者在某市《住房公积金管理系统》进行日终帐户滚积数计息处理时,对一个10万个帐户的游标处理导致程序进入了一个无限期的等特(后经测算需48个小时才能完成)(硬件环境:Alpha/4000 128Mram ,Sco Unix ,Sybase 11.0),后根据不同的条件改成用不同的UPDATE语句得以在二十分钟之内完成。示例如下:
Declare Mycursor cursor for select count_no from COUNT
Open Mycursor
Fetch Mycursor into @vcount_no
While (@@sqlstatus=0)
Begin
If @vcount_no=’’ 条件1
操作1
If @vcount_no=’’ 条件2
操作2
。。。
Fetch Mycursor into @vcount_no
End
。。。
。。。
改为
Update COUNT set 操作1 for 条件1
Update COUNT set 操作2 for 条件2
。。。
。。。

---- 在有些场合,有时也非得使用游标,此时也可考虑将符合条件的数据行转入临时表中,再对临时表定义游标进行操作,可时性能得到明显提高。笔者在某地市〈电信收费系统〉数据库后台程序设计中,对一个表(3万行中符合条件的30多行数据)进行游标操作(硬件环境:PC服务器,PII266 64Mram ,NT4.0 Ms Sqlserver 6.5)。 示例如下:

Create #tmp /* 定义临时表 */
( 字段1
字段2
。。。
)
Insert into #tmp select * from TOTAL where
条件 /* TOTAL中3万行 符合条件只有几十行 */
Declare Mycursor cursor for select * from #tmp
/*对临时表定义游标*/
。。。

索引(Index)的使用原则
---- 创建索引一般有以下两个目的:维护被索引列的唯一性和提供快速访问表中数据的策略。大型数据库有两种索引即簇索引和非簇索引,一个没有簇索引的表是按堆结构存储数据,所有的数据均添加在表的尾部,而建立了簇索引的表,其数据在物理上会按照簇索引键的顺序存储,一个表只允许有一个簇索引,因此,根据B树结构,可以理解添加任何一种索引均能提高按索引列查询的速度,但会降低插入、更新、删除操作的性能,尤其是当填充因子(Fill Factor)较大时。所以对索引较多的表进行频繁的插入、更新、删除操作,建表和索引时因设置较小的填充因子,以便在各数据页中留下较多的自由空间,减少页分割及重新组织的工作。
数据的一致性和完整性
---- 为了保证数据库的一致性和完整性,设计人员往往会设计过多的表间关联(Relation),尽可能的降低数据的冗余。表间关联是一种强制性措施,建立后,对父表(Parent Table)和子表(Child Table)的插入、更新、删除操作均要占用系统的开销,另外,最好不要用Identify 属性字段作为主键与子表关联。如果数据冗余低,数据的完整性容易得到保证,但增加了表间连接查询的操作,为了提高系统的响应时间,合理的数据冗余也是必要的。使用规则(Rule)和约束(Check)来防止系统操作人员误输入造成数据的错误是设计人员的另一种常用手段,但是,不必要的规则和约束也会占用系统的不必要开销,需要注意的是,约束对数据的有效性验证要比规则快。所有这些,设计人员在设计阶段应根据系统操作的类型、频度加以均衡考虑。
事务的陷阱
---- 事务是在一次性完成的一组操作。虽然这些操作是单个的操作,SQL Server能够保证这组操作要么全部都完成,要么一点都不做。正是大型数据库的这一特性,使得数据的完整性得到了极大的保证。
---- 众所周知,SQL Server为每个独立的SQL语句都提供了隐含的事务控制,使得每个DML的数据操作得以完整提交或回滚,但是SQL Server还提供了显式事务控制语句

---- BEGIN TRANSACTION 开始一个事务

---- COMMIT TRANSACTION 提交一个事务

---- ROLLBACK TRANSACTION 回滚一个事务

---- 事务可以嵌套,可以通过全局变量@@trancount检索到连接的事务处理嵌套层次。需要加以特别注意并且极容易使编程人员犯错误的是,每个显示或隐含的事物开始都使得该变量加1,每个事务的提交使该变量减1,每个事务的回滚都会使得该变量置0,而只有当该变量为0时的事务提交(最后一个提交语句时),这时才把物理数据写入磁盘。

数据库性能调整
---- 在计算机硬件配置和网络设计确定的情况下,影响到应用系统性能的因素不外乎为数据库性能和客户端程序设计。而大多数数据库设计员采用两步法进行数据库设计:首先进行逻辑设计,而后进行物理设计。数据库逻辑设计去除了所有冗余数据,提高了数据吞吐速度,保证了数据的完整性,清楚地表达数据元素之间的关系。而对于多表之间的关联查询(尤其是大数据表)时,其性能将会降低,同时也提高了客 户端程序的编程难度,因此,物理设计需折衷考虑,根据业务规则,确定对关联表的数据量大小、数据项的访问频度,对此类数据表频繁的关联查询应适当提高数据冗余设计。
数据类型的选择
---- 数据类型的合理选择对于数据库的性能和操作具有很大的影响,有关这方面的书籍也有不少的阐述,这里主要介绍几点经验。
Identify字段不要作为表的主键与其它表关联,这将会影响到该表的数据迁移。

Text 和Image字段属指针型数据,主要用来存放二进制大型对象(BLOB)。这类数据的操作相比其它数据类型较慢,因此要避开使用。

日期型字段的优点是有众多的日期函数支持,因此,在日期的大小比较、加减操作上非常简单。但是,在按照日期作为条件的查询操作也要用函数,相比其它数据类型速度上就慢许多,因为用函数作为查询的条件时,服务器无法用先进的性能策略来优化查询而只能进行表扫描遍历每行。
---- 例如:要从DATA_TAB1中(其中有一个名为DATE的日期字段)查询1998年的所有记录。
---- Select * from DATA_TAB1 where datepart(yy,DATE)=1998


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: