分享

基于SQL SERVER触发器技术的实现

 TUSHUGUANMM 2010-12-19




 

    在数据库管理系统中,如何保证数据库中的数据完整性是一项重要的课题。数据完整性是指存储在数据库的数据的一致性。主要体现在以下几个方面:实体完整性(Entity Integrity)、域完整性(Domain Integrity)、参照完整性(Referential Integrity)和用户的自定义完整性(User define Integrity)。目前,已有多种方法来解决这个问题。从最基本的数据类型,到多种形式的约束条件,虽然都提出了数据完整性的解决方案,但由于这些方法较为简单,不能解决比较复杂的数据完整性问题。而触发器(Trigger)作为一种高级的技术,可以轻松地解决任何有关保证数据完整性的问题。 
一、在SQL SERVER环境中使用触发器 
   1触发器的工作原理 
触发器是一种特殊类型的存储过程,它与表紧密联系在一起,在对表进行插入、删除和更新时,如该表(也称触发器表)有相应操作类型的触发器,则触发器便会自动触发执行。触发器分为INSERT触发器、DELETE触发器和UPDATE触发器3类。当向触发器表中插入数据时,INSERT触发器将触发执行,新的记录会增加到触发器表和inseted表中;当删除触发器表中的数据时,DELETE触发器将触发执行,被删除的记录会存放到deleted表中;当更新触发器表中的数据时,相当于插入一条新记录和删除一条旧记录,此时UPDATE触发器将触发执行,表中原有的记录存放到deleted表中,修改后的记录插入到inserted表中。其中inserted表和deleted表是两个逻辑表,由系统来维护,不允许用户直接对这两个表进行修改。它们存放于内存中,不存放在数据库中。这两个表的结构总是与被该触发器作用的表的结构相同。触发器工作完成后,与该触发器相关的这两个表也会被删除。deleted表用于存储SQL语言中DELETE和UPDATE语句所影响的行的复本。在执行DELETE或 UPDATE语句时,行从触发器表中删除,并传输到 deleted 表中。deleted 表和触发器表通常没有相同的行;inserted 表用于存储 SQL语言中INSERT 和 UPDATE 语句所影响的行的副本。在一个插入或更新事务处理中,新建行被同时添加到 inserted 表和触发器表中。inserted 表中的行是触发器表中新行的副本。 
  2触发器的实现步骤 
在笔者开发的教材管理系统中,建立了一个教材数据库JCSJK,其中有教材表JCB和教材进出明细表MXB,需要在对MXB进行插入、删除和修改时,动态地修改JCB中对应教材的库存数量。下面举例说明触发器的实现步骤。为减少篇幅,对表结构作了简化处理。以下操作在Microsoft SQL SERVER环境中完成。 
  (1)建立教材表JCB,并定义主键 
CREATE TABLE [dbo].[JCB] ( 
[教材代码] [char] (10) NOT NULL , 
[教材名] [char] (30) NOT NULL , 
[价格] [decimal](18, 2) NOT NULL , 
[库存量] [int] NULL , 
[出版社] [char] (20) NULL  
) ON [PRIMARY]ALTER TABLE [dbo].[JCB] WITH NOCHECK ADD 
CONSTRAINT [PK-JCB] PRIMARY KEY CLUSTERED 
([教材代码] 
) ON [PRIMARY] 
  (2)建立教材进出明细表MXB,并定义主键和外?br> CREATE TABLE [dbo].[MXB] ( 
[教材代码] [char] (10) NOT NULL , 
[日期] [datetime] NOT NULL , 
[教材进] [int] NULL , 
[教材出] [int] NULL , 
[备注] [char] (40) NULL  
) ON [PRIMARY] 
ALTER TABLE [dbo].[MXB] WITH NOCHECK ADD 
CONSTRAINT [PK-MXB] PRIMARY KEY CLUSTERED  
([教材代码], 
[日期] 
) ON [PRIMARY] 
ALTER TABLE [dbo].[MXB] ADD 
CONSTRAINT [FK-MXB-JCB] FOREIGN KEY 
([教材代码]) REFERENCES [dbo].[JCB] ( 
[教材代码]) ON DELETE CASCADE ON UPDATE CASCADE 
图1显示了JCB和MXB两个表的关系。 
 (3)在MXB上建立INSERT触发器 
CREATE TRIGGER MXB-INSE ON [dbo].[MXB] 
FOR INSERT 
ASUPDATE JCB SET 库存量=库存量+ 
(SELECT 教材进-教材出 FROM INSERTED) 
FROM JCB,INSERTEDWHERE JCB.教材代码=INSERTED.教材代码 
(4)在MXB上建立DELETE触发器 
CREATE TRIGGER MXB-DELE ON [dbo].[MXB] 
FOR DELETE 
AS 
UPDATE JCB SET 库存量=库存量- 
(SELECT 教材进-教材出 FROM DELETED) 
FROM JCB,DELETEDWHERE JCB.教材代码=DELETED.教材代码〖HT〗 
  (5)在MXB上建立UPDATE触发器 
CREATE TRIGGER MXB-UPDA ON [dbo].[MXB] 
FOR UPDATE 
AS 
BEGINUPDATE JCB SET 库存量=库存量- 
(SELECT 教材进-教材出 FROM DELETED) 
FROM JCB,DELETED 
WHERE JCB.教材代码=DELETED.教材代码 
UPDATE JCB SET 库存量=库存量+ 
(SELECT 教材进-教材出 FROM INSERTED) 
FROM JCB,INSERTEDWHERE JCB.教材代码=INSERTED.教材代码 
END 
  通过以上步骤,设置了MXB表的3类触发器,当用户对MXB表进行插入、删除和修改时,将根据MXB中教材进出的情况动态地修改JCB中对应教材的库存量。由于在触发器中,涉及到的inserted表和deleted表均存放在内存中,因此,触发器的执行速度较快。 
  3设计触发器的考虑 
  在写触发器代码时需要考虑的一个重要问题就是,引发触发器的语句可以是一个影响单行的语句,也可以是一个影响多行的语句。这在 UPDATE 和DELETE 触发器中很常见,因为这些语句经常作用于多行。而这在 INSERT 触发器中就比较少见,因为基本的 INSERT 语句只添加一行。然而,由于 INSERT 触发器可由 INSERT INTO (table_name) SELECT 语句激发,所以,插入许多行可能导致单个的触发器调用。上面讨论的涉及MXB的3类触发器都是针对影响单行的语句。因此,有必要考虑影响多行的语句,这里对MXB的INSERT触发器进行讨论。 
  (1)可处理多行的MXB上的INSERT触发器 
  如果要进行多行插入,上面示例中的触发器可能就不能正确处理,因为 UPDATE 语句赋值表达式右边的表达式只能是一个值,而不能是一个值列表。因此,该触发器的作用就是获取 inserted 表中任意一行的值,并将其添加到JCB表中特定教材代码值的已有库存量值上。如果某个教材代码值在inserted 表中出现了多次,则可能无法得到预期的结果。为了正确地更新JCB表,触发器就必须适应inserted表中出现多行的可能性。这可以通过 SUM 函数实现,它为 inserted 表中每个教材代码计算教材进出的总计。SUM 函数存放于相关子查询中(SELECT 语句在括号内)。该子查询为 inserted 表中与JCB表的教材代码匹配或相关的每个教材代码返回一个单一值。 
CREATE TRIGGER MXB-INSE ON [dbo].[MXB] 
FOR INSERT 
AS 
UPDATE JCB SET 库存量=库存量+ 
(SELECT SUM(教材进-教材出) FROM INSERTED 
WHERE JCB.教材代码=INSERTED.教材代码) 
WHERE JCB.教材代码 IN 
(SELECT 教材代码 FROM INSERTED) 
  该触发器对单行插入同样适用,不过,使用该触发器时,WHERE 子句中所使用的相关子查询和 IN 运算符需要额外处理,而这对于单行插入来说是不必要的。 
  (2)可区分单行和多行插入的MXB上的INSERT触发器可以通过系统函数@@ROWCOUNT以区分单行插入和多行插入,以使触发器针对不同行数使用最优方法。 
CREATE TRIGGER MXB_INSE ON [dbo].[MXB] 
FOR INSERT 
AS 
IF @@ROWCOUNT=1 
BEGIN 
UPDATE JCB SET 库存量=库存量+ 
(SELECT 教材进-教材出 FROM INSERTED) 
FROM JCB,INSERTED 
WHERE JCB.教材代码=INSERTED.教材代码 
END 
ELSE 
BEGIN 
UPDATE JCB SET 库存量=库存量+ 
(SELECT SUM(教材进-教材出) FROM INSERTED 
WHERE JCB.教材代码=INSERTED.教材代码) 
WHERE JCB.教材代码 IN 
(SELECT 教材代码 FROM INSERTED) 
END 
  二、结论 
  触发器应用于支持企业级商业解决方案时,是一个功能十分强大的工具。它可以用于实现业务规则,可以检查事务,可以在同一表上创建多个触发器来分离代码的功能。另外,通过使用触发器收集的信息,可以提高数据库的性能,可以用来维护那些使用外键所不能实现的复杂参数完整性。因此,对于维护数据表之间一致性,保持数据的相关完整性的情况,触发器应作为首先考虑的技术。 
储过程中返回结果 
从存储过程中返回结果有三种方式: 
1、 返回结果集 
这是客户端应用程序返回结果的最通用的方法。结果集是通过使用SELECT语句选择数据产生的。结果集可以从永久表、临时表或局部变量中产生。将结果返回到另一个存储过程不是一种有效的方法。存储过程不能访问另一个存储过程建立的结果集。 
例如从永久表中返回结果集: 
USE pubs 
GO 
CREATE PROCEDURE ap_CreateResultFromPermtable 
AS 
SELECT au_iname FROM authors 
GO 
例如从局部变量中创建结果集: 
USE pubs 
GO 
CREATE PROCEDURE ap_CreateResultFromVariable 
AS 
DECLARE @au_iname char(20) 
SELECT @au_iname = au_iname FROM authors 
WHERE au_id = ‘172-32-1176’ 
SELECT @au_id 
GO 
2、 设置OUTPUT参数的值 
输出参数经常用来从存储过程中检索出结果。如果某个参数在传输到存储过程中时被定义成OUTPUT,则对该参数的任何修改在退出存储之后仍然有效。 
例如: 
USE pubs 
GO 
CREATE PROCEDURE ap_SetOutputVar @count integer OUTPUT 
AS 
SELECT @count = count(*) FROM authors 
GO 
从输出参数中检索出值: 
USE pubs 
GO 
CREATE PROCEDURE ap_GetOutputVar 
AS 
DECLARE @num integer 
EXECUTE ap_SetOutputVar @num OUTPUT 
PRINT “the count is”+convert(char,@num) 
GO 
? 将游标使用成OUTPUT参数。游标可以使用OUTPUT(输出)参数,但不能使用成输入参数。也就是说,游标可以作为结果返回,但却不能传输到过程中去。当游标被用作参数时,需要限定其为OUTPUT和VARYING。VARYING关键字指出该结果集要用来支持输出参数。这样就提供了将结果集返回到调用过程的能力。 
例如: 
USE pubs 
GO 
CREATE PROCEDURE GetTitleCount @count_cursor CURSOR VARYING OUTPUT 
AS 
SET @count_cursor = CURSOR 
FOR 
SELECT au_id,count(*) 
FROM titleauthors 
GROUP BY au_id 
OPEN @count_cursor 
GO 
3、 通过RETURN参数返回状态 
这是一种从存储过程返回错误码的方法。存储过程总是返回一个状态值,用户也可以使用RETURN语句返回自己的状态。 
例如: 
USE pubs 
GO 
CREATE PROCEDURE ap_SetReturnStatus 
AS 
DECLARE @count integer 
SELECT @count = count(*) FROM authors 
IF @count = 0 
RETURN(1) 
ELSE 
RETURN (0) 
GO 
例如检索出返回的状态: 
USE pubs 
GO 
CREATE PROCEDURE ap_GetReturnStatus 
AS 
DECLARE @status integer 
EXECUTE @status = ap_SetReturnStatus 
IF @status = 1 
PRINT “No rows found” 
ELSE 
PRINT “successful” 
GO 
在存储过程中进行错误处理 
如同其它程序一样,在存储过程中进行错误处理是非常重要的。系统变更@@error在执行每一个Transact SQL语句之后都会得到一个值。对于成功的执行,@@error的值为0,如果出现错误,则@@error中将包含错误信息。@@error系统变量对存储过程的错误处理是非常重要的。 
注意:为了防止错误,@@error所能设置的值在sysmessages表的“error”中反映了出来。 
在存储过程中的错误有两种类型: 
1、 数据库相关的错误 
这些错误是由数据库的不一致性引起的,系统使用非0的@@error值表示特定的数据库问题。在Transact SQL执行之后,可以通过@@error获得所出现的错误。如果发现@@error不为0,则必须采取必要的行动,大多数情况下,存储将不再继续进行处理而返回。下面的示例展示了典型的获取数据库错误的方法。该过程将错误代码放置到输出变量中,这样,调用程序就能够访问到。 
USE pubs 
GO 
CREATE PROCEDURE ap_TrapDatabaseError @return_code integer OUTPUT 
AS 
UPDATE authors SET au_iname = “Jackson” 
WHERE au_iname = “Smith” 
IF @@error <> 0 
BEGIN 
SELECT @return_code = @@error 
RETURN 
END 
ELSE 
@return_code = 0 
GO 
2、 业务逻辑错误 
这些错误是由于违反了业务规则而引起的。要获取这些错误,首先需要定义业务规则,基于这些规则,需要在存储过程中增加必要的错误检测代码。人们经常使用RAISERROR语句通报这些错误。RAISERROR提供了返回用户定义错误及将@@error变量设置成用户定义错误号的能力。错误消息可以被动态地建立,或者基于错误号从“sysmessages”表中检索到。一旦出现了错误,错误就会以一种服务器错误消息的方式返回到客户机。下面是RAISERROR命令的语法: 
RAISERROR (msg_id | msg_str, severity, state 
[, argument ][,…n])) 
[WITH options] 
Msg_id指明用户定义消息的id,该消息存储在“sysmessages”系统表中。 
Msg_str用于动态创建消息的消息字符串。这与C语言中的“printf”非常相似。 
Severity定义用户赋值的错误消息严重程度。 
State是从1到127的任意整数值,它表示错误的调用状态信息。负数的state值将缺省为1。 
OPTIONS指明错误的定制选项。OPTIONS的有效值如下: 
1) LOG。 
将错误记录到服务器错误日志和NT事件日志中。该选项需要消息带有从19到25的严重程度。而只有系统管理员才能发出这种消息。 
2) NOWAIT。 
将消息立即发送到客户端服务器。 
3) SETERROR。 
不管其严重级别如何,将@@error的值设置为msg_id或5000。 
远程过程调用 
SQL Server提供了调用驻留在不同服务器上的存储过程的能力。调用这样的存储过程称谓远程存储过程调用。为了使得调用能从一个SQL Server转移到另一个服务器,两个服务器应该相互定义成对方的有效远程服务器。 
设置远程服务器的配置: 
? 扩展某个服务器的组。 
? 右击该服务器并点击“Properties”。 
? 设置选项“Allow other SQL Servers to connect remotely to this SQL server via RPC”。 
? 设置“Query time out”选项的值,该值指定从一个查询处理返回所能等待的秒数。缺省值为0,表示允许无限的等待时间。 
? 设置完成配置选项之后,点击“OK”。 
? 重新启动服务器之后,修改将会生效。 
? 在另一台远程服务器上重复相同的步骤。 
调用远程存储过程需要指明服务器的名称,后带数据库的名称和拥有者的名称。下面是在不同的服务器(Server2)上调用一个存储过程的示例。 
Exec server2.pubs.dbo.myproc 
豆豆的后话: 
这里只是粗浅的介绍了SQL Server常用的知识,对象也是基于SQL Server数据库编写应用程序的编程人员,而非数据库管理者。但对于应用程序编程者,了解数据库的管理也是非常有用的。建议在以后的时间自行去了解数据库的管理,这对于优化程序也是相当有用的。 
 
查看锁信息 
create table #t(req_spid int,obj_name sysname) 
declare @s nvarchar(4000) 
,@rid int,@dbname sysname,@id int,@objname sysname 
declare tb cursor for 
select distinct req_spid,dbname=db_name(rsc_dbid),rsc_objid 
from master..syslockinfo where rsc_type in(4,5) 
open tb 
fetch next from tb into @rid,@dbname,@id 
while @@fetch_status=0 
begin 
set @objname=name from ['+@dbname+']..sysobjects where id=@id' 
exec sp_executesql @s,N'@objname sysname out,@id int',@objname out,@id 
insert into #t values(@rid,@objname) 
fetch next from tb into @rid,@dbname,@id 
end 
close tb 
deallocate tb 
select 进程id=a.req_spid 
,数据库=db_name(rsc_dbid) 
,类型=case rsc_type when 1 then 'NULL 资源(未使用)' when 2 then '数据库' 
when 3 then '文件' 
when 4 then '索引' 
when 5 then '表' 
when 6 then '页' 
when 7 then '键' 
when 8 then '扩展盘区' 
when 9 then 'RID(行 ID)' 
when 10 then '应用程序' 
end 
,对象id=rsc_objid 
,对象名=b.obj_name 
,rsc_indid 
from master..syslockinfo a left join #t b on a.req_spid=b.req_spid 
go 
drop table #t 
 

Transact-SQL 参考 
sysobject
在数据库内创建的每个对象(约束、默认值、日志、规则、存储过程等)在表中占一行。只有在 tempdb 内,每个临时对象才在该表中占一行。 
列名 数据类型 描述 
name sysname 对象名。 
Id int 对象标识号。 
xtype char(2) 对象类型。可以是下列对象类型中的一种: C = CHECK 约束 
D = 默认值或 DEFAULT 约束 F = FOREIGN KEY 约束 
L = 日志 
FN = 标量函数 
IF = 内嵌表函数 
P = 存储过程 
PK = PRIMARY KEY 约束(类型是 K) 
RF = 复制筛选存储过程 
S = 系统表 
TF = 表函数 
TR = 触发器 
U = 用户表 
UQ = UNIQUE 约束(类型是 K) 
V = 视图 
X = 扩展存储过程 

uid smallint 所有者对象的用户 ID。 
info smallint 保留。仅限内部使用。 
status int 保留。仅限内部使用。 
base_schema_ 
ver int 保留。仅限内部使用。 
replinfo int 保留。供复制使用。 
parent_obj int 父对象的对象标识号(例如,对于触发器或约束,该标识号为表 ID)。 
crdate datetime 对象的创建日期。 
ftcatid smallint 为全文索引注册的所有用户表的全文目录标识符,对于没有注册的所有用户表则为 0。 
schema_ver int 版本号,该版本号在每次表的架构更改时都增加。 
stats_schema_ 
ver int 保留。仅限内部使用。 
type char(2) 对象类型。可以是下列值之一: 
C = CHECK 约束 
D = 默认值或 DEFAULT 约束 
F = FOREIGN KEY 约束 
FN = 标量函数 
IF = 内嵌表函数 
K = PRIMARY KEY 或 UNIQUE 约束 
L = 日志 
P = 存储过程 
R = 规则 
RF = 复制筛选存储过程 
S = 系统表 
TF = 表函数 
TR = 触发器 
U = 用户表 
V = 视图 
X = 扩展存储过程 

userstat smallint 保留。 
sysstat smallint 内部状态信息。 
indexdel smallint 保留。 
refdate datetime 留作以后使用。 
version int 留作以后使用。 
deltrig int 保留。 
instrig int 保留。 
updtrig int 保留。 
seltrig int 保留。 
category int 用于发布、约束和标识。 
cache smallint 保留。 
查询指定表在那些数据库中存在 
declare @tbname sysname set 客户资料' declare @dbname sysname,@sql nvarchar(4000),@re bit,@sql1 varchar(8000) set @sql1='' 
declare tb cursor for select name from master..sysdatabases 
open tb 
fetch next from tb into @dbname 
while @@fetch_status=0 
begin 
set @re=case when exists(select 1 from [' 
+@dbname+']..sysobjects where xtype=''U'' and name=''' 
+@tbname+''') then 1 else 0 end' 
exec sp_executesql @sql,N'@re bit out',@re out 
if @re=1 set @sql1=@sql1+' union all select '''+@dbname+'''' 
fetch next from tb into @dbname 
end 
close tb 
deallocate tb 
set @sql1=substring(@sql1,12,8000) 
exec(@sql1)   

比较两个数据库的表结构差异 
exec p_comparestructure 'xzkh_model','xzkh_new' 
--*/ if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[p_comparestructure]') and OBJECTPROPERTY(id, N'IsProcedure') = 1) 
drop procedure [dbo].[p_comparestructure] 
GO 
create proc p_comparestructure 
@dbname1 varchar(250), --要比较的数据库名1 
@dbname2 varchar(250) --要比较的数据库名2 
as 
create table #tb1(表名1 varchar(250),字段名 varchar(250),序号 int,标识 bit,主键 bit,类型 varchar(250), 
占用字节数 int,长度 int,小数位数 int,允许空 bit,默认值 varchar(500),字段说明 varchar(500)) 
create table #tb2(表名2 varchar(250),字段名 varchar(250),序号 int,标识 bit,主键 bit,类型 varchar(250), 占用字节数 int,长度 int,小数位数 int,允许空 bit,默认值 varchar(500),字段说明 varchar(500)) --得到数据库1的结构 
exec('insert into #tb1 SELECT 表名=d.name,字段名=a.name,序号=a.colid, 
标识=case when a.status=0x80 then 1 else 0 end, 主键=case when exists(SELECT 1 FROM '+@dbname1+'..sysobjects where xtype=''PK'' and name in (SELECT name FROM '+@dbname1+'..sysindexes WHERE indid in(SELECT indid FROM '+@dbname1+'..sysindexkeys WHERE id = a.id AND colid=a.colid ))) then 1 else 0 end, 
类型=b.name, 占用字节数=a.length,长度=a.prec,小数位数=a.scale, 允许空=a.isnullable, 
默认值=isnull(e.text,''''''),字段说明=isnull(g.[value],'''''') FROM '+@dbname1+'..syscolumns a left join '+@dbname1+'..systypes b on a.xtype=b.xusertype inner join '+@dbname1+'..sysobjects d on a.id=d.id and d.xtype=''U'' and d.name<>''dtproperties'' left join '+@dbname1+'..syscomments e on a.cdefault=e.id left join '+@dbname1+'..sysproperties g on a.id=g.id and a.colid=g.smallid 
order by a.id,a.colorder') --得到数据库2的结构 
exec('insert into #tb2 SELECT表名=d.name,字段名=a.name,序号=a.colid, 
标识=case when a.status=0x80 then 1 else 0 end, 主键=case when exists(SELECT 1 FROM '+@dbname2+'..sysobjects where xtype=''PK'' and name in (SELECT name FROM '+@dbname2+'..sysindexes WHERE indid in(SELECT indid FROM '+@dbname2+'..sysindexkeys WHERE id = a.id AND colid=a.colid ))) then 1 else 0 end, 
类型=b.name, 占用字节数=a.length,长度=a.prec,小数位数=a.scale, 允许空=a.isnullable, 
默认值=isnull(e.text,''''''),字段说明=isnull(g.[value],'''''') 
FROM '+@dbname2+'..syscolumns a left join '+@dbname2+'..systypes b on a.xtype=b.xusertype 
inner join '+@dbname2+'..sysobjects d on a.id=d.id and d.xtype=''U'' and d.name<>''dtproperties'' 
left join '+@dbname2+'..syscomments e on a.cdefault=e.id left join '+@dbname2+'..sysproperties g on a.id=g.id and a.colid=g.smallid order by a.id,a.colorder') 
--and not exists(select 1 from #tb2 where 表名2=a.表名1) 
select 比较结果=case when a.表名1 is null and b.序号=1 then '库1缺少表:'+b.表名2 
when b.表名2 is null and a.序号=1 then '库2缺少表:'+a.表名1 when a.字段名 is null and exists(select 1 from #tb1 where 表名1=b.表名2) then '库1 ['+b.表名2+'] 缺少字 段:'+b.字段名when b.字段名 is null and exists(select 1 from #tb2 where 表名2=a.表名1) then '库2 ['+a.表名1+'] 缺少字段:'+a.字段名 when a.标识<>b.标识 then '标识不同' when a.主键<>b.主键 then '主键设置不同' when a.类型<>b.类型 then '字段类型不同' when a.占用字节数<>b.占用字节数 then '占用字节数' when a.长度<>b.长度 then '长度不同' when a.小数位数<>b.小数位数 then '小数位数不同' when a.允许空<>b.允许空 then '是否允许空不同' 
when a.默认值<>b.默认值 then '默认值不同' when a.字段说明<>b.字段说明 then '字段说明不同' else '' end, * from #tb1 a full join #tb2 b on a.表名1=b.表名2 and a.字段名=b.字段名 
where a.表名1 is null or a.字段名 is null or b.表名2 is null or b.字段名 is null or a.标识<>b.标识 or a.主键<>b.主键 or a.类型<>b.类型 or a.占用字节数<>b.占用字节数 or a.长度<>b.长度 or a.小数位数<>b.小数位数 or a.允许空<>b.允许空 or a.默认值<>b.默认值 or a.字段说明<>b.字段说明 order by isnull(a.表名1,b.表名2),isnull(a.序号,b.序号)--isnull(a.字段名,b.字段名) go 
sql 系统存储过程xp_cmdshell使用 
xp_cmdshell 
以操作系统命令行解释器的方式执行给定的命令字符串,并以文本行方式返回任何输出。授予非管理用户执行 xp_cmdshell 的权限。 
说明 在 Microsoft? Windows? 95 或 Microsoft Windows 98 操作系统中执行 xp_cmdshell 时,将不把 xp_cmdshell 的返回代码设置为唤醒调用的可执行文件的进程退出代码。返回代码始终为 0。 
语法 
xp_cmdshell {'command_string'} [, no_output] 
参数 
'command_string' 
是在操作系统命令行解释器上执行的命令字符串。command_string 的数据类型为 varchar(255) 或 nvarchar(4000),没有默认值。command_string 不能包含一对以上的双引号。如果由 command_string 引用的文件路径或程序名称中有空格,则需要使用一对引号。如果使用嵌入空格不方便,可考虑使用 FAT 8.3 文件名作为解决办法。 
no_output 
是可选参数,表示执行给定的 command_string,但不向客户端返回任何输出。 
返回代码值 
0(成功)或 1(失败) 
结果集 
执行下列 xp_cmdshell 语句将返回当前目录的目录列表。 
xp_cmdshell 'dir *.exe' 
行以 nvarchar(255) 列的形式返回。 
执行下列 xp_cmdshell 语句将返回随后的结果集: 
xp_cmdshell 'dir *.exe', NO_OUTPUT 
下面是结果: 
The command(s) completed successfully. 
注释 
xp_cmdshell 以同步方式操作。在命令行解释器命令执行完毕之前,不会返回控制。 
当授予用户执行权限时,用户能在 Microsoft Windows NT? 命令行解释器上执行运行 Microsoft SQL Server? 的帐户有权执行的任何操作系统命令。 
默认情况下,只有 sysadmin 固定服务器角色的成员才能执行此扩展存储过程。但是,也可以授予其他用户执行此存储过程的权限。 
当作为 sysadmin 固定服务器角色成员的用户唤醒调用 xp_cmdshell 时,将在运行 SQL Server 服务的安全上下文中执行 xp_cmdshell。当用户不是 sysadmin 组的成员时,xp_cmdshell 将模拟使用 xp_sqlagent_proxy_account 指定的 SQL Server 代理程序的代理帐户。如果代理帐户不能用,则 xp_cmdshell 将失败。这只是针对于 Microsoft? Windows NT? 4.0 和 Windows 2000。在 Windows 9.x 上,没有模拟,且 xp_cmdshell 始终在启动 SQL Server 的 Windows 9.x 用户的安全上下文下执行。 
说明 在早期版本中,获得 xp_cmdshell 执行权限的用户在 MSSQLServer 服务的用户帐户上下文中运行命令。可以通过配置选项配置 SQL Server,以便对 SQL Server 无 sa 访问权限的用户能够在 SQLExecutiveCmdExec Windows NT 帐户的上下文中运行 xp_cmdshell。在 SQL Server 7.0 中,该帐户称为 SQLAgentCmdExec。现在,不是 sysadmin 固定服务器角色成员的用户将在该帐户上下文中运行命令,而无需再进行配置更改。 
权限 
xp_deletemail 的执行权限默认授予 sysadmin 固定服务器角色的成员,但可以授予其他用户。 
重要 如果为 MSSQLServer 服务选用的 Windows NT 帐户不是本地管理员组的成员,则非 sysadmin 固定服务器角色成员的用户将无法执行 xp_cmdshell。 
示例 
A. 返回可执行文件列表 
下例显示执行目录命令的 xp_cmdshell 扩展存储过程。 
EXEC master..xp_cmdshell 'dir *.exe' 
B. 使用 Windows NT net 命令 
下例显示 xp_cmdshell 在存储过程中的使用。下例先用 net send 通知用户 SQL Server 即将关闭,然后用 net pause 暂停服务器,最后用 net stop 关闭服务器。 
CREATE PROC shutdown10 
AS 
EXEC xp_cmdshell 'net send /domain:SQL_USERS ''SQL Server shutting down 
in 10 minutes. No more connections allowed.', no_output 
EXEC xp_cmdshell 'net pause sqlserver' 
WAITFOR DELAY '00:05:00' 
EXEC xp_cmdshell 'net send /domain: SQL_USERS ''SQL Server shutting down 
in 5 minutes.', no_output 
WAITFOR DELAY '00:04:00' 
EXEC xp_cmdshell 'net send /domain:SQL_USERS ''SQL Server shutting down 
in 1 minute. Log off now.', no_output 
WAITFOR DELAY '00:01:00' 
EXEC xp_cmdshell 'net stop sqlserver', no_output 
C. 不返回输出 
下例使用 xp_cmdshell 执行命令字符串,且不向客户端返回输出。 
USE master 
EXEC xp_cmdshell 'copy c:\sqldumps\pubs.dmp \\server2\backups\sqldumps', 
NO_OUTPUT 
D. 使用返回状态 
在下例中,xp_cmdshell 扩展存储过程也给出了返回状态。返回代码值存储在变量 @result 中。 
DECLARE @result int 
EXEC @result = xp_cmdshell 'dir *.exe' 
IF (@result = 0) 
PRINT 'Success' 
ELSE 
PRINT 'Failure' 
E. 将变量内容写入文件 
下例将当前目录内容写入当前服务器目录下名为 dir_out.txt 的文件中。 
DECLARE @cmd sysname, @var sysname 
SET @var = 'dir /p' 
SET @cmd = 'echo ' + @var + ' > dir_out.txt' 
EXEC master..xp_cmdshell @cmd 
SQL 2K 安全指南 
数据库是电子商务、金融以及ERP系统的基础,通常都保存着重要的商业伙伴和客户信息。大多数企业、组织以及政府部门的电子数据都保存在各种数据库中,他们用这些数据库保存一些个人资料,比如员工薪水、个人资料等等。数据库服务器还掌握着敏感的金融数据。包括交易记录、商业事务和账号数据,战略上的或者专业的信息,比如专利和工程数据,甚至市场计划等等应该保护起来防止竞争者和其他非法者获取的资料。数据完整性和合法存取会受到很多方面的安全威胁,包括密码策略、系统后门、数据库操作以及本身的安全方案。但是数据库通常没有象操作系统和网络这样在安全性上受到重视。 
微软的SQL Server是一种广泛使用的数据库,很多电子商务网站、企业内部信息化平台等都是基于SQL Server上的,但是数据库的安全性还没有被人们更系统的安全性等同起来,多数管理员认为只要把网络和 
操作系统的安全搞好了,那么所有的应用程序也就安全了。大多数系统管理员对数据库不熟悉而数据库管理员有对安全问题关心太少,而且一些安全公司也忽略数据库安全,这就使数据库的安全问题更加严峻了。数据库系统中存在的安全漏洞和不当的配置通常会造成严重的后果,而且都难以发现。数据库应用程序通常同操作系统的最高管理员密切相关。广泛SQL Server数据库又是属于“端口”型的数据库,这就表示任何人都能够用分析工具试图连接到数据库上,从而绕过操作系统的安全机制,进而闯入系统、破坏和窃取数据资料,甚至破坏整个系统。 
这里,我们主要谈论有关SQL Server2000数据库的安全配置以及一些相关的安全和使用上的问题。 
在进行SQL Server 2000数据库的安全配置之前,首先你必须对操作系统进行安全配置,保证你的操作系统处于安全状态。然后对你要使用的操作数据库软件(程序)进行必要的安全审核,比如对ASP、PHP等脚本,这是很多基于数据库的WEB应用常出现的安全隐患,对于脚本主要是一个过滤问题,需要过滤一些类似 , ‘ ; @ / 等字符,防止破坏者构造恶意的SQL语句。接着,安装SQL Server2000后请打上补丁sp1以及最新的sp2。 
下载地址是:http://www.microsoft.com/sql/downlo...ds/2000/sp2.asp 
在做完上面三步基础之后,我们再来讨论SQL Server的安全配置。
1、使用安全的密码策略 
我们把密码策略摆在所有安全配置的第一步,请注意,很多数据库账号的密码过于简单,这跟系统密码过于简单是一个道理。对于sa更应该注意,同时不要让sa账号的密码写于应用程序或者脚本中。健壮的密码是安全的第一步! 
SQL Server2000安装的时候,如果是使用混合模式,那么就需要输入sa的密码,除非你确认必须使用空密码。这比以前的版本有所改进。 
同时养成定期修改密码的好习惯。数据库管理员应该定期查看是否有不符合密码要求的账号。比如使用下面的SQL语句: 
Use master 
Select name,Password from syslogins where password is null 
2、使用安全的账号策略。 
由于SQL Server不能更改sa用户名称,也不能删除这个超级用户,所以,我们必须对这个账号进行最强的保护,当然,包括使用一个非常强壮的密码,最好不要在数据库应用中使用sa账号,只有当没有其它方法登录到 SQL Server 实例(例如,当其它系统管理员不可用或忘记了密码)时才使用 sa。建议数据库管理员新建立一个拥有与sa一样权限的超级用户来管理数据库。安全的账号策略还包括不要让管理员权限的账号泛滥。 
SQL Server的认证模式有Windows身份认证和混合身份认证两种。如果数据库管理员不希望操作系统管理员来通过操作系统登陆来接触数据库的话,可以在账号管理中把系统账号“BUILTIN\Administrators”删除。不过这样做的结果是一旦sa账号忘记密码的话,就没有办法来恢复了。 
很多主机使用数据库应用只是用来做查询、修改等简单功能的,请根据实际需要分配账号,并赋予仅仅能够满足应用要求和需要的权限。比如,只要查询功能的,那么就使用一个简单的public账号能够select就可以了。 
3、加强数据库日志的记录。 
审核数据库登录事件的“失败和成功”,在实例属性中选择“安全性”,将其中的审核级别选定为全部,这样在数据库系统和操作系统日志里面,就详细记录了所有账号的登录事件。如图: 
请定期查看SQL Server日志检查是否有可疑的登录事件发生,或者使用DOS命令。 
findstr /C:"登录" d:\Microsoft SQL Server\MSSQL\LOG\*.* 
4、管理扩展存储过程 
对存储过程进行大手术,并且对账号调用扩展存储过程的权限要慎重。其实在多数应用中根本用不到多少系统的存储过程,而SQL Server的这么多系统存储过程只是用来适应广大用户需求的,所以请删除不必要的存储过程,因为有些系统的存储过程能很容易地被人利用起来提升权限或进行破坏。 
如果你不需要扩展存储过程xp_cmdshell请把它去掉。使用这个SQL语句: 
use master 
sp_dropextendedproc 'xp_cmdshell' 
xp_cmdshell是进入操作系统的最佳捷径,是数据库留给操作系统的一个大后门。如果你需要这个存储过程,请用这个语句也可以恢复过来。 sp_addextendedproc 'xp_cmdshell', 'xpsql70.dll' 
如果你不需要请丢弃OLE自动存储过程(会造成管理器中的某些特征不能使用),这些过程包括如下: 
Sp_OACreate Sp_OADestroy Sp_OAGetErrorInfo Sp_OAGetProperty 
Sp_OAMethod Sp_OASetProperty Sp_OAStop 
去掉不需要的注册表访问的存储过程,注册表存储过程甚至能够读出操作系统管理员的密码来,如下: 
Xp_regaddmultistring Xp_regdeletekey Xp_regdeletevalue Xp_regenumvalues 
Xp_regread Xp_regremovemultistring Xp_regwrite 
还有一些其他的扩展存储过程,你也最好检查检查。 
在处理存储过程的时候,请确认一下,避免造成对数据库或应用程序的伤害。 
5、使用协议加密 
SQL Server 2000使用的Tabular Data Stream协议来进行网络数据交换,如果不加密的话,所有的网络传输都是明文的,包括密码、数据库内容等等,这是一个很大的安全威胁。能被人在网络中截获到他们需要的东西,包括数据库账号和密码。所以,在条件容许情况下,最好使用SSL来加密协议,当然,你需要一个证书来支持。 
6、不要让人随便探测到你的TCP/IP端口 
默认情况下,SQL Server使用1433端口监听,很多人都说SQL Server配置的时候要把这个端口改变,这样别人就不能很容易地知道使用的什么端口了。可惜,通过微软未公开的1434端口的UDP探测可以很容易知道SQL Server使用的什么TCP/IP端口了(请参考《深入探索SQL Server网络连接的安全问题》)。 
不过微软还是考虑到了这个问题,毕竟公开而且开放的端口会引起不必要的麻烦。在实例属性中选择TCP/IP协议的属性。选择隐藏 SQL Server 实例。如果隐藏了 SQL Server 实例,则将禁止对试图枚举网络上现有的 SQL Server 实例的客户端所发出的广播作出响应。这样,别人就不能用1434来探测你的TCP/IP端口了(除非用Port Scan)。 
7、修改TCP/IP使用的端口 
请在上一步配置的基础上,更改原默认的1433端口。在实例属性中选择网络配置中的TCP/IP协议的属性,将TCP/IP使用的默认端口变为其他端口。如图: 
9、拒绝来自1434端口的探测 
由于1434端口探测没有限制,能够被别人探测到一些数据库信息,而且还可能遭到DOS攻击让数据库服务器的CPU负荷增大,所以对Windows 2000操作系统来说,在IPSec过滤拒绝掉1434端口的UDP通讯,可以尽可能地隐藏你的SQL Server。 
10、对网络连接进行IP限制 
SQL Server 2000数据库系统本身没有提供网络连接的安全解决办法,但是Windows 2000提供了这样的安全机制。使用操作系统自己的IPSec可以实现IP数据包的安全性。请对IP连接进行限制,只保证自己的IP能够访问,也拒绝其他IP进行的端口连接,把来自网络上的安全威胁进行有效的控制。 
关于IPSec的使用请参看:http://www.microsoft.com/china/tech...ty/ipsecloc.asp 
上面主要介绍的一些SQL Server的安全配置,经过以上的配置,可以让SQL Server本身具备足够的安全防范能力。当然,更主要的还是要加强内部的安全控制和管理员的安全培训,而且安全性问题是一个长期的解决过程,还需要以后进行更多的安全维护。

 





    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多