2014年5月27日星期二

SQL Server 监控统计阻塞脚本信息 - 潇湘隐者

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
SQL Server 监控统计阻塞脚本信息 - 潇湘隐者  阅读原文»

数据库产生阻塞(Blocking)的本质原因 :SQL语句连续持有锁的时间过长 ,数目过多, 粒度过大。阻塞是事务隔离带来的副作用,它是不可避免的,而且是一个数据库系统常见的现象。 但是阻塞的时间和出现频率要控制在一定的范围内,阻塞持续的时间过长或阻塞出现过多(过于频繁),就会对数据库性能产生严重的影响。

很多时候,DBA需要知道数据库在出现性能问题时,有没有发生阻塞? 什么时候开始的?发生在那个数据库上? 阻塞发生在那些SQL语句之间? 阻塞的时间有多长? 阻塞发生的频率? 阻塞有关的连接是从那些客户端应用发送来的?.......

如果我们能够知道这些具体信息,我们就能迅速定位问题,分析阻塞产生的原因, 从而找出出现性能问题的根本原因,并根据具体原因给出相应的解决方案(索引调整、优化SQL语句等)。

查看阻塞的方法比较多, 我在这篇博客MS SQL 日常维护管理常用脚本(二)里面提到查看阻塞的一些方法:

方法1:查看那个引起阻塞,查看blk不为0的记录,如果存在阻塞进程,则是该阻塞进程的会话 ID。否则该列为零。

EXEC sp_who active

方法2:查看那个引起阻塞,查看字段BlkBy,这个能够得到比sp_who更多的信息。

EXEC sp_who2 active

方法3:sp_lock 系统存储过程,报告有关锁的信息,但是不方便定位问题

方法4:sp_who_lock存储过程

方法5:右键服务器-选择"活动和监视器",查看进程选项。注意"任务状态"字段。

方法6:右键服务名称-选择报表-标准报表-活动-所有正在阻塞的事务。

但是上面方法,例如像sp_who、 sp_who2,sp_who_lock等,都有或多或少的缺点:例如不能查看阻塞和被阻塞的SQL语句。不能从查看一段时间内阻塞发生的情况等;没有显示阻塞的时间....... 我们要实现下面功能:

1: 查看那个会话阻塞了那个会话

2:阻塞会话和被阻塞会话正在执行的SQL语句

3:被阻塞了多长时间

4:像客户端IP、Proagram_Name之类信息

5:阻塞发生的时间点

6:阻塞发生的频率

7:如果需要,应该通知相关开发人员,DBA不能啥事情都包揽是吧,那不还得累死,总得让开发人员员参与进来优化(有些问题就该他们解决),多了解一些系统运行的具体情况,有利于他们认识问题、解决问题。

8:需要的时候开启这项功能,不需要关闭这项功能

于是为了满足上述功能,有了下面SQL 语句

SELECT wt.blocking_session_id AS BlockingSessesionId ,sp.program_name AS ProgramName ,COALESCE(sp.LOGINAME, sp.nt_username) AS HostName ,ec1.client_net_address AS ClientIpAddress ,db.name AS DatabaseName ,wt.wait_type AS WaitType ,ec1.connect_time AS BlockingStartTime ,wt.WAIT_DURATION_MS/1000 AS WaitDuration ,ec1.session_id AS BlockedSessionId ,h1.TEXT AS BlockedSQLText ,h2.TEXT AS BlockingSQLText FROM sys.dm_tran_locks AS tl INNER JOIN sys.databases db ON db.database_id = tl.resource_database_id INNER JOIN sys.dm_os_waiting_tasks AS wt ON tl.lock_owner_address = wt.resource_address INNER JOIN sys.dm_exec_connections ec1 ON ec1.session_id = tl.request_session_id INNER JOIN sys.dm_exec_connections ec2 ON ec2.session_id = wt.blocking_session_id LEFT OUTER JOIN master.dbo.sysprocesses sp ON SP.spid = wt.blocking_session_id CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1 CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

我们做一个测试例子来验证一下

1:打开第一会话窗口1,执行下面语句

USE DBMonitor; GO BEGIN TRANSACTION SELECT * FROM dbo.TEST(TABLOCKX); --COMMIT TRANSACTION;

2:打开第二个会话窗口2,执行下面语句

USE DBMonitor; GO SELECT * FROM dbo.TEST

3:打开第三个会话窗口3,执行下面语句

编写高质量代码改善C#程序的157个建议[用抛异常替代返回错误、不要在不恰当的场合下引发异常、重新引发异常时使用inner Exception] - aehyok  阅读原文»

前言  

  自从.NET出现后,关于CLR异常机制的讨论就几乎从未停止过。迄今为止,CLR异常机制让人关注最多的一点就是“效率”问题。其实,这里存在认识上的误区,因为正常控制流程下的代码运行并不会出现问题,只有引发异常时才会带来效率问题。基于这一点,很多开发者已经达成共识:不应将异常机制用于正常控制流中。达成的另一个共识是:CLR异常机制带来的“效率”问题不足以“抵消”它带来的巨大收益。CLR异常机制至少有一下几个优点:

  1、正常控制流会倍立即中止,无效值或状态不会在系统中继续传播。

  2、提供了统一处理错误的方法。

  3、提供了在构造函数、操作符重载及属性中报告异常的便利机制。

  4、提供了异常堆栈,便于开发者定位异常发生的位置。

  另外,“异常”其名称本身就说明了它的发生是一个小概率事件。所以,因异常带来的效率问题会倍限制在一个很小的范围内。实际上,try catch所带来的效率问题几乎忽略的。在某些特定的场合,如Int32的Parse方法中, 确实存在这因为滥用而导致的效率问题。在这种情况下,我们就应该考虑提供一个TryParse方法,从设计的角度让用户选择让程序运行得更快。另一种规避因为异常而影响效率的方法是:Tester-doer模式,下文将详细阐述。

  本章将给出一些在C#中处理CLR异常方面的通用建议,一帮助大家构建和开发一个运行良好和可靠的应用系统。

  本文已同步到http://www.cnblogs.com/aehyok/p/3624579.html。本文主要来学习以下几点建议

  建议58、用抛出异常代替返回错误代码

  建议59、不要在不恰当的场合下引发异常

  建议60、重新引发异常时使用inner Exception

58、用抛出异常代替返回错误代码  

  在异常机制出现之前,应用程序普遍采用返回错误代码的方式来通知调用者发生了异常。本建议首先阐述为什么要用抛出异常的方式来代替返回错误代码的方式。

  对于一个成员方法来说,它要么执行成功,要么执行失败。成员方法成功的情况很容易理解。但是如果执行失败了却没有那么简单,因为我们需要将导致执行失败的原因通知调用者。抛出异常和返回错误代码都是用来通知调用者的手段。

  假设我们要实现这样一个简单的功能:应用程序需要完成一次保存新建用户的操作。这是一个分布式的操作,保存动作除了需要将用户保存在本地外,还需要通过WCF在远程服务器上保存数据。负责保存用户的成员方法如下:

public int SaveUser(User user)
{
if (!SaveToFile(user))
{
return 1;
}
if (!SaveToDataBase(user))
{
return 2;
}
return 0;
}

public bool SaveToFile(User user)
{
return true;
}

public bool SaveToDataBase(User user)
{
return true;
}

如果单纯的看SaveUser方法,似乎一切都还不错,在约定好了错误代码后,调用者只要接收到1或2,就知道到底是那里出现了问题。但仔细研究会发现,如果方法执行失败,似乎还可以挖掘出更多的原因。

假设在SaveToFile方法中,我们可能会遇到:

1、程序无数据存储文件写权限导致的失败。

2、硬盘空间不足导致的失败。

在SaveToDataBase方法中,我们可能会遇到:

1、服务不存在导致的失败。

2、网络连接不正常导致的失败。

当我们想要告诉调用者更多的细节的时候,就需要与调用者约定更多的错误代码。于是我们很快就会发现,错误代码飞速膨胀,直到看起来似乎无法维护。因为我们总在查找并确认错误代码。

  采用接下来的方法,可能会省略很大一部分的错误代码:

public bool SaveUser1(User user,ref string errorMessage)
{
if (!SaveToFile(user))
{
errorMessage
= "本地保存失败";
return false;
}
if (!SaveToDataBase(user))
{
errorMessage
= "远程保存失败";
return false;
}
return true;
}

  这看上去不错,即使存在更多的错误也可以将失败信息呈现给调用者或者上层用户。然后仅仅呈现失败信息就可以了吗?我们来看看这样一种情况:给失败通知增加稍微复杂一点的功能。

  如果本地保存失败,要完成“通知运行本段代码的客户机管理员”的功能。通常情况下,仅仅只需要显示类似的信息:“本地保存失败,请检查用户权限”。如果远程保存失败,应用程序需要“发送一封邮件给远程服务器的系统管理员”。总金额个增加的功能导致我们不能像处理“本地保存失败”那样来处理“远程保存失败”。

  一切仿佛又回到了起点,在没有异常处理机制之前,我们只能返回错误代码,但是现在有了另一种选择,即使用异常机制。如果使用异常机制,那么最终的代码看起来应该是下面这样的:

static void Main(string[] args)
{
try
{
SaveUser(
new User());
}
catch (IOException e)
{
///IO异常,通知当前用户
}
catch (UnauthorizedAccessException e)
{
////权限异常,通知客户端管理员
}
catch (CommunicationException e)
{
///网络异常,通知发送给网络管理员
}
}

public static void SaveUser(User user)
{
SaveToFile(user);

SaveToDataBase(user);
}

  使用CLR异常机制后,我们会发现代码变得更清晰、更易于理解了。至于效率问题,还可以重新审视“效率”的立足点:throw exception产生的那点效率损耗与等待网络连接异常相比,简直微不足道,而CLR异常机制带来的好处却是显而易见的。

  这里需要稍加强调的是,在catch(CommunicationException)这个代码块中,代码所完成的功能是“通知发送”而不是“发送”本身,因为我们要确保在catch和finally中所执行的代码是可以倍执行的。换句话说,尽量不要在catch和finally中再让代码“出错”,那么让异常堆栈信息变得复杂和难以理解。

  在本例的catch代码块中,不要真得编写发送邮件的代码,因为发送邮件这个行为可能会产生更多的异常,而“通知发送”这个行为稳定性更高(即不“出错”)。

  以上通过实际的案例阐述了抛出异常相比于返回错误代码的优越性,以及在某些情况下错误代码将无用武之地,如构造函数、操作符重载及属性。语法特性决定了其不能具备任何返回值,于是异常机制倍当作取代错误代码的首要选择。

59、不要在不恰当的场合下引发异常  

  最常见不易引发异常的情况是对在可控范围内的输入和输出引发异常。如下面的代码所示:

public void SaveUse

阅读更多内容

没有评论:

发表评论