最大递归 32767 在语句完成之前已经用完。 [SQLSTATE 42000](错误 530)

The maximum recursion 32767 has been exhausted before statement completion. [SQLSTATE 42000] (Error 530)

我正在使用以下查询来识别服务器上的阻塞并发送警报同时终止会话(通过 SQL 作业执行)。 查询正在使用 2 CET table Blcokers 和 Hierarchy。

很多次我遇到错误并且作业失败。

错误: 新台币 SERVICE\SQLSERVERAGENT。声明终止。最大递归 32767 在语句完成之前已经用完。 [SQL状态 42000](错误 530)。该步骤失败。

在研究中我发现在查询中使用 OPTION(MAXRECURSION) HINT 来避免错误。 我已经包含了 HINT for Hierarchy CET table 如下所示,但仍然出现相同的错误

SELECT * INTO #BlockingProcess 从层次结构 选项(MAXRECURSION 32767)

有人可以建议我应该在下面的代码中进行哪些更改以停止无限递归。

编码:

SET nocount ON; 
SET concat_null_yields_null OFF 
go


CREATE TABLE #sp_who2(
    ID INT IDENTITY(1,1) NOT NULL,
    SPID VARCHAR(4),
    Status VARCHAR(200),
    Login VARCHAR(200),
    HostName  VARCHAR(200),
    BlkBy VARCHAR(4),
    DBName VARCHAR(200),
    Command VARCHAR(200),
    CPUTime VARCHAR(20),
    DiskIO VARCHAR(20),
    LastBatch VARCHAR(20),
    ProgramName VARCHAR(200),
    SPID2 VARCHAR(4),
    RequestID VARCHAR(4)
)

INSERT #sp_who2
EXEC sp_who2

--SELECT SPID, BlkBy FROM #sp_who2
DELETE FROM #sp_who2
 WHERE BlkBy='  .'
   AND SPID NOT IN (SELECT BlkBy FROM #sp_who2 WHERE BlkBy IS NOT NULL)

;WITH Hierarchy(ChildSPID,Generation,BlkBy)
AS
(
    SELECT SPID, 0, BlkBy
      FROM #sp_who2 AS FirtGeneration
     WHERE BlkBy='  .'       
     UNION ALL
    SELECT NextGeneration.SPID, Parent.Generation+1, Parent.ChildSPID
      FROM #sp_who2 AS NextGeneration
            INNER JOIN Hierarchy AS Parent ON NextGeneration.BlkBy = Parent.ChildSPID    
)
SELECT * INTO #BlockingProcess
  FROM Hierarchy
OPTION(MAXRECURSION 32767)

SELECT * FROM #BlockingProcess

--loop and kill lead blockers
DECLARE @SPIDGen0           INT
DECLARE @SPIDGen1           INT
DECLARE @ElapsedTimeMSGen0  INT  --if NULL, use Gen1
DECLARE @ElapsedTimeMSGen1  INT
DECLARE @SUBJECTKILL            VARCHAR(200);
--DECLARE @tableHTMLKILL            NVARCHAR(MAX);

WHILE EXISTS(SELECT * FROM #BlockingProcess WHERE BlkBy='  .')
BEGIN
    SELECT @SPIDGen0=MIN(ChildSPID) FROM #BlockingProcess WHERE Generation=0
    SELECT @SPIDGen1=MIN(ChildSPID) FROM #BlockingProcess WHERE Generation=1 and BlkBy=@SPIDGen0
    PRINT @SPIDGen0
    PRINT @SPIDGen1

    SELECT @ElapsedTimeMSGen0 = BlockingRequest.total_elapsed_time
      FROM sys.dm_exec_requests BlockingRequest
     WHERE session_id=@SPIDGen0

    SELECT @ElapsedTimeMSGen1 = BlockingRequest.total_elapsed_time
      FROM sys.dm_exec_requests BlockingRequest
     WHERE session_id=@SPIDGen1
     
    PRINT @ElapsedTimeMSGen0
    PRINT @ElapsedTimeMSGen1

--If (select count(*) from #BLOCKERS) >= 1
IF ISNULL(@ElapsedTimeMSGen0,@ElapsedTimeMSGen1) >= 120000
begin
DECLARE @Subject varchar(100)
SELECT @Subject = 'Blocking Tree Report from ' +  @@servername
    EXEC msdb.dbo.sp_send_dbmail @body = @tableHTML
        ,@body_format = 'HTML'
        ,@profile_name = N''
        ,@recipients = N''
       
        ,@Subject = @Subject      
end

drop table #BLOCKERS

WAITFOR DELAY '00:03'

IF ISNULL(@ElapsedTimeMSGen0,@ElapsedTimeMSGen1)>180000 --milliseconds = 3 minutes
    --IF ISNULL(@ElapsedTimeMSGen0,@ElapsedTimeMSGen1)>60000 --milliseconds = 3 minutes
    BEGIN
        SELECT @SUBJECTKILL=@@SERVERNAME+' - Lead Blocker Session '+CAST(@SPIDGen0 AS VARCHAR(5))+' Killed'
        EXEC msdb.dbo.sp_send_dbmail
            @profile_name='',
            @recipients='',
        
            @subject = @SUBJECTKILL,
            @body = @tableHTML,
            @body_format = 'HTML'

        EXEC('KILL ' + @SPIDGen0)
    END
    --Skip current SPID and move to next SPID
    DELETE FROM #BlockingProcess WHERE ChildSPID = @SPIDGen0
END

IF OBJECT_ID('tempdb..#sp_who2') IS NOT NULL DROP TABLE #sp_who2
IF OBJECT_ID('tempdb..#BlockingProcess') IS NOT NULL DROP TABLE #BlockingProcess

改用sp_whoisactive。它可以很好地 human-readable 了解正在发生的事情以及正在处理或阻止其他进程的进程。

此外,您可以按 运行 时间过滤结果并杀死最慢的。

但是,你的做法似乎不对。您有用于终止交易的自动化脚本。如果每月生成一次发票报告需要 5 分钟,而您不断地取消它怎么办?如果一个人正在创建一个索引来阻止指定 table 上的事务并且你杀死它怎么办?

Microsoft 没有此类功能是有原因的 - 需要有人接听电话。

最好使用此例程来识别慢查询并修复它们。

我观察到,在上面的查询中,当并行会话阻塞自身时,它以某种方式超过了最大递归,不应将其视为阻塞。

因此,我通过在下面的删除语句中包含 OR SPID=BlkBy 来避免并行会话

DELETE FROM #sp_who2
 WHERE BlkBy='  .'
   AND SPID NOT IN (SELECT BlkBy FROM #sp_who2 WHERE BlkBy IS NOT NULL)

此后查询开始正常工作。

感谢您的回复。

抱歉回复晚了。

我已经删除了对 CET table 的依赖,因为当每个阻塞都是由于睡眠会话而我以前的脚本无法识别睡眠会话时进入最大递归。 我观察到 exec_requests dmv 没有捕获所有睡眠会话,因此一些脚本如何在睡眠会话阻塞时用于达到最大递归,因为我已经加入 exec_requests dmv 来获取阻塞数据。

解决方案: 我已将 CET 替换为 Temp Table,并将 exec_requests dmv 替换为 sys.sysprocesses 系统 table。这解决了最大递归问题。

此外,我还添加了一些过滤器来消除对睡眠会话的阻塞。 因此,现在查询会杀死 head blocker,如果它是一个活动会话并且阻塞超过 3 分钟,或者如果 head blocker 是一个休眠会话。

以下是修改后的完整查询,供您参考。肯定会有很多修改,如有建议请多多指教

SET nocount ON;
SET concat_null_yields_null OFF
go

WITH blockers (spid, blocked, level, batch, lastwaittype, status,hostname,cmd,DBNAME,loginname,open_tran,login_time)
     AS (SELECT spid,
                blocked,

                Cast (Replicate ('0', 4-Len (Cast (spid AS VARCHAR)))
                      + Cast (spid AS VARCHAR) AS VARCHAR (1000))         AS
                LEVEL,
                Replace (Replace (T.text, Char(10), ' '), Char (13), ' ') AS
                BATCH,
                R.lastwaittype,
R.status,
                R.hostname,r.cmd,DB_NAME(r.dbid),r.loginame,r.open_tran,r.login_time
         FROM   sys.sysprocesses R WITH (nolock)
                CROSS apply sys.Dm_exec_sql_text(R.sql_handle) T
         WHERE  ( blocked = 0
                   OR blocked = spid )
                AND EXISTS (SELECT spid,
                                   blocked,
                                   Cast (Replicate ('0', 4-Len (Cast (spid AS
                                         VARCHAR
                                         )))
                                         + Cast (spid AS VARCHAR) AS VARCHAR (
                                         1000))
                                   AS
                                       LEVEL,
                                   blocked,
                                   Replace (Replace (T.text, Char(10), ' '),
                                   Char (13
                                   ),
                                   ' ') AS
                                       BATCH,
                                   R.lastwaittype,
  R.status,
                                    R.hostname,r.cmd,DB_NAME(r.dbid),r.loginame,r.open_tran,r.login_time
                            FROM   sys.sysprocesses R2 WITH (nolock)
                                   CROSS apply
                                   sys.Dm_exec_sql_text(R.sql_handle) T
                            WHERE  R2.blocked = R.spid
                                   AND R2.blocked <> R2.spid
                                   AND DB_NAME(r.dbid) NOT IN ('msdb'))
         UNION ALL
         SELECT R.spid,
                R.blocked,
                Cast (blockers.level
                      + RIGHT (Cast ((1000 + R.spid) AS VARCHAR (100)), 4) AS
                      VARCHAR
                      (
                      1000)) AS
                LEVEL,
                Replace (Replace (T.text, Char(10), ' '), Char (13), ' ')
                AS BATCH,
                R.lastwaittype,
R.status,
                R.hostname ,r.cmd,DB_NAME(r.dbid),r.loginame,r.open_tran,r.login_time
         FROM   sys.sysprocesses AS R WITH (nolock)
                CROSS apply sys.Dm_exec_sql_text(R.sql_handle) T
                INNER JOIN blockers
                        ON R.blocked = blockers.spid
         WHERE  R.blocked > 0
                AND R.blocked <> R.spid
                AND DB_NAME(r.dbid) NOT IN ('msdb'))
SELECT N''
       + Replicate (N'|.......', Len (level)/4 - 2)
       + CASE WHEN (Len (level)/4 - 1) = 0 THEN 'HEAD - ' ELSE '|------ ' END +
       Cast (
       spid AS VARCHAR (10)) + ' ' + batch AS BLOCKING_TREE,
  Spid,
  blocked,
       hostname,
  Status,
       lastwaittype, cmd,DBNAME,loginname,open_tran,login_time,
       Getdate()                           AS 'RunTime' ,
       level
 --(Select convert(varchar, total_elapsed_time /60000 ) + ':' + right('0' + convert(varchar(2), (total_elapsed_time /1000) % 60 ),2 )   from sys.dm_Exec_requests) as StartedSince
INTO  #BLOCKERS
FROM   blockers WITH (nolock)
ORDER  BY level ASC


--select * from #BLOCKERS

    DECLARE @tableHTML NVARCHAR(MAX);

    SET @tableHTML = N'<H1>Blocking Tree Report</H1>' + N'<table border="1" bordercolor="#ff0000" cellspacing="0">' + N'<tr>' +
    N'<th>Blocking_Tree</th>' + N'<th>hostname</th>' + N'<th>Status</th>' + N'<th>lastwaittype</th>'+'<th>CurrentTime</th>'
    + N'<th>cmd</th>'
    + N'<th>DatabaseName</th>'
    + N'<th>loginname</th>'
    + N'<th>open_tran</th>'
    + N'<th>login_time</th>'
    + '</tr>' + CAST((
SELECT td = Blocking_Tree,'',
         td =hostname,'',
td =Status,'',
         td =lastwaittype,'',
         td =RunTime,'',
         td= cmd,'',
         td= DBNAME,'',
         td= loginname,'',
         td=open_tran,'',
         td=login_time,''
         FROM #BLOCKERS
             order by level asc
FOR XML PATH('tr')
                    ,TYPE
                ) AS NVARCHAR(MAX)) + N'</table>';  

----------------------------------------------------------------

Insert Into  [TOOLS].[dbo].[Lead_Blocker_Log]
select  * from  #BLOCKERS
------------------------------------------------------

SELECT * INTO #BlockingProcess
  FROM #BLOCKERS


  --select * from #BlockingProcess

---------------------------------------------------------
  --loop and kill lead blockers
--DECLARE @SPIDGen2 INT
DECLARE @SPIDGen0 INT
DECLARE @SPIDGen1 INT
DECLARE @ElapsedTimeMSGen0 INT  --if NULL, use Gen1
DECLARE @ElapsedTimeMSGen1 INT
DECLARE @SUBJECTKILL VARCHAR(200)
Declare @sleeping1 VARCHAR (50)
Declare @sleeping2 VARCHAR (50)
--DECLARE @tableHTMLKILL NVARCHAR(MAX);

IF EXISTS(SELECT * FROM #BlockingProcess WHERE Blocked NOt in ('0') )
Begin

    SELECT @SPIDGen0=MIN(SPID) FROM #BlockingProcess WHERE Blocked=0
SELECT @SPIDGen1=MIN(SPID) FROM #BlockingProcess WHERE lastwaittype = 'LCK_M_U'
    --PRINT @SPIDGen0
---PRINT @SPIDGen1


SELECT @ElapsedTimeMSGen0 = BlockingRequest.total_elapsed_time
 FROM sys.dm_exec_requests BlockingRequest
WHERE session_id=@SPIDGen0

SELECT @ElapsedTimeMSGen1 = BlockingRequest.total_elapsed_time
 FROM sys.dm_exec_requests BlockingRequest
WHERE session_id=@SPIDGen1

--PRINT @ElapsedTimeMSGen0
--PRINT @ElapsedTimeMSGen1

SELECT @sleeping1 = BlockingRequest.status
 FROM sys.dm_exec_sessions BlockingRequest
WHERE session_id=@SPIDGen0

SELECT @sleeping2 = BlockingRequest.status
 FROM sys.dm_exec_sessions BlockingRequest
WHERE session_id=@SPIDGen1

--PRINT @sleeping1
--PRINT @sleeping2


If (select count(*) from #BLOCKERS) > 1
IF ISNULL(@ElapsedTimeMSGen0,@ElapsedTimeMSGen1) >180000 OR  ISNULL (@sleeping1,@sleeping2) = 'sleeping'
begin
DECLARE @Subject varchar(100)
SELECT @Subject = 'Blocking Tree Report from ' +  @@servername
    EXEC msdb.dbo.sp_send_dbmail @body = @tableHTML
        ,@body_format = 'HTML'
        ,@profile_name = N'DBMailProfile'
      
   ,@Subject = @Subject      
end


IF ISNULL(@ElapsedTimeMSGen0,@ElapsedTimeMSGen1)>180000 OR  ISNULL (@sleeping1,@sleeping2) = 'sleeping'--milliseconds = 3 minutes
--IF ISNULL(@ElapsedTimeMSGen0,@ElapsedTimeMSGen1)>60000 --milliseconds = 3 minutes
BEGIN

EXEC ('KILL ' + @SPIDGen0)

SELECT @SUBJECTKILL=@@SERVERNAME+' - Lead Blocker Session '+CAST(@SPIDGen0 AS VARCHAR(5))+' Killed'
EXEC msdb.dbo.sp_send_dbmail

@subject = @SUBJECTKILL,
@body = @tableHTML,
@body_format = 'HTML'

Print ('KILL ' + Convert (varchar (50),@SPIDGen0))

END

END

--IF OBJECT_ID('tempdb..#sp_who2') IS NOT NULL DROP TABLE #sp_who2
IF OBJECT_ID('tempdb..#BLOCKERS') IS NOT NULL DROP TABLE #BLOCKERS
IF OBJECT_ID('tempdb..#BlockingProcess') IS NOT NULL DROP TABLE #BlockingProcess