当前位置:  数据库>sqlserver

如何编写高效的存储过程

    来源: 互联网  发布时间:2014-08-29

    本文导语:  使用存储过程的好处: 1.使用存储过程可以对所执行的SQL语句进行封装,在接口保持不变的情况下不影响调用程序。 2.最大限度的重用已缓存的执行计划。 3.减少网络流量。 4.提供更好的数据库安全控制,防止直接对表的操...

使用存储过程的好处:
1.使用存储过程可以对所执行的SQL语句进行封装,在接口保持不变的情况下不影响调用程序。
2.最大限度的重用已缓存的执行计划。
3.减少网络流量。
4.提供更好的数据库安全控制,防止直接对表的操作。

存储过程的编译占用CPU,因此我们应该防止存储过程不必要的重新编译。
正常的编译发生于:
1.所引用的表中大部分的数据发生了的更改,导致统计信息变化过大。
2.所引用的表的架构被修改,包括添加或取消约束、默认值或规则。
3.明确使用WITH RECOMPILE强制每次执行过程时重新编译或sp_recompile使用过程缓存无效。
4.由于服务器内存不足或长期不使用,使缓存过程被清除。

在以下情况下,过程会被不必要的重新编译:
1.在调用过程时,不指定架构所有者。
这时为了找到正确的缓存计划,SQLServer会按照如下顺序查找过程所属的架构:
①sys
②调用此过程所属于的架构,如果是被其它过程所包含,则首先查询包含过程的架构
③dbo
为了能重新编译此过程,必须要对过程施加编译锁,因此在很多用户并行访问时可能会带来额外的等待时间。可通过sys.dm_exec_requests动态视图或master.dbo.sysprocesses系统表进行观察,如果lastwaittypee出现LCK_M_X,则表示出现了编译锁。

2.过程在临时表上执行了特定操作。
在过程中经常会用到临时表与表变量,一直有种误解是表变量只会存储于内存中。其实如果两者都足够小的话,是不会保存于磁盘中的,但是两者的架构是都会存在于tempdb定义中的。只有在内存不足时,才会把数据存储于磁盘中。以下示例可查看表变量也是存储于tempdb中:
 

代码如下:
DECLARE @employee TABLE(employeeId INT);
INSERT INTO @employee VALUES(1);
SELECT * FROM tempdb.INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME LIKE '%#%';

这时会看到TABLE_NAME有一#开头的记录
选择这两者的主要依据如下:
①对于小数据量的中间表优先使用表变量,反之如果数据量大且被用于连接,则使用临时表。因为在表变量中只能定义主键和约束,如果在进行联结时,必须在要联结的字段上建立索引,从而防止出现哈稀联结而占用过多的资源。联结的三种算法嵌套循环、合并与哈稀所占用的资源依次增加,而前两者的前提是在联结的字段上存在索引时,根据数据量多少而决定的。
②中间表对事务的影响,临时表的回滚会影响上层事务,而表变量不会。
③可见性问题。临时表对下次均可见,而表变量只在所声明的层次可见。

另一个重要的选择依据就是在对临时表会创建相应的统计信息,因此在过程中引用临时表是可能会迫使语句因为引用的表的统计变化而被重新编译多次。以下示例演示了此过程,为了能看到被重新编译的事件,请在跟踪事件中选择SP:Starting、SP:StmtStarting、SP:Recompile 和 SP:Completed,注意SP:StmtStarting与SP:StmtCompleted 事件,最好不要同时包含这两个事件,因为这样会将需要查询的信息量加倍。
 

代码如下:
USE AdventureWorks;
GO
CREATE PROCEDURE GetCustomerOrder
AS
CREATE TABLE #t (SalesOrderID int, CustomerID int)
SELECT * FROM #t
INSERT #t
SELECT SalesOrderID, CustomerID
FROM Sales.SalesOrderHeader
SELECT COUNT(*) FROM #t
WHERE CustomerID = 40
GO
EXEC GetCustomerOrder
 

通过上面的结果我们看到,每次在对临时表进行操作时,都会引起一次过程的重新编译。在查询CustomerID = 40时,可以看到有一SELECT语句正是为了能获得所需的统计信息而发生的。可以使用sp_executesql

来防止这种额外的编译,修改过程如下,再次跟踪事件:
 

代码如下:
USE AdventureWorks;
GO
ALTER PROCEDURE GetCustomerOrder
AS
CREATE TABLE #t (SalesOrderID int, CustomerID int)
SELECT * FROM #t
EXEC sp_executesql N'
INSERT #t
SELECT SalesOrderID, CustomerID
FROM Sales.SalesOrderHeader'
EXEC sp_executesql N'SELECT COUNT(*) FROM #t WHERE CustomerID = @CustomerID',
N'@CustomerID int', @CustomerID = 40
GO
EXEC GetCustomerOrder

此时我们发现只要是使用sp_executesql执行的语句都没有引起语句的重新编译。虽然此处演示的是使用临时表,但对永久表而言同样也存在这种问题。另一种做法是使用KEEP PLAN,不推荐使用。因此,我们应该优先使用sp_executesql,使用它所执行的语句在缓存中只会存在一条语句,而使用EXEC会根据具体的参数为每条不同的语句生成一个缓存计划。从而占用过多的缓存。

3.过程定义中出现DDL和DML的交错。以下示例演示引起重新编译:
 

代码如下:
USE AdventureWorks;
GO
CREATE PROCEDURE GetOrderInfo
AS
-- DDL
CREATE TABLE #CompleteOrder(SalesOrderID int, CustomerID int)
-- DML
SELECT * FROM #CompleteOrder
-- DDL
CREATE INDEX idx_#CompleteOrder ON #CompleteOrder(SalesOrderID)
-- DML
SELECT * FROM #CompleteOrder
-- DDL
CREATE TABLE #ProcOrder (a int)
-- DML
SELECT * FROM #ProcOrder
GO
EXEC GetOrderInfo

SQL2005引入了新的语句级重新编译技术,有效的防止了因为部分语句引起的整个过程或批的重新编译。因此在SQL2000中跟踪到的内容会与图示所示存在不同之处。
另一个对临时表的命名问题,请慎记不要随便起一个#T1作为临时表的名称。这在过程不调用其它过程时不会造成问题,但在调用的另一过程中也存在一个#T1的临时表时,就会造成问题。以上示例演示:
 

代码如下:
SET NOCOUNT ON;
USE tempdb;
GO
IF OBJECT_ID('dbo.proc1') IS NOT NULL
  DROP PROC dbo.proc1;
GO
IF OBJECT_ID('dbo.proc2') IS NOT NULL
  DROP PROC dbo.proc2;
GO
CREATE PROC dbo.proc1
AS
CREATE TABLE #T1(col1 INT NOT NULL);
INSERT INTO #T1 VALUES(1);
SELECT * FROM #T1;
EXEC dbo.proc2;
GO
CREATE PROC dbo.proc2
AS
CREATE TABLE #T1(col1 INT NULL, col2 INT NOT NULL);
INSERT INTO #T1 VALUES(2, 2);
SELECT * FROM #T1;
GO
EXEC dbo.proc1;

因此,请在对临时表命名时使用过程名称与临时表名称的组,如#proc_T1。这样会有效的防止此类问题的出现。

4.另一个值得注意的问题是,我们不应该使用sp_作为自定义存储过程的前缀。这是微软用作系统存储过程的前缘。在调用以sp_开头的过程时,会首先在master数据库中进行查找,如果找不到才会在用户数据库中查找。如果要使过程真正成为系统过程要使用sp_MS_marksystemobject过程,如下所示:
USE master;
GO
EXEC sp_MS_marksystemobject 'dbo.sp_Proc1';
同样对表或其它对象的命名时也不要以sp_作为前缀。

5.另一个问题是安全方面的,因为传入的参数可能会引起脚本注入的危险,因此应该对每个参数进行一些判断,以下是一种通用的判断语句,可应用于每个参数:
 

代码如下:
IF UPPER(@cols) LIKE UPPER(N'%0x%')
  OR UPPER(@cols) LIKE UPPER(N'%;%')
  OR UPPER(@cols) LIKE UPPER(N'%''%')
  OR UPPER(@cols) LIKE UPPER(N'%--%')
  OR UPPER(@cols) LIKE UPPER(N'%/*%*/%')
  OR UPPER(@cols) LIKE UPPER(N'%EXEC%')
  OR UPPER(@cols) LIKE UPPER(N'%xp_%')
  OR UPPER(@cols) LIKE UPPER(N'%sp_%')
  OR UPPER(@cols) LIKE UPPER(N'%SELECT%')
  OR UPPER(@cols) LIKE UPPER(N'%INSERT%')
  OR UPPER(@cols) LIKE UPPER(N'%UPDATE%')
  OR UPPER(@cols) LIKE UPPER(N'%DELETE%')
  OR UPPER(@cols) LIKE UPPER(N'%TRUNCATE%')
  OR UPPER(@cols) LIKE UPPER(N'%CREATE%')
  OR UPPER(@cols) LIKE UPPER(N'%ALTER%')
  OR UPPER(@cols) LIKE UPPER(N'%DROP%')
BEGIN
  SET @msg = N'Possible SQL injection attempt.';
  RAISERROR(@msg, 16, 1);
RETURN;
END

    
 
 
 
本站(WWW.)旨在分享和传播互联网科技相关的资讯和技术,将尽最大努力为读者提供更好的信息聚合和浏览方式。
本站(WWW.)站内文章除注明原创外,均为转载、整理或搜集自网络。欢迎任何形式的转载,转载请注明出处。












  • 相关文章推荐
  • 编写 linux shell 有什么好的编写工具呢?
  • VB编写的DLL不能被JAVA调用,用C将VB编写的DLL包装起来,做成一个新的DLL;用C编写的DLL可以被JAVA调用,用JAVA再进行一层的包装,因为JAV
  • 用Qt3.1.4编写的程序,如何加入自己编写的另外一个动态库呀?诚恳指教!!
  • 如图 scsi <---> scsi controller <---> pci <---> cpu 如果我想对scsi编写驱动我该对哪个部分编写?
  • 用什么工具编写JSP,可以象用InterDev编写ASP一样方便快捷?
  • 编写程序如编写人生,有人和我的意见相同吗?
  • 请问用jsp编写程序,非要编写代码么?有没有什么象fontpage2000那样的工具,能够生成一些固定模板的工具?就是说不用全部都写的工具。望
  • 用jsp编写web程序到底和java serverlet编写web程序有和区别
  • socket中,从client端(用java编写的)传到server端(用c编写的)的字符串为何总多一个ASCII码为10的字符呀?
  • 如何用java编写一个NT平台下的标准服务?
  • 关于用vj编写sniffer一问。
  • Linux下如何编写脚本文件
  • java能不能编写单独运行的程序?
  • Linux有没有类似bat文件,如何编写。
  • 用JAVA编写COM组件
  • 关于版本和bug管理的rule(规则)的编写
  • 在windows下可以用Code::Blocks编写linux程序吗?
  • 函数互相调用时Makefile编写
  • linux下用java编写邮箱客户端程序与在windows下有什么不同??
  • AIX下如何编写批处理命令和计划任务


  • 站内导航:


    特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

    ©2012-2021,,E-mail:www_#163.com(请将#改为@)

    浙ICP备11055608号-3