C#提升性能"数据库连接打开与关闭"经验分享(附:优化过的DBHelper类) 之配餐系统的开发
2011-03-10 13:57
1121 查看
做程序开发到现在已有三年多的时间了,先不说技术已达到了什么样的一个水平,就对自己熟悉或比较精通的技术等——感觉需要再继续深究或清楚其如何用好(提升性能)的东西还不少[简单的说:就是有些自认为懂的技术,其实未必真懂,了解的可能只是部分或不是合适的用法]。这篇文章要说就是——对程序性能起着很大决定性作用的数据库操作(一般情况下:优化数据库(包括数据库操作),比优化代码对性能提升的效果更显著的多),——数据库连接打开与关闭 的时间和范围。
以下,以几个问题去阐述本文要说的核心!
1. 要及时关闭数据库连接?
——这个答案,是肯定的,即:要及时关闭数据库连接。无论在你的项目里数据库访问(操作)是否有用连接池,都需要及时关闭数据库连接(ps: 连接池的关闭数据库连接,并不是真正意义上的关闭,而是(通过close()方法)将当前使用的连接放回到连接池中)。但却不要及时关闭数据库连接,why?——答案在第二个问题中,将会做出解答。
2.数据库连接打开与关闭,(为了确保'连接用时打开用完立即关闭'的原则),要在每次数据库操作时都去打开和关闭连接吗?
——在给出解答之前,先看如下代码("配餐系统" 中<食物库>分页查询的方法)
大家看后,会发现此方法中,有以下几点值得注意:
a.有些代码后有 "//[*]“——此用于标识所在行代码是执行数据库操作,方便大家能清楚的知道 try/catch 代码块中有几处数据库(连接)操作
b.DBHelper.OpenCon();和DBHelper.CloseCon(); ——大家大概可以知道:此try/catch 代码块中只有一次数据库打开和关闭,——事实上也确实只有一次。再看下其中涉及(调用)到的部分方法:
在 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类中的]:
好了,到这儿,可以对以上两个问题一起做个答复,阐明此文的关键点:数据库连接的打开和关闭,要在当前可见范围内或代码块中 数据库操作开始前 打开连接,在无需(或者说最后一个)数据库操作后 关闭连接,举例:在一个方法或代码块中,如上GetFoodInfosList方法;在一个事件中,如:一个按钮的点击事件中:可能会执行n次数据库 增删改差等操作....
结束语:应该是第一次写这么长的技术文章,写的比较艰难,呵呵...,感觉把自己知道的东西想写的让别人能很容易看懂且不丢失自己想说的,不是一件容易的事。后附的是最新的DBHelper类(里面还有一些地方可以或需要优化),希望路过的朋友能多提意见或交流你的看法!
最新的DBHelper类:
View Code
以下,以几个问题去阐述本文要说的核心!
1. 要及时关闭数据库连接?
——这个答案,是肯定的,即:要及时关闭数据库连接。无论在你的项目里数据库访问(操作)是否有用连接池,都需要及时关闭数据库连接(ps: 连接池的关闭数据库连接,并不是真正意义上的关闭,而是(通过close()方法)将当前使用的连接放回到连接池中)。但却不要及时关闭数据库连接,why?——答案在第二个问题中,将会做出解答。
2.数据库连接打开与关闭,(为了确保'连接用时打开用完立即关闭'的原则),要在每次数据库操作时都去打开和关闭连接吗?
——在给出解答之前,先看如下代码("配餐系统" 中<食物库>分页查询的方法)
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) { List<ZhiyiModel.JustNeed.FoodInfo> list = new List<ZhiyiModel.JustNeed.FoodInfo>(); xxCount = 0; pageCount = 0; //是否需要按营养素排序 bool isNeedOrder = false; Dictionary<int, ZhiyiModel.JustNeed.FoodInfo> dictCx = null; Dictionary<string, string> dictFlName = new Dictionary<string, string>(); string ids = String.Empty; string flId = String.Empty; string flName = String.Empty; string fieldList = " id,name,fid,Sid,type,IsSys "; #region 组合where条件 //省略 #endregion OleDbDataReader reader = null; try { DBHelper.OpenCon(); //得到信息总条数 xxCount = GetFoodInfosXxCount(where);//[*] pageCount = FenyeHelper.GetPageCount(xxCount, pageSize); ZhiyiModel.JustNeed.FoodInfo foodinfo = null; reader = FenyeHelper.PageView_Reader_Other2("food", fieldList, "id", where, "", false, pageSize, currentPage, pageCount, xxCount);//[*] while (reader.Read()) { foodinfo = new ZhiyiModel.JustNeed.FoodInfo(); foodinfo.Id = (int)reader["id"]; foodinfo.IsSys = (int)reader["IsSys"]; foodinfo.FoodName = reader["name"].ToString(); flId = reader["fid"].ToString(); foodinfo.FirstFl = GetFlName(dictFlName, flId, flName);//[*] flId = reader["Sid"].ToString(); foodinfo.SecondFl = GetFlName(dictFlName, flId, flName);//[*] foodinfo.FoodType = reader["type"].ToString() == "0" ? "原料" : "菜肴"; foodinfo.Heat = YysPropertyService.GetYysInfoByFoodId("heat", foodinfo.Id,"0");//[*] if (isNeedOrder) { dictCx.Add(foodinfo.Id, foodinfo); ids += string.IsNullOrEmpty(ids) ? foodinfo.Id.ToString() : "," + foodinfo.Id; } else list.Add(foodinfo); } #region 按营养素排序 if (isNeedOrder && !string.IsNullOrEmpty(ids)) { DBHelper.CloseReader(reader); //[*] reader = DBHelper.GetReader_Other2("select foodid from xxxxxxxxxxxxx", CommandType.Text); int foodId = 0; list.Clear(); while (reader.Read()) { foodId = (int)reader["foodid"]; foodinfo = new ZhiyiModel.JustNeed.FoodInfo(); if (!dictCx.TryGetValue(foodId, out foodinfo)) continue; list.Add(foodinfo); } } #endregion } catch (Exception ex) { throw ex; } finally { DBHelper.CloseReader(reader); DBHelper.CloseCon(); dictCx = null; } return list; }
大家看后,会发现此方法中,有以下几点值得注意:
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; using System.Configuration; namespace ZhiyiHelper { public partial class DBHelper { private static string connstr = String.Empty; private static OleDbConnection conObject = null; public DBHelper() { } #region 基础方法 /// <summary> /// 获取连接字符串的属性 /// </summary> private static string Connstr { get { if (connstr == String.Empty) { connstr = ConfigHelper.GetConnectionStringsString("accessConSql"); connstr = GetConnString(); } return connstr; } } /// <summary> /// 得到数据库连接方法 /// </summary> /// <returns>数据库连接</returns> private static void Getconn() { if (conObject == null) conObject = new OleDbConnection(Connstr); } /// <summary> /// 获得并打开数据库连接方法 /// </summary> /// <returns></returns> public static void OpenCon() { Getconn(); if (conObject.State == ConnectionState.Open) return; if (conObject.State != ConnectionState.Closed) conObject.Close(); conObject.Open(); } /// <summary> /// 关闭数据库连接方法 /// </summary> public static void CloseCon() { if (conObject != null && conObject.State != ConnectionState.Closed) conObject.Close(); } #endregion #region 数据库操作方法 public static int GetMaxID(string FieldName, string TableName) { string strsql = "select max(" + FieldName + ") from " + TableName; try { return GetScalar(strsql); } catch (OleDbException ex) { throw ex; } } /// <summary> /// 删除制定表中的一个字段(文本列) /// </summary> /// <param name="colName"></param> /// <param name="TableName"></param> public static void DelColumn(string colName, string TableName) { if (string.IsNullOrEmpty(colName.Trim())) return; try { Execute("alter table ["+TableName+"] drop COLUMN ["+colName+"]"); } catch (OleDbException ex) { throw ex; } } /// <summary> /// 在制定表中添加一个字段(文本列) /// </summary> /// <param name="colName"></param> /// <param name="TableName"></param> public static void AddColumn(string colName, string TableName) { try { Execute("ALTER TABLE " + TableName + " ADD COLUMN " + colName + " TEXT(100)");//TEXT不加长度,则为此字段类型的默认最大长度 } catch (OleDbException ex) { throw ex; } } /// <summary> /// 在制定表中添加一个字段(数字(double)列) /// </summary> /// <param name="colName"></param> /// <param name="TableName"></param> public static void AddColumn_Double(string colName, string TableName) { try { Execute("ALTER TABLE " + TableName + " ADD COLUMN " + colName + " Double DEFAULT 0"); } catch (OleDbException ex) { throw ex; } } /// <summary> /// 执行增,删,改命令的方法(一) [非存储过程SQL] /// </summary> /// <param name="sql"></param> /// <returns></returns> public static int Execute(string sql) { return Execute(sql, CommandType.Text); } /// <summary> /// 执行增,删,改命令的方法(二) /// </summary> /// <param name="sql"></param> /// <returns></returns> public static int Execute(string sql, CommandType commandType, params OleDbParameter[] sqlParams) { OpenCon(); 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); CloseCon(); } } /// <summary> /// 执行增,删,改命令的方法 ----重载方法 [存储过程] /// </summary> /// <param name="sql">存储过程名</param> /// <param name="sqlParams"></param> /// <returns></returns> public static int Execute(string sql, params OleDbParameter[] sqlParams) { return Execute(sql, CommandType.StoredProcedure, sqlParams); } /// <summary> /// 返回第一行第一列的值 /// </summary> /// <param name="sql"></param> /// <returns></returns> public static int ExecuteScalar(string sql, CommandType comType, params OleDbParameter[] sqlParams) { object reObj = ExecuteScalar1(sql, comType, sqlParams); return reObj == null ? 0 : Convert.ToInt32(reObj); } /// <summary> /// 返回第一行第一列的值 ----非存储过程SQL查询方法 /// </summary> /// <param name="sql"></param> /// <param name="sqlParams"></param> /// <returns></returns> public static int GetScalar(string sql, params OleDbParameter[] sqlParams) { return ExecuteScalar(sql, CommandType.Text, sqlParams); } /// <summary> /// 返回第一行第一列的值 ----[存储过程]重载方法 /// </summary> /// <param name="sql">存储过程名</param> /// <returns></returns> public static int ExecuteScalar(string sql, params OleDbParameter[] sqlParams) { return ExecuteScalar(sql, CommandType.StoredProcedure, sqlParams); } /// <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(); } } /// <summary> /// 返回第一行第一列的值 ----[存储过程]重载方法 /// </summary> /// <param name="sql">存储过程名</param> /// <returns></returns> public static Object ExecuteScalar2(string sql, params OleDbParameter[] sqlParams) { return ExecuteScalar1(sql, CommandType.StoredProcedure, sqlParams); } /// <summary> /// 执行增,删,改命令的方法 ----非存储过程SQL查询方法 /// </summary> /// <param name="sql"></param> /// <param name="sqlParams"></param> /// <returns></returns> public static void ExecuteCommand(string sql, params OleDbParameter[] sqlParams) { Execute(sql, CommandType.Text, sqlParams); } /// <summary> /// 查询返回数据表的方法 ---非存储过程SQL查询方法 /// </summary> /// <param name="sql">Sql语句</param> /// <param name="commandType"></param> /// <param name="sqlParams"></param> /// <returns></returns> public static DataTable GetTable(string sql) { return GetTable(sql, CommandType.Text); } /// <summary> /// 查询返回数据表的重载方法 /// </summary> /// <param name="sql"></param> /// <param name="commandType"></param> /// <param name="sqlParams"></param> /// <returns></returns> public static DataTable GetTable(string sql, CommandType commandType, params OleDbParameter[] sqlParams) { DataSet dataSet = null; Getconn(); OleDbCommand cmd = new OleDbCommand(sql, conObject); cmd.CommandType = commandType; SetParams(cmd, sqlParams); OleDbDataAdapter adp = new OleDbDataAdapter(); adp.SelectCommand = cmd; try { dataSet = new DataSet(); adp.Fill(dataSet, "table"); } catch (Exception ex) { throw ex; } finally { //释放资源 DisponseAdp(adp); DisponseCmd(cmd); CloseCon(); } if (dataSet != null && dataSet.Tables[0] != null) return dataSet.Tables[0]; return null; } /// <summary> /// 查询返回数据表的重载方法 ---非存储过程SQL /// </summary> /// <param name="sql"></param> /// <param name="sqlParams"></param> /// <returns></returns> public static DataTable GetTable(string sql, params OleDbParameter[] sqlParams) { return GetTable(sql, CommandType.Text, sqlParams); } /// <summary> /// 返回OleDbDataReader的方法 /// </summary> /// <param name="sql"></param> /// <param name="commandType"></param> /// <param name="sqlParams"></param> /// <returns></returns> public static OleDbDataReader GetReader(string sql, CommandType commandType, params OleDbParameter[] sqlParams) { OleDbDataReader reader = null; OpenCon(); 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的方法 ---非存储过程的SQL语句 ---重载 /// </summary> /// <param name="sql"></param> /// <param name="sqlParams"></param> /// <returns></returns> public static OleDbDataReader GetReader(string sql, params OleDbParameter[] sqlParams) { return GetReader(sql, CommandType.Text, sqlParams); } /// <summary> /// 执行多条SQL语句,实现数据库事务。 /// </summary> /// <param name="SQLStringList">多条SQL语句</param> public static void ExecuteSqlTran(List<string> SQLStringList) { if (SQLStringList == null || SQLStringList.Count == 0) return; OpenCon(); OleDbCommand cmd = new OleDbCommand(); cmd.Connection = conObject; cmd.CommandType = CommandType.Text; OleDbTransaction tx = conObject.BeginTransaction(); cmd.Transaction = tx; try { string sql = String.Empty; for (int n = 0; n < SQLStringList.Count; n++) { sql = SQLStringList ; if (sql.Trim().Length > 1) { cmd.CommandText = sql; cmd.ExecuteNonQuery(); } } tx.Commit(); } catch (System.Data.OleDb.OleDbException e) { tx.Rollback(); throw e; } finally { //释放资源 if (tx != null) { tx.Dispose(); tx = null; } DisponseCmd(cmd); CloseCon(); } } /// <summary> /// 设置命令中参数的方法 /// </summary> /// <param name="cmd"></param> /// <param name="sqlParams"></param> private static void SetParams(OleDbCommand cmd, params OleDbParameter[] sqlParams) { if (sqlParams != null && sqlParams.Length > 0) cmd.Parameters.AddRange(sqlParams); } #endregion #region 释放资源的方法 private static void DisponseCmd(OleDbCommand cmd) { if (cmd != null) { cmd.Dispose(); cmd = null; } } private static void DisponseAdp(OleDbDataAdapter adp) { if (adp != null) { adp.Dispose(); adp = null; } } #endregion } }
相关文章推荐
- C#提升性能"数据库连接打开与关闭"经验分享(附:优化过的DBHelper类) 之配餐系统的开发
- C#提升性能"数据库连接打开与关闭"经验分享(附:优化过的DBHelper类) 之配餐系统的开发
- C#提升性能"数据库连接打开与关闭"经验分享(附:优化过的DBHelper类) 之配餐系统的开发
- C#提升性能"数据库连接打开与关闭"经验分享(附:优化过的DBHelper类) 之配餐系统的开发
- C#提升性能"数据库连接打开与关闭"经验分享(附:优化过的DBHelper类) 之配餐系统的开发
- WinForm"ZedGraph柱状图"控件使用经验分享 之配餐系统的开发
- WinForm"ZedGraph柱状图"控件使用经验分享 之配餐系统的开发
- WinForm"仿js星形评分效果"控件制作经验分享(原创) 之配餐系统的开发
- WinForm"仿js星形评分效果"控件制作经验分享(原创) 之配餐系统的开发
- C#.NET 大型企业信息化系统 - 防黑客攻击 - SSO系统加固优化经验分享
- SQL性能优化案例:分享一次系统迁移获得的经验
- c#缓存机制,用于大批量连接数据库的性能优化,可减少对数据库的消耗。
- Oracle性能优化经验分享之系统参数设置
- C#.NET 大型企业信息化系统 - 防黑客攻击 - SSO系统加固优化经验分享
- Oracle性能优化经验分享之系统参数设置
- 数据库开发的性能优化经验
- C#.NET 大型企业信息化系统集成快速开发平台 4.2 版本 - 几十套业务系统集中统一授权管理实现经验分享
- 【unity实用技能】性能优化经验分享
- C#.NET软件项目中程序开发外包经验分享【从接包者转变为发包者】
- SQLServer性能优化之 nolock,大幅提升数据库查询性能