好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

记一次数据库调优过程(IIS发过来SQLSERVER的FETCHAPI

记一次 数据库 调优 过程 (IIS发 过来 SQLSERVER 的FETCH API_CURSOR语句是神马?) 前几天帮客户优化一个 数据库 ,那个 数据库 的大小是6G 这麽小的 数据库 按道理不会有太大的性能问题的,但是客户反应说CPU占用很高,经常达到 80%~90% 我检查了任务管理

记一次 数据库 调优 过程 (IIS发 过来 SQLSERVER 的FETCH API_CURSOR语句是神马?)

前几天帮客户优化一个 数据库 ,那个 数据库 的大小是6G

这麽小的 数据库 按道理不会有太大的性能问题的,但是客户反应说CPU占用很高,经常达到 80%~90%

我检查了任务管理器,确实是SQLSERVER占的CPU

而服务器的内存是16G内存,只占用了 7G+

客户的环境:

Windows2008R2

SQLSERVER2005 SP3 64位 企业版

服务器内存:16G

CPU:8核

RDS:阿里云主机

IIS7.5

网站使用ASP技术

着手查找原因

于是就着手检查占用CPU高的原因,检查了很久,发现有一些SQL语句占用CPU很高,而执行的SQL语句如下:

这些是什么语句呢?在msdn上面找不到任何资料,使用下面的SQL语句查看,在[program_name]字段可以看到是IIS发 过来 的

 SELECT   *   FROM  sys. [  sysprocesses  ]   WHERE  SPID >=  50  

难道是IIS的bug?然后我又继续在茫茫网海里查找资料,最后终于在paul的博客里找到原因

文章地址:Hunting down the origins of FETCH API_CURSOR and sp_cursorfetch

文章大意

我在调优 数据库 的时候,使用sqlserver profiler捕获RPC:Completed 事件,可以看到很多类似下面的语句

你看到这些语句是从session_id为53的session那里发 过来 的

于是用下面语句看一下session_id为53执行的究竟是什么语句

 DBCC  INPUTBUFFER ( 53 ) 

而返回的结果是

  FETCH  API_CURSOR0000000000000004 

您很快意识到这跟服务器游标有一定的关系

如果你使用 sys.dm_exec_requests 视图或者sys.dm_exec_connections视图来查看session_id53执行了什么语句

和执行的状态

 SELECT  t. text 
 FROM   sys.dm_exec_connections c
  CROSS   APPLY sys.dm_exec_sql_text (c.most_recent_sql_handle) t
  WHERE  session_id  =   53  

但是返回的结果依然是

 FETCH  API_CURSOR0000000000000004 

那么还有没有其他的视图来帮助我们呢?我们可以使用sys.dm_exec_cursors视图,将spid代入进去

 SELECT  c.session_id, c.properties, c.creation_time, c.is_open, t. text 
 FROM  sys.dm_exec_cursors ( 53  ) c
  CROSS  APPLY sys.dm_exec_sql_text (c.sql_handle) t 

从结果来看,我们知道语句使用了游标,并且知道游标的属性(scroll locks)和游标创建时间

并且我们看到执行的SQL语句不像是FETCH API_CURSOR或者sp_cursorfetch,而是

 SELECT   *   FROM  dbo.FactResellerSales. 

本人的处理 过程

1、先使用下面的SQL语句找出当前实例下有使用到游标的语句

 --   =============================================  
--   Author:         
--   Blog:           
--   Create date:    
--   Description:    
--   ============================================= 
 DECLARE   @spid   NVARCHAR ( 100  )
  DECLARE   @SQL   NVARCHAR ( MAX  )

  DECLARE  CurSPID  CURSOR 
 FOR 
     SELECT    [  spid  ] 
     FROM     sys. [  sysprocesses  ] 
     WHERE     [  spid  ]   >=   50 

 OPEN   CurSPID
  FETCH   NEXT   FROM  CurSPID  INTO   @spid 

 WHILE   @@FETCH_STATUS   =   0 
     BEGIN   
         SET   @SQL   =  N '  
SELECT  cursors.session_id ,
        cursors.properties ,
        cursors.creation_time ,
        cursors.is_open ,
        text.text
FROM    sys.dm_exec_cursors (  '   +   @spid   +   '  ) cursors
        CROSS APPLY sys.dm_exec_sql_text(cursors.sql_handle) text  ' 
         EXEC ( @SQL  ) 

          FETCH   NEXT   FROM  CurSPID  INTO   @spid 
     END 
 CLOSE   CurSPID
  DEALLOCATE  CurSPID 

为什麽上面的脚本要使用游标,因为当时我根据paul的脚本来执行的时候,在活动监视器里能看到使用游标的SQL语句,

但是在SSMS里查询的时候,怎麽也查询不出来,所以才用游标,将使用到游标的语句一网打尽,这里 输出的结果要忽略本身这个脚本使用到的游标!!

2、根据 输出的结果,发现有几个地方使用了游标,下面只是部分截图

3、把结果拷贝出来,可以发现也是执行的是SELECT 语句

4、因为是ASP程序,没有用到存储 过程 ,于是搜索项目文件,看一下哪个文件有类似的代码

5、找到结果

ASP的语法跟VB是很像的,本人觉得非常羞涩

可以看到server对象创建了一个recordset对象,然后从recordset对象里逐条记录取出来,再做处理,可以看到后续还有

select case....case...case....

就是对取出来的记录再做处理

因为ASP是脚本语言,由IIS来执行,所以在SQLSERVER这边可以看到下面语句的program_name字段是IIS

 SELECT   *   FROM  sys. [  sysprocesses  ]   WHERE  SPID >=  50  

6、验证一下是否是游标的原因导致CPU高,使用下面的脚本

 SELECT   *   FROM  sys. [  dm_os_performance_counters  ]  
 WHERE   [  counter_name  ]  =  '  CPU usage %  '    
 AND   [  object_name  ]  =  '  SQLServer:Resource Pool Stats  '      
 AND   [  instance_name  ]  =  '  default  '                                                 


 SELECT   *   FROM  sys. [  dm_os_performance_counters  ]  
 WHERE   [  counter_name  ]  =  '  Active cursors  '    
 AND   [  object_name  ]  =  '  SQLServer:Cursor Manager by Type  '      
 AND   [  instance_name  ]  =  '  _Total  '                                                 


 --  建表 
 USE   [  msdb  ] 
 GO 
 CREATE   TABLE   ActiveCursors
(cntr_value   BIGINT ,cntr_time  DATETIME   PRIMARY   KEY  )
  GO 
 CREATE   TABLE   CPUUsage
(cntr_value   BIGINT ,cntr_time  DATETIME   PRIMARY   KEY  )
  GO 


 --  建作业 
 DECLARE   @DBName   NVARCHAR ( MAX  )
  DECLARE   @job_name   sysname

  SET   @DBName  =  '  xxx  '    --  ★Do 

 SET   @job_name  =  '  Monitor_CPUUsage_  '   +   @DBName 
 EXEC  msdb.dbo.sp_add_job  @job_name  =  @job_name  , 
  @enabled  =  1  , 
  @notify_level_eventlog  =  0  , 
  @notify_level_email  =  0  , 
  @notify_level_netsend  =  0  , 
  @notify_level_page  =  0  , 
  @delete_level  =  0  , 
  @description  = N '  监控CPU使用率  '  , 
  @category_name  = N '  Database Maintenance  '  , 
  @owner_login_name  = N '  sa  '  


 --  添加监控步骤 
 DECLARE   @job_name   SYSNAME
  DECLARE   @SQL   NVARCHAR ( MAX  )
  DECLARE   @DBName   NVARCHAR ( MAX  )

  SET   @DBName  =  '  xxx  '    --  ★Do 
 SET   @job_name  =  '  Monitor_CPUUsage_  '   +   @DBName    --  ★Do 

 BEGIN  
     SET   @SQL   =  N '  
USE [msdb]
GO
INSERT INTO CPUUsage(cntr_value,cntr_time)  
SELECT cntr_value,GETDATE() FROM sys.[dm_os_performance_counters] 
WHERE [counter_name]=  ''  CPU usage %  ''     
AND [object_name]=  ''  SQLServer:Resource Pool Stats  ''     
AND [instance_name]=  ''  default  '' 
 ' 
     EXEC  msdb.dbo.sp_add_jobstep  @job_name   =   @job_name  ,
          @step_name   =  N '  Monitor  ' ,  @step_id   =   1 ,  @cmdexec_success_code   =   0  ,
          @on_success_action   =   3 ,  @on_success_step_id   =   0 ,  @on_fail_action   =   2  ,
          @on_fail_step_id   =   0 ,  @retry_attempts   =   0 ,  @retry_interval   =   0  ,
          @os_run_priority   =   0 ,  @subsystem   =  N '  TSQL  ' ,  @command   =   @SQL  ,
          @database_name   =   @DBNAME ,  @flags   =   0 

 END 

  


 --  创建Monitor作业的调度计划 
 DECLARE   @job_name   SYSNAME
  DECLARE   @SQL   NVARCHAR ( MAX  )
  DECLARE   @DBName   NVARCHAR ( MAX  )

  SET   @DBName  =  '  xxx  '    --  ★Do 
 SET   @job_name  =  '  Monitor_CPUUsage_  '   +   @DBName    --  ★Do 

 --  修改作业的执行时间 
 EXEC   msdb.dbo.sp_add_jobschedule   @job_name   =   @job_name ,  @name  = N '  Plan  '  , 
          @enabled  =  1  , 
          @freq_type  =  4  , 
          @freq_interval  =  1  , 
          @freq_subday_type  =  2  , 
          @freq_subday_interval  =  30  , 
          @freq_relative_interval  =  0  , 
          @freq_recurrence_factor  =  0  , 
          @active_start_date  =  20140105  , 
          @active_end_date  =  99991231  , 
          @active_start_time  =  2000  , 
          @active_end_time  =  235959 

 EXEC   msdb.dbo.sp_add_jobserver   @job_name   =   @job_name ,  @server_name   =  N '  (local)  ' 





 --  ----------------------------------------------------------------------------  
--  建作业 
 DECLARE   @DBName   NVARCHAR ( MAX  )
  DECLARE   @job_name   sysname

  SET   @DBName  =  '  xxx  '    --  ★Do 

 SET   @job_name  =  '  Monitor_ActiveCursors_  '   +   @DBName 
 EXEC  msdb.dbo.sp_add_job  @job_name  =  @job_name  , 
  @enabled  =  1  , 
  @notify_level_eventlog  =  0  , 
  @notify_level_email  =  0  , 
  @notify_level_netsend  =  0  , 
  @notify_level_page  =  0  , 
  @delete_level  =  0  , 
  @description  = N '  监控游标使用  '  , 
  @category_name  = N '  Database Maintenance  '  , 
  @owner_login_name  = N '  sa  '  


 --  添加监控步骤 
 DECLARE   @job_name   SYSNAME
  DECLARE   @SQL   NVARCHAR ( MAX  )
  DECLARE   @DBName   NVARCHAR ( MAX  )

  SET   @DBName  =  '  xxxx  '    --  ★Do 
 SET   @job_name  =  '  Monitor_ActiveCursors_  '   +   @DBName    --  ★Do 

 BEGIN  
     SET   @SQL   =  N '  
USE [msdb]
GO
INSERT INTO ActiveCursors(cntr_value,cntr_time)  
SELECT cntr_value,GETDATE()  FROM sys.[dm_os_performance_counters] 
WHERE [counter_name]=  ''  Active cursors  ''     
AND [object_name]=  ''  SQLServer:Cursor Manager by Type  ''    
AND [instance_name]=  ''  _Total  '' 
 ' 
     EXEC  msdb.dbo.sp_add_jobstep  @job_name   =   @job_name  ,
          @step_name   =  N '  Monitor  ' ,  @step_id   =   1 ,  @cmdexec_success_code   =   0  ,
          @on_success_action   =   3 ,  @on_success_step_id   =   0 ,  @on_fail_action   =   2  ,
          @on_fail_step_id   =   0 ,  @retry_attempts   =   0 ,  @retry_interval   =   0  ,
          @os_run_priority   =   0 ,  @subsystem   =  N '  TSQL  ' ,  @command   =   @SQL  ,
          @database_name   =   @DBNAME ,  @flags   =   0 

 END 

  


 --  创建Monitor作业的调度计划 
 DECLARE   @job_name   SYSNAME
  DECLARE   @SQL   NVARCHAR ( MAX  )
  DECLARE   @DBName   NVARCHAR ( MAX  )

  SET   @DBName  =  '  xxxx  '    --  ★Do 
 SET   @job_name  =  '  Monitor_ActiveCursors_  '   +   @DBName    --  ★Do 

 --  修改作业的执行时间 
 EXEC   msdb.dbo.sp_add_jobschedule   @job_name   =   @job_name ,  @name  = N '  Plan  '  , 
          @enabled  =  1  , 
          @freq_type  =  4  , 
          @freq_interval  =  1  , 
          @freq_subday_type  =  2  , 
          @freq_subday_interval  =  30  , 
          @freq_relative_interval  =  0  , 
          @freq_recurrence_factor  =  0  , 
          @active_start_date  =  20140105  , 
          @active_end_date  =  99991231  , 
          @active_start_time  =  2000  , 
          @active_end_time  =  235959 

 EXEC   msdb.dbo.sp_add_jobserver   @job_name   =   @job_name ,  @server_name   =  N '  (local)  '  

View Code

上面视图里的[object_name]字段和 [instance_name]字段跟你的环境会不一样,所以大家要按照自己的环境来修改

如果是SQLSERVER2005是没有CPU usage %这个counter的,我使用了下面的SQL语句

 SELECT   SUM ( [  cpu  ] )  FROM  sys. [  sysprocesses  ]   WHERE  SPID >=  50  

7、画折线图

监控了一天的时间,根据结果使用EXCEL画出折线图

凌晨那段曲线是因为 数据库 有做清除数据的操作,所以会比较高

游标跟CPU图虽然说不能完全吻合,但是基本能吻合

解决方法

1、修改代码

2、升级到SQL2008,然后使用资源调控器把CPU压下去

最终还是找人修改代码

总结

有时候对一些老旧的程序,例如ASP,可能老一代程序员还会,现在的程序员基本都使用ASP.NET

所以如果可能,还是跟上技术的脚步,不然出问题了,没有人维护就麻烦了

如有不对的地方,欢迎大家拍砖o(∩_∩)o

查看更多关于记一次数据库调优过程(IIS发过来SQLSERVER的FETCHAPI的详细内容...

  阅读:49次