背景

SQLServer Agent作为Windows服务提供给用户定期执行管理任务,这些任务被称为Job;考虑应用镜像的场景如何解决Job同步问题,AWS RDS的做法是不予理会,由用户维护Job,假如发生切换用户需要在新的Principal端创建Job;另一种做法是镜像端保持同步Job,切换后尽量让用户无感知不需要多余维护动作,但这种做法在某些情况会遇到非常严重的问题——内存耗尽。

问题排查分析

第一次分析

问题发生时实例的ERRORLOG出现:

Error: 701, Severity: 17, State: 123.

并且记录了 MEMORYSTATUS,根据 MEMORYSTATUS 的信息本身已经可以做推断,但既然有现场 我们不妨用DAC(Dedicated Administrator Connection)看一下:

SELECT
TOP 10 [type],
sum ( single_pages_kb) as [SPA MEM/KB],
SUM(multi_pages_kb) AS [MPA MEM/KB]
FROM sys.dm_os_memory_clerks
GROUP BY type
ORDER BY SUM ( single_pages_kb+multi_pages_kb) DESC ;

内存占用

占用最多的 OBJECTSTORE_SNI_PACKET、MEMORYCLERK_SQLCONNECTIONPOOL 一般跟连接数、network packet size有关;

select
c.session_id,
c.net_packet_size,
s.host_name as client_host_name,
s.program_name,
s.client_interface_name
from sys.dm_exec_connections c
join sys.dm_exec_sessions s
on c.session_id = s.session_id
join sys.endpoints e
on c.endpoint_id = e.endpoint_id
order by c.net_packet_size desc

net_packet_size

select value_in_use from sys.configurations where configuration_id=1544

select program_name,count(*) as conn_num from sys.sysprocesses where spid>50 group by program_name order by conn_num desc

内存规格

1W+的链接保持,根据sys.dm_exec_sessions.program_name可以确认都来自SQLServer Agent,每个session的packet size是4K,实例的max server memory是1G,所以出现内存耗尽;

那么这1W个链接在干嘛?根据sys.dm_exec_sessions.program_name中暴露的job_id我们找到对应的Job,先看下这个Job要做什么;

select * from msdb.dbo.sysjobs where job_id=0x825F84340AFD5B4BA1D5AD82A8E76C1A

Job信息

第一次推论

这部分内容涉及业务逻辑我不再贴出,重点是Job_Step使用的DATABASE是镜像中的(RESTORING),因为镜像的DB不可用,Schedule又设置的过于频繁,所以SQLServer Agent没能及时释放这部分链接导致内存耗尽。

第一次验证

重新搭建了一个测试环境,制造类似的场景但问题并没能复现。感兴趣的小伙伴可以测试下:镜像 + Mirror端Job + Job链接镜像库且频度调成10s + Job内容不限(为什么不限后续通过Profiler可以看出)。

借助Profiler和Session相关视图我们可以看出,当Job-Step的链接DB设置为镜像DB时,会出现:

Error: 18456, Severity: 14, State: 38.

表示账号校验成功但数据库不可访问或登录权限不够,SQLServer Agent会重试多次,但最后都会释放链接,这跟之前的推论不符。

Profiler信息

select program_name,count(*) as conn_num from sys.sysprocesses where spid>50 group by program_name order by conn_num desc

connection

第二次分析

回到原现场,我们再做分析;尝试复现时我们发现完整的链接都无法建立,但这1W个链接是如何做到的:

select spid,program_name,loginame,login_time as time,db_name(dbid) as dbname from sys.sysprocesses where spid>50

dbname

根据spid我们看一下未释放的链接最后执行的SQL是什么:

inputbuffer

(@P1 nvarchar(max),@P2 uniqueidentifier,@P3 int,@P4 int)DECLARE @logTextWithPreAndPost nvarchar(max) set @logTextWithPreAndPost = N'' + @P1 + N''; EXECUTE msdb.dbo.sp_write_sysjobstep_log @job_id = @P2, @step_id = @P3, @log_text=@logTextWithPreAndPost, @append_to_last=@P4

看下 msdb.dbo.sp_write_sysjobstep_log这个存储过程:

exec sp_helptext 'sp_write_sysjobstep_log'

	Text
-------------------------------------------------------------------------------
CREATE PROCEDURE sp_write_sysjobstep_log
@job_id UNIQUEIDENTIFIER,
@step_id INT,
@log_text NVARCHAR(MAX),
@append_to_last INT = 0
AS
BEGIN
DECLARE @step_uid UNIQUEIDENTIFIER
DECLARE @log_already_exists int
SET @log_already_exists = 0 SET @step_uid = ( SELECT step_uid FROM msdb.dbo.sysjobsteps
WHERE (job_id = @job_id)
AND (step_id = @step_id) ) IF(EXISTS(SELECT * FROM msdb.dbo.sysjobstepslogs
WHERE step_uid = @step_uid ))
BEGIN
SET @log_already_exists = 1
END --Need create log if "overwrite is selected or log does not exists.
IF (@append_to_last = 0) OR (@log_already_exists = 0)
BEGIN
-- flag is overwrite --if overwrite and log exists, delete it
IF (@append_to_last = 0 AND @log_already_exists = 1)
BEGIN
-- remove previous logs entries
EXEC sp_delete_jobsteplog @job_id, NULL, @step_id, NULL
END INSERT INTO msdb.dbo.sysjobstepslogs
(
log,
log_size,
step_uid
)
VALUES
(
@log_text,
DATALENGTH(@log_text),
@step_uid
)
END
ELSE
BEGIN
DECLARE @log_id INT
--Selecting TOP is just a safety net - there is only one log entry row per step.
SET @log_id = ( SELECT TOP 1 log_id FROM msdb.dbo.sysjobstepslogs
WHERE (step_uid = @step_uid)
ORDER BY log_id DESC ) -- Append @log_text to the existing log record. Note that if this
-- action would make the value of the log column longer than
-- nvarchar(max), then the engine will raise error 599.
UPDATE msdb.dbo.sysjobstepslogs
SET
log .WRITE(@log_text,NULL,0),
log_size = DATALENGTH(log) + DATALENGTH(@log_text) ,
date_modified = getdate()
WHERE log_id = @log_id
END RETURN(@@error) -- 0 means success END

MSDN 没有找到详尽 Document,但看完定义也可以确认它是Job-Step做Advanced配置时会用到的一个存储过程,作用是把Job-Step日志写到表 msdb.dbo.sysjobstepslogs 中,根据参数的不同可能会overwrite或append;

log_to_table

第二次推论

根据之前的信息我们可以推测出,这1W+空闲链接是由于执行完Job-Step后,内部更新msdb的日志表,更新完成后链接未释放。

第二次验证

构造的场景跟第一次基本相同,只需要增加一点的是Job-Step开启了log to table。

验证成功

结果稳定复现,40s左右新增一个链接(Schedule 10s 不开启Step Retry attempts 和 Retry interval),根据msdb.dbo.sysjobstepslogs.log_size和msdb.dbo.sysjobstepslogs.log可以确认日志的更新频度在40s。

开启Profiler、不断调整Schedule时间、监控msdb.dbo.sysjobstepslogs.log_size大小,可以发现,当调度频度大于40s时,新增链接按照频度增加,当频度小于40s时,新增链接按照40s一个增加。

结论

如果Job-Step中定义的链接 DATABASE 是镜像库(RESTORING)且配置了Log To Table,那么每次做日志记录的Session(msdb.dbo.sysjobstepslogs)都不会自动关闭,即Agent在这种场景下存在链接泄露。

 

SQLServer · BUG分析 · Agent 链接泄露分析(转载)的更多相关文章

  1. xcode怎样分析检测内存泄露(iOS)

    虽然iOS 5.0版本之后加入了ARC机制,由于相互引用关系比较复杂时,内存泄露还是可能存在.所以了解原理很重要. 这里讲述在没有ARC的情况下,如何使用Instruments来查找程序中的内存泄露, ...

  2. 详解服务器性能测试的全生命周期?——从测试、结果分析到优化策略(转载)

    服务器性能测试是一项非常重要而且必要的工作,本文是作者Micheal在对服务器进行性能测试的过程中不断摸索出来的一些实用策略,通过定位问题,分析原因以及解决问题,实现对服务器进行更有针对性的优化,提升 ...

  3. 鸿蒙内核源码分析(静态链接篇) | 完整小项目看透静态链接过程 | 百篇博客分析OpenHarmony源码 | v54.01

    百篇博客系列篇.本篇为: v54.xx 鸿蒙内核源码分析(静态链接篇) | 完整小项目看透静态链接过程 | 51.c.h.o 下图是一个可执行文件编译,链接的过程. 本篇将通过一个完整的小工程来阐述E ...

  4. 关于内存泄露分析插件 MAT 的用法

    关于内存泄露分析插件 MAT 的用法,建议大家有时间看一下,下面的文章 http://www.blogjava.net/rosen/archive/2010/05/21/321575.html htt ...

  5. 学会用Clang来进行内存泄露分析

    最近项目出现了内存泄露的问题,对于PC x86平台来说,一点点的内存泄露往往不会出错,很难进行debug调试.这个时候我们可以用到苹果给我们带来的神器--Clang编译器来进行内存泄露分析检测,开关打 ...

  6. 偶发异常BUG,如何高效精准分析排查定位?

    偶发异常BUG,如何高效精准分析排查定位? 作为测试,经常会收到领导.同事.用户反馈过来各种各样BUG,令人措手不及 首选需要判断确认是不是BUG,不要急于给予回复,需有充分的条件给予说明回复 很多测 ...

  7. 性能分析工具gprof介绍(转载)

    性能分析工具gprof介绍Ver:1.0 目录1. GPROF介绍 42. 使用步骤 43. 使用举例 43.1 测试环境 43.2 测试代码 43.3 数据分析 53.3.1 flat profil ...

  8. Okhttp之RealConnection建立链接简单分析

    在之前的博客中我们知道Okhttp在发起链接请求先从链接池中获取连接,如果链接池中没有链接则创建新的链接RealConnection对象,然后执行其connet方法打开SOCKET链接(详见< ...

  9. QQ链接病毒分析

    QQ链接病毒分析 特征 点击病毒链接后,自动会在每一时刻范围内通过所有途径群发新的病毒链接(途径包括Qzone,群聊等) 分析 首先看一下病毒链接的一个样例 http://news.soso.com/ ...

随机推荐

  1. vue组件的一个总结

    用vue进行开发到目前为止也有将近一年的时间了,在项目技术选型的时候隔壁组选 react的时候我们坚持使用vue作为前端的开发框架.虽然两者思想上的差异不大,但是vue的语法在代码的可读性以及后期的维 ...

  2. 学习笔记——SQL SERVER的递归

    SQL SERVER似乎天然具有支持递归的属性. 1.比如说,有几次,我编写或修改存储过程的时候,为图方便,在末尾随手写上 执行这个存储过程 的语句,比如 [sql] view plaincopy A ...

  3. React-Router 中文简明教程(上)

    概述 说起 前端路由,如果你用过前端 MV* 框架构建 SPA 应用(单页面应用),对此一定不陌生. 传统开发中的 路由,是由服务端根据不同的用户请求地址 URL,返回不同内容的页面,而前端路由则将这 ...

  4. 87.Ext_菜单组件_Ext.menu.Menu

    转自:https://blog.csdn.net/lms1256012967/article/details/52574921 菜单组件常用配置: /* Ext.menu.Menu主要配置项表: it ...

  5. js getyear和getfullyear

    getyear()函数如果今年是2015年会得到115,getfullyear()会得到完整的年份.

  6. 5.13Junit单元测试-反射-注解

    一.Junit单元测试 * 测试分类: 1.黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值 2.白盒测试:需要些代码的.关注程序具体的执行流程 Junit使用:白盒测试 步骤: 1.定义 ...

  7. 团队作业-Beta版本发布

    这个作业属于哪个课程  <课程的链接>            这个作业要求在哪里 <作业要求的链接> 团队名称 Three cobblers 这个作业的目标 Beta版本发布报 ...

  8. VM虚拟机中Ubuntu中执行apt-get update失败的解决方法(可能有效)

    首先确保虚拟机是连接网络的,可以用ping命令检测一下看是否连通网络.采用nat网络的时候确保服务是开的. 如果之前执行过apt-get update命令但是失败了,执行一下      rm -rf ...

  9. VHDL之concurrent之when

    WHEN (simple and selected) It is one of the fundamental concurrent statements (along with operators ...

  10. dubbo之上下文信息

    上下文信息 上下文中存放的是当前调用过程中所需的环境信息.所有配置信息都将转换为 URL 的参数,参见 schema 配置参考手册 中的对应URL参数一列. RpcContext 是一个 Thread ...