记录数据库执行情况来分析数据库查询性能问题
对于web项目(直接查询数据为主),有时经常会遇到性能问题,而性能瓶颈对于普通的web应用项目来讲,主要是IO瓶颈,而IO中最重要的要数数据库IO。这里我们抛开各种架构,各种缓存机制,只考虑如下应用场景:普通的OA系统,正常三层架构,直接访问数据库查询数据,如果遇到某个页面加载过慢,如何分析呢?
常规方法:
第一:利用各种浏览器工具,测试页面首包时间,排除页面其它资源文件加载的因素。
如首包时间占大部分,说明.net程序运行时间过长,并非页面下载资源文件所造成的原因。某些情况页面加载慢是由于加载了一些第三方的文件,比如广告等。
第二:运行各种.net性能分析工具,精确定位哪部分函数运行时间过长。
缺点:需要有开发环境才能调试,当生产环境出现问题时,总不能在线上环境进行调试吧,而有些性能问题是与软硬件,以及网络环境有重大关系,这也是为什么在测试环境没有问题,到了生产环境就出问题的原因。
第三:将页面调用数据库访问的执行情况记录下来,以供分析。
当.net执行完成一个数据库操作后,将执行的语句,连接串,执行时间,客户端信息全部记录下来,然后做进一步分析。
缺点:需要对代码做额外的改动,或者说会增加额外的工作量。
优点:无论运行在什么环境,均能实时准确的记录当时的执行情况。
第四:由DBA对数据库进行监控,挑选出运行时间最长的需要优化的语句。
缺点:
1:需要DBA支持,或者说需要一名对数据库管理有一定经验的人员来操作。
2:数据库只能监控到少量信息,比如很难知道运行时间最长的语句是由哪次请求,哪个页面产生的。
结合以上四种方法,我来分享下最简单的情况:即第一种与第三种组合,第一种就不多说了,这里主要分享下第三种方法的做法,即如何记录项目中所以针对数据库的访问情况,以及如何更加方便的帮助我们分析一次请求过程中需要优化及注意的地方。
第一部分:如何记录数据库访问信息?
要想分析,首先需要有数据才行,这里有两种方式可供选择:
方式一:利用AOP,将记录数据功能插入代码中,优点是对数据访问组件影响最小,这部分以后有机会再讲,稍微复杂一点。
方式二:修改Ado.net访问数据库的帮助类,比如SqlHelper,手工加入记录数据逻辑。这里就先采取最为直接最为简单的方式。
对于方式二,实现非常简单,就是在方法开始前记录时间,结束后再记录时间,两者的差值就认为是方法的执行时间,这里我们引用一个类:DbContext,继承自IDisposable,在Dispose方法中完成数据记录逻辑,其中记录的信息,可根据实际需求进行编写。
View Code
public void Dispose()
{
if (null != this._stopwatch)
{
this._stopwatch.Stop();
this.ElapsedMilliseconds = this._stopwatch.ElapsedMilliseconds;
string errorInfo = "请求id:{0}\n请求IP:{1}\n查询时间:{2}\n查询SQL:{3}\n";
errorInfo = string.Format(errorInfo,
ContextFactory.GetContext().UniqueID,
ContextFactory.GetContext().ClientAddress,
this.ElapsedMilliseconds,
this .CommandText
);
WebLog.ApplicationLog.CommonLogger.Error(errorInfo);
}
}
复制代码
我们再看下针对SqlHelper需要做怎样的修改。
public static DataSet ExecuteDataset(string connectionString, CommandType commandType, string commandText,
EDataBaseType DataBaseType)
{
using (DbContext context = new DbContext(connectionString,DataBaseType,commandText))
{
//pass through the call providing null for the set of SqlParameters
return ExecuteDataset(context.ConnectionString, commandType, context.CommandText,
context.DataBaseType, (DbParameter[])null);
}
}
复制代码
using (DbContext context = new DbContext(connectionString,DataBaseType,commandText))这条语句就是包装在真正查询语句之上的部分。
第二部分:如何认定多个方法属于一次请求产生的?
用户进行一个页面,一般情况会产生N个动作,即有可能会查询多次数据库,上面最然记录了数据访问情况,但如何将这些记录关联起来呢,即需要知道一次请求,具体执行了哪些语句,它们分别执行的情况。
解决方案就是借助于HttpContext,它能够存储一次请求所有相关内容。
第一:定义一个上下文实例接口,用于存储上下文的各种自定义信息。比如页面请求的guid,页面路径,ip以及用户等业务数据。
View Code
public inte易做图ce IContext : ICloneable
{
/// <sum