如何避免存储过程中不必要的重新编译?(一)

一、  使用存储过程有如下好处:

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会根据具体的参数为每条不同的语句生成一个缓存计划。从而占用过多的缓存。

还有几种情况也会引起重新编译,我在明天的文章中会给大家介绍。

时间: 2024-11-25 14:46:39

如何避免存储过程中不必要的重新编译?(一)的相关文章

在oracle存储过程中创建临时表

在oracle的存储过程中,不能直接使用DDL语句,比如create.alter.drop.truncate等. 那如果我们想在存储过程中建立一张临时表就只能使用动态sql语句了: create or replace procedure pro as str_sql varchar2(100); begin -- 创建临时表 str_sql := 'create global temporary table temp_table ( col1 varchar2(10), col2 number

sqlserver 存储过程中使用临时表到底会不会导致重编译

曾经在网络上看到过,SqlServer的存储过程中使用临时表,会导致执行计划无法重用, 运行时候会导致重编译的这么一个说法,自己私底下去做测试的时候,根据profile的跟踪结果, 如果不是统计信息变更导致导致的重编译,单单是使用临时表,并不会导致重编译, 但是对于一些特殊的情况,又确实会出现重编译的, 为了弄清楚这个问题,查阅了大量的资料,才把这个问题弄清楚,这里特意记录下来,希望武断地认为存储过程中使用了临时表就会导致重编译的这个观点得到纠正. 首先进行下面的测试,我们知道,导致临时表重编译

SQL Server存储过程中使用事务

今天修改之前一个同事写的代码,发现方法中直接执行了两个sql语句,一个是删除用户,一个是删除该用户的权限.由于数据库数据比较多,导致有时候这个两个sql不能都执行成功,数据库出现了脏数据. 鉴于这个原因,我把两个sql放到了一个存储过程中执行,在存储过程中添加事务,使其要么都执行,要么都不执行. 代码框架如下: 1 CREATE PROCEDURE pro_TrancDemo @backvalue INT OUTPUT 2 AS 3 BEGIN 4 SET NOCOUNT ON; 5 6 BEG

Sql Server 存储过程中查询数据无法使用 Union(All)

原文:Sql Server 存储过程中查询数据无法使用 Union(All) 微软Sql Server数据库中,书写存储过程时,关于查询数据,无法使用Union(All)关联多个查询. 1.先看一段正常的SQL语句,使用了Union(All)查询: SELECT ci.CustId --客户编号 , ci.CustNam --客户名称 , ci.ContactBy --联系人 , ci.Conacts --联系电话 , ci.Addr -- 联系地址 , ci.Notes --备注信息 , ai

Sql server中根据存储过程中的部分信息查找存储过程名称的方法【视图和Function】

1.查询的语句: select a.id,b.name,a.*,b.* from syscomments a join sysobjects b on a.id=b.id where b.xtype='P' and a.text like '%usp_cm%' b.xtype='P'指定在什么类型的范围进行搜索 '%usp_cm%'就是你能记得的存储过程中的内容. 2.查找类型: select distinct xtype from sysobjects 找到数据库中所有的对象类型 P是存储过程

Mysql存储过程中使用cursor

一.表 学生表 CREATE TABLE `t_student` ( `stuNum` int(11) NOT NULL auto_increment, `stuName` varchar(20) default NULL, `birthday` date default NULL, PRIMARY KEY  (`stuNum`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 学生分数表 CREATE TABLE `t_stu_score` ( `id` int(11

mysql中游标在存储过程中的详细用法

昨天写的一个东东,分享下给大家. drop PROCEDURE  if exists sp_cleanUserData; CREATE  PROCEDURE `sp_cleanUserData`() BEGIN /*定义游标*/ declare v_dt bigint(20) default 0 ; declare v_num INT DEFAULT 0; /*游标循环到末尾时给定义的常量赋值*/ declare cur_userId   CURSOR FOR select  userId fr

Oracle存储过程中异常Exception的捕捉和处理

Oracle存储过程中异常的捕捉和处理 CREATE OR REPLACE Procedure Proc_error_process ( v_IN in Varchar2, v_OUT Out Varchar2) AUTHID CURRENT_USER AS --声明异常 some_kinds_of_err EXCEPTION; -- Exception to indicate an error condition v_ErrorCode NUMBER; -- Variable to hold

存储过程中使用事物(转)

一.存储过程中使用事务的简单语法 在存储过程中使用事务时非常重要的,使用数据可以保持数据的关联完整性,在Sql server存储过程中使用事务也很简单,用一个例子来说明它的语法格式: 代码 Create Procedure MyProcedure ( @Param1 nvarchar(10), @param2 nvarchar(10) ) AS Begin Set NOCOUNT ON; Set XACT_ABORT ON; Begin Tran Delete from table1 where