• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

C#提升性能"数据库连接打开与关闭"经验分享(附:优化过的DBHelper类)之配餐 ...

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

     做程序开发到现在已有三年多的时间了,先不说技术已达到了什么样的一个水平,就对自己熟悉或比较精通的技术等——感觉需要再继续深究或清楚其如何用好(提升性能)的东西还不少[简单的说:就是有些自认为懂的技术,其实未必真懂,了解的可能只是部分或不是合适的用法]。这篇文章要说就是——对程序性能起着很大决定性作用的数据库操作(一般情况下:优化数据库(包括数据库操作),比优化代码对性能提升的效果更显著的多),——数据库连接打开与关闭 的时间和范围。

    以下,以几个问题去阐述本文要说的核心!

   1. 要及时关闭数据库连接?

   ——这个答案,是肯定的,即:要及时关闭数据库连接。无论在你的项目里数据库访问(操作)是否有用连接池,都需要及时关闭数据库连接(ps: 连接池的关闭数据库连接,并不是真正意义上的关闭,而是(通过close()方法)将当前使用的连接放回到连接池中)。但却不要及时关闭数据库连接,why?——答案在第二个问题中,将会做出解答。

   2.数据库连接打开与关闭,(为了确保'连接用时打开用完立即关闭'的原则),要在每次数据库操作时都去打开和关闭连接吗?

   ——在给出解答之前,先看如下代码("配餐系统" 中<食物库>分页查询的方法)

1 public static IList<ZhiyiModel.JustNeed.FoodInfo> GetFoodInfosList(string key, int type, int fid, int sid, string yysZdName, int pageSize, int currentPage, ref int xxCount, ref int pageCount)
2 {
3 List<ZhiyiModel.JustNeed.FoodInfo> list = new List<ZhiyiModel.JustNeed.FoodInfo>();
4 xxCount = 0;
5 pageCount = 0;
6
7 //是否需要按营养素排序
8   bool isNeedOrder = false;
9 Dictionary<int, ZhiyiModel.JustNeed.FoodInfo> dictCx = null;
10 Dictionary<string, string> dictFlName = new Dictionary<string, string>();
11 string ids = String.Empty;
12 string flId = String.Empty;
13 string flName = String.Empty;
14
15 string fieldList = " id,name,fid,Sid,type,IsSys ";
16 #region 组合where条件
17 //省略
18   #endregion
19
20 OleDbDataReader reader = null;
21 try
22 {
23 DBHelper.OpenCon();
24 //得到信息总条数
25 xxCount = GetFoodInfosXxCount(where);//[*]
26
27 pageCount = FenyeHelper.GetPageCount(xxCount, pageSize);
28
29 ZhiyiModel.JustNeed.FoodInfo foodinfo = null;
30
31 reader = FenyeHelper.PageView_Reader_Other2("food", fieldList, "id", where, "", false, pageSize, currentPage, pageCount, xxCount);//[*]
32
33 while (reader.Read())
34 {
35 foodinfo = new ZhiyiModel.JustNeed.FoodInfo();
36 foodinfo.Id = (int)reader["id"];
37 foodinfo.IsSys = (int)reader["IsSys"];
38 foodinfo.FoodName = reader["name"].ToString();
39 flId = reader["fid"].ToString();
40 foodinfo.FirstFl = GetFlName(dictFlName, flId, flName);//[*]
41
42 flId = reader["Sid"].ToString();
43 foodinfo.SecondFl = GetFlName(dictFlName, flId, flName);//[*]
44
45 foodinfo.FoodType = reader["type"].ToString() == "0" ? "原料" : "菜肴";
46 foodinfo.Heat = YysPropertyService.GetYysInfoByFoodId("heat", foodinfo.Id,"0");//[*]
47 if (isNeedOrder)
48 {
49 dictCx.Add(foodinfo.Id, foodinfo);
50 ids += string.IsNullOrEmpty(ids) ? foodinfo.Id.ToString() : "," + foodinfo.Id;
51 }
52 else
53 list.Add(foodinfo);
54 }
55
56 #region 按营养素排序
57 if (isNeedOrder && !string.IsNullOrEmpty(ids))
58 {
59 DBHelper.CloseReader(reader);
60 //[*]
61 reader = DBHelper.GetReader_Other2("select foodid from xxxxxxxxxxxxx", CommandType.Text);
62 int foodId = 0;
63 list.Clear();
64 while (reader.Read())
65 {
66 foodId = (int)reader["foodid"];
67 foodinfo = new ZhiyiModel.JustNeed.FoodInfo();
68 if (!dictCx.TryGetValue(foodId, out foodinfo))
69 continue;
70 list.Add(foodinfo);
71 }
72 }
73 #endregion
74 }
75 catch (Exception ex)
76 {
77 throw ex;
78 }
79 finally
80 {
81 DBHelper.CloseReader(reader);
82 DBHelper.CloseCon();
83 dictCx = null;
84 }
85 return list;
86 }

    大家看后,会发现此方法中,有以下几点值得注意:

    a.有些代码后有 "//[*]“——此用于标识所在行代码是执行数据库操作,方便大家能清楚的知道 try/catch 代码块中有几处数据库(连接)操作

    b.DBHelper.OpenCon();和DBHelper.CloseCon(); ——大家大概可以知道:此try/catch 代码块中只有一次数据库打开和关闭,——事实上也确实只有一次。再看下其中涉及(调用)到的部分方法:

  

private static string GetFlName(Dictionary<string, string> dictFlName, string flId, string flName)
{
flName
= string.Empty;
if (!string.IsNullOrEmpty(flId) && flId != "0")
{
if (!dictFlName.TryGetValue(flId, out flName))
{
flName
= ShiwuClassService.GetShiwuClassNameById(flId);
dictFlName.Add(flId, flName);
}
}
return flName;
}

/// <summary>
/// [Notice Conn]
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static string GetShiwuClassNameById(string id)
{
object obj = DBHelper.ExecuteScalar_Object_Other(string.Format("select name from xxxx where id={0}",id), CommandType.Text);
return obj == null ? "" : obj.ToString();
}

/// <summary>
/// 返回第一行第一列的值[Object] (此方法需要 手动(即调用OpenCon(); CloseCon();方法)打开和关闭连接)
/// </summary>
/// <returns></returns>
public static object ExecuteScalar_Object_Other(string sql, CommandType comType, params OleDbParameter[] sqlParams)
{
OleDbCommand cmd
= new OleDbCommand(sql, conObject);
cmd.CommandType
= comType;
cmd.CommandTimeout
= 180;
DBHelper.SetParams(cmd, sqlParams);

try
{
return cmd.ExecuteScalar();
}
catch (OleDbException ex)
{
throw ex;
}
finally
{
//释放资源
DisponseCmd(cmd);
}
}

  在 while循环代码块中 有两次GetFlName方法(此方法最终是对ExecuteScalar_Object_Other方法)的调用,这样如果是reader中有20条记录, 并且在ExecuteScalar_Object_Other方法内部(查询)操作开始前打开连接,结束时关闭连接,此while循环代码块执行完——将可能有20*2=40次的数据库连接打开与关闭操作,假设:每次数据库连接打开与关闭操作需要0.1s的时间,那么此while循环代码块将需要至少0.1*40=4s的时间执行,再加上其它的查询或更多更频繁的数据库操作, 效率就可想而知。【这个得回到在此文一开始所说的,“有些东西你以为弄清楚明白了,其实未必”。在之前未做winform开发(确切的说是 没有使用access数据库时),数据库操作方法,都是如下方法([旧DBHelper类中的]:即在方法内部,连接即开即关。也是网上很多通用DBHelper数据库操作类中的写法),因为在项目中用的都是mysql,sqlserver这种大型的数据库, 即使不用连接池,数据量不大的情况下,查询等速度都比access数据库要快的多,那时还感觉自己略作优化过的DBHelper类已经够用了,效果也还不错。但是在做winform"配餐系统"开发时,用的是access数据库,还用之前的DBHelper类,也是做GetFoodInfosList方法中相同的查询操作(当时还没考虑分页),问题就暴露出来了——只查询10条左右的记录,界面却等待了3s左右,结果才(卡)出来。  出现了问题,只能查看代码思考解决问题:数据库操作代码还是之前项目中的DBHelper类中,为什么会查询速度如此之慢呢?后慢慢想明白和知道:access数据库跟mysql,sqlserver等大型的数据库相比,性能差了很多,数据库性能差,而也不考虑换用其它的数据库,只能在代码上做优化,于是修改了DBHelper类,类似于重构了部分方法——以适应不同情况下的需要。】

[旧DBHelper类中的]:

/// <summary>
/// 返回第一行第一列的值[Object]
/// </summary>
/// <returns></returns>
public static object ExecuteScalar1(string sql, CommandType comType, params OleDbParameter[] sqlParams)
{
OpenCon();
OleDbCommand cmd
= new OleDbCommand(sql, conObject);
cmd.CommandType
= comType;
cmd.CommandTimeout
= 180;
DBHelper.SetParams(cmd, sqlParams);

try
{
return cmd.ExecuteScalar();
}
catch (OleDbException ex)
{
throw ex;
}
finally
{
//释放资源
DisponseCmd(cmd);
CloseCon();
}
}

     好了,到这儿,可以对以上两个问题一起做个答复,阐明此文的关键点:数据库连接的打开和关闭,要在当前可见范围内或代码块中 数据库操作开始前 打开连接,在无需(或者说最后一个)数据库操作后 关闭连接,举例:在一个方法或代码块中,如上GetFoodInfosList方法;在一个事件中,如:一个按钮的点击事件中:可能会执行n次数据库 增删改差等操作....

    结束语:应该是第一次写这么长的技术文章,写的比较艰难,呵呵...,感觉把自己知道的东西想写的让别人能很容易看懂且不丢失自己想说的,不是一件容易的事。后附的是最新的DBHelper类(里面还有一些地方可以或需要优化),希望路过的朋友能多提意见或交流你的看法!

最新的DBHelper类:

View Code
using System;
using System.Collections.Generic;
using System.Text;
using System.Data.OleDb;
using System.Data;

namespace ZhiyiHelper
{
public partial class DBHelper
{
/**
* 以下的方法需要在调用的可见区域内:手动(即调用OpenCon(); CloseCon();方法)打开和关闭连接
* 方法注释中有[Notice Con] 或 [Notice Connection] 或方法名中含有“_Other”,则调用的是以下的方法
*
*/

#region 数据库操作方法

/// <summary>
/// 执行增,删,改命令的方法
/// </summary>
/// <param name="sql"></param>
/// <returns></returns>
public static int Execute_Other(string sql, CommandType commandType, params OleDbParameter[] sqlParams)
{
OleDbCommand cmd
= new OleDbCommand(sql, conObject);
cmd.CommandType
= commandType;
cmd.CommandTimeout
= 180;
SetParams(cmd, sqlParams);

try
{
return cmd.ExecuteNonQuery();
}
catch (OleDbException ex)
{
throw ex;
}
finally
{
//释放资源
DisponseCmd(cmd);
}
}

/// <summary>
/// 返回第一行第一列的值[Int]
/// </summary>
/// <param name="sql"></param>
/// <returns></returns>
public static int ExecuteScalar_Int_Other(string sql, CommandType comType, params OleDbParameter[] sqlParams)
{
object reObj = ExecuteScalar_Object_Other(sql, comType, sqlParams);
return reObj == null ? 0 : Convert.ToInt32(reObj);
}

/// <summary>
/// 返回第一行第一列的值[Object]
/// </summary>
/// <returns></returns>
public static object ExecuteScalar_Object_Other(string sql, CommandType comType, params OleDbParameter[] sqlParams)
{
OleDbCommand cmd
= new OleDbCommand(sql, conObject);
cmd.CommandType
= comType;
cmd.CommandTimeout
= 180;
DBHelper.SetParams(cmd, sqlParams);

try
{
return cmd.ExecuteScalar();
}
catch (OleDbException ex)
{
throw ex;
}
finally
{
//释放资源
DisponseCmd(cmd);
}
}


/// <summary>
/// 返回OleDbDataReader的方法
/// </summary>
/// <param name="sql"></param>
/// <param name="commandType"></param>
/// <param name="sqlParams"></param>
/// <returns></returns>
public static OleDbDataReader GetReader_Other(string sql, CommandType commandType, params OleDbParameter[] sqlParams)
{
OleDbDataReader reader
= null;

OleDbCommand cmd
= new OleDbCommand(sql, conObject);
cmd.CommandType
= commandType;

SetParams(cmd, sqlParams);
try
{
reader
= cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
catch (Exception ex)
{
throw ex;
}
finally
{
//释放资源
DisponseCmd(cmd);
}
return reader;
}

/// <summary>
/// 返回OleDbDataReader的方法 [reader和数据库连接都需要显示关闭]
/// </summary>
/// <param name="sql"></param>
/// <param name="commandType"></param>
/// <param name="sqlParams"></param>
/// <returns></returns>
public static OleDbDataReader GetReader_Other2(string sql, CommandType commandType, params OleDbParameter[] sqlParams)
{
OleDbDataReader reader
= null;

OleDbCommand cmd
= new OleDbCommand(sql, conObject);
cmd.CommandType
= commandType;

SetParams(cmd, sqlParams);
try
{
reader
= cmd.ExecuteReader();
}
catch (Exception ex)
{
throw ex;
}
finally
{
//释放资源
DisponseCmd(cmd);
}
return reader;
}



/// <summary>
/// 执行多条SQL语句,实现数据库事务。
/// </summary>
/// <param name="SQLStringList">多条SQL语句</param>
public static void ExecuteSqlTran_Other(List<string> SQLStringList)
{
if (SQLStringList == null || SQLStringList.Count == 0)
return;
OleDbCommand cmd
= new OleDbCommand();
cmd.Connection
= conObject;
cmd.CommandType
= CommandType.Text;

OleDbTransaction tx
= conObject.BeginTransaction();
cmd.Transaction
= tx;
try

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
C#ASP.NET中Process.Start没有反应也没有报错的解决方法发布时间:2022-07-13
下一篇:
c#开源框架发布时间:2022-07-13
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap