Wizard 編程網

一位台灣的程序員,鑽研 OOP、ASP.NET、ADO.NET、Ajax、Control & Component development、Performance Tuning

博客园 首页 新随笔 联系 订阅 管理
  27 Posts :: 0 Stories :: 80 Comments :: 1 Trackbacks

(本帖在版工的旧 Blog 中,发表日期为 2007/05/01)

.NET 1.x C#.NET 2.0 的各种语言中,有所谓的 using statement (如本 blog 上一篇帖子「使用ADO.NET NextResult 方法取得多个Result Set」的代码范例),可保证自动 dispose (释放) unmanaged object (对象) 所占用的资源,包括因未处理的 exception 而造成区块结束 (StackOverflowException 除外),系统都会 dispose 资源。因此若您在 using 区块中建立了数据库的 connection,即无须再手动 close connection,亦无须再下 Connection.Dispose()Command.Dispose() 等指令。根据 MSDN Library 和目前市面上几本 ADO.NET 2.0 原文书都有提到,在 using 区块中会自动去做 dispose 的动作。

.NET
garbage collector 会自动释放不再使用的 managed resources 所占用的内存,不用程序员手动撰码;但 unmanaged resources 则需要程序员自行下 Dispose() 去做处理,以让对象彻底终止 unmanaged resources 的使用。例如传统的做法,常会在 try-catch-finally pattern 中去呼叫 Dispose 方法;但若是数据库的联机,则必须有不同考虑,因为若任意下 Dispose 提早回收,也可能导致联机无法有效地被重复引用。

现在大部分的数据库和 Data Provider 都有支持 Connection Pooling 机制,亦即在建立完数据库的联机后,当程序员呼叫 Close 方法关闭一个数据库的 Connection 对象时,.NET 的 Data Provider 并不会将这个对象所占用的内存空间释放掉,而是将此对象暂存至 Pool 之中 ,以便待会可以再重复使用。

若在设定时间 (默认为 60 秒) 内,没有应用程序使用到此对象,或是呼叫了 Dispose 方法,则 .NET Data Provider 才会真正关闭这个联机,并由 Garbage Collector 自动将资源收回。因此,常有 web 程序员在网络上各讨论区提到,是否有必要在呼叫 Close 方法后,再呼叫 Dispose 方法,并将 Connection 设为 Nothing (或 Null)?答案是不必要的。因为 GC 过一阵子就会自动回收未再被参照的联机,手动呼叫 Dispose 只不过提早回收的动作而已。而且若是该联机,可能会在短时间内被大量使用者同时存取的话,也应让其待在 Pool 中待命,而应避免手动呼叫 Dispose 方法,导致它被真正关闭并被回收,而无法有效地被重复使用。

由于 GC 只会在系统闲置或内存不足时才启动,因此除非是使用频率非常繁复的资源,否则交由 GC 自行处理即可。而设为 Nothing (或 Null) 也只是将 Connection 变量的位置清除 (Null Reference),事实上原来 New 出来的 Connection 对象还是存在。而 Dispose 方法是用来处理自行建立的 Windows 资源,但又不会自行释放的对象,如档案 (开档与关文件)、GDI 对象 (直接由 Win API 叫用)…等等。


接下来,再回头探讨 .NET VB/C# 语言中都有的 using statementusing 语句算是简易版的 try-finally pattern,可让程序员以较简便的写法尽早去释放资源,尤其最适用在有限的 unmanaged resources 上,例如:档案和串流 I/OSocket 网络连接、File Handle (档案控制代码)COM 组件、绘图和字形、数据库存取、WorkflowRuntime (WF)等等的内存自动释放。using statement 遇到例外时,也会抛出例外 (throw),但不会去 catch 处理例外;因此若您想要自行处理例外的话,只能回归传统的 try-catch-finally 写法。

提供给 using statement 的对象必须实作 IDisposable 接口。若是自己写的 class,只要实作 System.IDisposable 接口,即拥有 Dispose 方法。之后若引用 using statement 去释放这个 class instance,即会自动做 object Close()Dispose()、设定为 null (Nothing) 这三种动作,不需要再自己手动处理。反而若是自己手动多下一次 Close(),会让 CLR 浪费资源多做一次处理,反倒会影响程序「性能 (performance)。根据国外网站ADO.NET 2.0 书籍证实,若 using 语句搭配 CommandBehavior.CloseConnection 一起使用,其重复关闭数据库联机的动作,会大幅地降低程序性能,处理时间甚至会多出 84% 以上 (叫用 ExecuteReader() 时,若搭配 CommandBehavior 枚举值 (enumerated value),可要求在查询完成后,自动关闭数据库联机)

此外,using statement 也可多层巢状地使用,例如:第一层的 using statement 里包 SqlConnection 的宣告及 instance 的新增,第二层包 SqlCommand,第三层包 SqlDataReader。您亦可在巢状的 using statement 中指定多种的系统资源,包括数据库的 transaction 交易处理。



-------------------------------------------------------------------------
参考文件:
[1] 「
Prescriptive Architecture - Improving ADO.NET Performance
」, MSDN
[2] 「Choose the right .NET data provider, optimize application performance」,  SearchWinDevelopment.com

[3]
多本中英文 ADO.NET 书籍、网络技术文件

posted on 2008-07-13 12:08 WizardWu 阅读(1471) 评论(16)  编辑 收藏 所属分类: dotNET 程序語言ADO.NET & LINQ

Feedback

#1楼  2008-07-13 13:01 junshanhudazhaxi      
看了楼主的好几篇文章,写的都很有条理,讲的东西都很清楚.
  回复  引用  查看    

#2楼 [楼主] 2008-07-13 17:33 吳宇澤      
感谢您的回应。

  回复  引用  查看    

#3楼  2008-07-13 19:03 Angel Lucifer      
Close 方法等效于 Dispose 方法,它只是 Dispose 方法的别名而已,都是调用内部的 Dispose(true)。
  回复  引用  查看    

#4楼  2008-07-13 20:50 金色海洋(jyk)      
无语 。
  回复  引用  查看    

#5楼 [楼主] 2008-07-13 22:26 吳宇澤      
各位大大好:

就数据库的联机而言,若仅呼叫 Dispose 方法,确实也包含 Close 方法关闭数据库联机的功能没错,但 Dispose 会强制「马上」做资源回收 (garbage collect) 的动作。但如此一来,此一联机就无法放在 Connection Pool 重复使用,若后续有同样的数据库联机需求,就又要耗费 database 资源,重新建立联机、开启联机、建立多个联机相关对象…等动作。

但若仅呼叫 Close 方法,会把此对象暂存至 Pool 之中 ,以便再重复使用。若在设定时间 (默认为 60 秒) 内,没有应用程序使用到此对象,.NET Data Provider 才会真正关闭这个数据库联机,并由 Garbage Collector 自动将资源收回。

因此您的系统,若有某个页面,会在短时间内有大量的用户涌入,如:订车票系统、竞标系统、投票系统,应用 Close 方法就好,以让数据库联机能放在 Connection Pool 中重复使用,避免用 Dispose 方法、using statement (会自动去做 dispose 的动作)。否则就变成每个用户,都要重复建立数据库联机、消耗许多数据库的硬件资源,无法用到 Connection Pool 的好处。

一般 ASP.NET 书上并不会写这些技巧,甚至有些入门书的作者自己都搞不清楚,所有的代码范例都用同一种写法 (尤其在台湾有很多这种入门书)。只有研读原文书籍或文件才会提到这些观念。

  回复  引用  查看    

#6楼  2008-07-14 01:00 Angel Lucifer      
@吳宇澤
感谢解答。
您的观点是正确的。
因为在 I/O Operations 中 Close 和 Dispose 的确一样,所以俺想当然了。抱歉。
看来使用ADO.NET 中的 Close 和 Dispose ,还是要分场景。

但仍有一处疑惑,设定时间 (默认为 60 秒)有什么依据吗?
俺查阅的资料默认值是 4~8分钟内的一个随机值。

此外,Dispose 虽然会使得 Connection Pool 失效(Source为 this._poolGroup = null;
),但 GC 应该不会立刻强制进行资源回收操作。是否回收,视 GC 认为有无必要来决定。

恳请再次指正。
  回复  引用  查看    

#7楼 [楼主] 2008-07-14 09:55 吳宇澤      
您好:

我5楼的响应,应该是写错了。呼叫 Dispose,应是会让该对象不放入 Pool,并且无法重复使用,
而不是「马上」做资源回收 (garbage collect) 的动作。
待会回家再去查 ADO.NET 2.0 的原文书看看。

GC 只会在系统闲置或内存不足时才启动,因为启用 GC 的垃圾回收,也是会消耗系统资源的动作。
感谢您的指正。


另外「默认为 60 秒」,是一本台湾的 ADO.NET 3.5 书籍写的,作者为「许薰尹」,
是台湾一家「恆逸資訊」的讲师,出版社为「悦知」出版社。
60 秒这一点,我只是看书上写的,小弟我自己尚未测试过。

而 OleDb、SqlClient Data Provider,都支持 Connection Pooling 机制,这点是确定的。


  回复  引用  查看    

#8楼  2008-07-14 11:09 airwolf2026      
楼主好啊.呵呵,这几天也被一个Connection问题搞的头痛,昨天看了下ADO.NET里面有关连接池的一些东西.里面确实没有提到没有使用的连接,啥时候会被关闭,(不知道是不是俺没有仔细看).另外一个就是,每当连接字符串不一样的时候,默认就会新建一个相应的连接池.俺现在有个程序,就是给用户查询数据库相关字段功能....恰恰这时候,连接字符串是会根据用户选择的数据库不同而改变的....一旦用户多了...会把数据库可用连接给耗光,不知道该如何处理
------------------------------------------------------
题外话:台湾一些词汇和大陆都不一样了...
  回复  引用  查看    

#9楼  2008-07-14 11:26 Duron800 [未注册用户]
不错,学习了,就是语言不太习惯
  回复  引用    

#10楼 [楼主] 2008-07-14 12:44 吳宇澤      
回 8 楼的 airwolf2026 大大:

若您用户连的数据库不同,或联机账号、密码不同,则肯定无法共享 Conn Pool 里的联机对象,
一定会建立全新的联机。

以下节录自小弟我 7 楼回应,提到的那本 ADO.NET 3.5 书籍:
「当应用程序要求建立一个联机时,Data Provider 会检视连接字符串 (包含: 数据库名称、联机
账号、联机密码、…等数据)。若能够从 Conn Pool 找到一个联机条件「一模一样」的联机对象,
就可以直接使用此对象连接至数据库、节省许多数据库资源,否则将建立一个全新的联机对象。」


至于您接下来提到的「一旦用户多了...会把数据库可用连接给耗光」,
小弟虽然经验有限,但提供以下想法给您参考:

(1) 您的数据库 Connection.Close() 可能是写在 try...catch...finally block 的 try block,而不是 finally block,
造成某些特殊情况时,无法正确关闭数据库联机,造成联机一下子就被用完、网站当机。

(2) 您的数据库是试用版或 Developer 版,而不是 Standard、Enterprise 版,因此有同时联机数量的限制。

(3) 用了过于严格的数据库 Transaction「交易隔离等级」,造成多人同时上线时,有些 Record 一直被
lock 住无法使用,因此使用者一直占着数据库联机,甚至产生 deadlock 死结。

// 交易隔离等级 (ReadUncommitted 最宽松,Serializable 最严)
public class MyTransactionLevel
{
public static string strLevel1_Chaos = "Chaos";
public static string strLevel2_ReadCommitted = "ReadCommitted";
public static string strLevel3_ReadUncommitted = "ReadUncommitted";
public static string strLevel4_RepeatableRead = "RepeatableRead";
public static string strLevel5_Serializable = "Serializable";
public static string strLevel6_Snapshot = "Snapshot";
public static string strLevel7_Unspecified = "Unspecified";
}

(4) 硬件等级不够应付太多人同时上线。砸钱,建立数据库丛集 (cluster)、负载平衡 (loading balance)
的数据库、ap server 相互支持网络。

  回复  引用  查看    

#11楼  2008-07-14 13:59 airwolf2026      
多谢楼主反馈.俺再去试验下.
  回复  引用  查看    

#12楼 [楼主] 2008-07-14 22:19 吳宇澤      
回 6 楼的 Angel Lucifer 大大,您是对的,台湾的那本 ADO.NET 3.5 中文书写的应该是错的。

本帖中的第四段,提到「若在设定时间 (默认为 60 秒) 内,没有应用程序使用到此对象,则 .NET Data Provider 才会
真正关闭这个联机」

「默认 60 秒」,应该也是错的。


Programming Microsoft ADO.NET 2.0 Core Reference
作者: David Sceppa
出版社: Microsoft Press

http://www.amazon.com/Programming-Microsoft%C2%AE-ADO-NET-Core-Reference/dp/073562206X/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1216044560&sr=8-1

p.72

When you call the Close method, SqlClient returns the connection to the pool. Assuming that connection is not re-used,
it will be removed from the pool after approximately five minutes. There is no exact number of seconds involved.
The behavior depends on random number generation.....


  回复  引用  查看    

#13楼 [楼主] 2008-07-15 09:51 吳宇澤      
catch (Exception ex)
{
  throw new Exception("發生 OleDb 以外的error: " + ex.Message);
}
finally
{
  odCmd.Dispose();
  odAdapter.Dispose();
  ds.Dispose();
  if (odConn.State == ConnectionState.Open)
  {
    odConn.Close();
  }

  // 错误的观念和写法 (很多ASP.NET书上也这样写)
  //odConn.Dispose();
  // SELECT 使用极频繁,应避免下 Dispose(),才能有效利用 Connection Pooling,避免大量重复建立数据库联机、提升效能
}

  return ds;

} // end of SelectDataSetBindGridView()


#endregion region *** GridView 在 DAL 層,執行 SELECT 時的函數 ***

  回复  引用  查看    

#14楼 [楼主] 2008-07-16 14:16 吳宇澤      
http://blogs.msdn.com/angelsb/archive/2004/08/25/220333.aspx
  回复  引用  查看    

#15楼 [楼主] 2008-07-16 14:35 吳宇澤      
以下证明本帖的观念是正确的,以 db connection 来说,
dispose(), close() 都会关闭联机,但 dispose() 会将
pool 中的 connection 对象相关数据清除(Dispose clears the connection string),
造成 pool 中的 connection 对象比对时不符合,
永远无法被复用。
正确做法,在 finally block 中,
connection 只要 close 即可,不需要再 dispose。


http://msdn.microsoft.com/en-us/library/ms998569.aspx

Using either the Close method or the Dispose method is sufficient. You do not have to call one method after the other. There is no benefit to calling one method after the other.

Dispose internally calls Close. In addition, Dispose clears the connection string.

If you do not call Dispose or Close, and if you do not use the using statement, you are reliant upon the finalization of the inner object to free the physical connection.

Use the using statement, instead of Dispose or Close, when you are working with a single type, and you are coding in Visual C#®. Dispose is automatically called for you when you use the using statement, even when an exception occurs.

If you do not use the using statement, close connections inside a finally block. Code in the finally block always runs, regardless of whether an exception occurs.

You do not have to set the SqlConnection reference to null or Nothing because there is no complex object graph. Setting object references to null or to Nothing is usually done to make a graph of objects unreachable.


  回复  引用  查看    

#16楼 [楼主] 2008-07-19 20:06 吳宇澤      

总结论:

实际以 SQL Server 的 SQL Profiler 监测工具来观察,
就数据库的 Connection 来说,有没有下 Connection.Dispose() 都无差别,
Connection Pooling 一定都能自动重复使用同一条 Connection,以提升数据库 performance、节省系统资源。

重点是,只要在程序中,记得下 Connecion.Close(),把联机放回 Pool,即可不断地重复使用同一条数据库联机。至于有没有下 Dispose() 都无差别。但这是就数据库联机而言,至于其它种类的 .NET Unmanaged Object 则不清楚。

因此小弟我自己 15 楼的论点,还是有一点是错的,亦即 Dispose() 虽然会清除 VS 2005 中,DB Connection 的联机信息 (下断点即看得出来),但实际以 SQL Profiler 观察,仍够能让其它人的联机请求,都持续使用 Pooling 中的同一条联机,并不会造成联机无法被复用,所以 Dispose() 并不会影响 Pooling 的功能。

但只要页面中,有使用 ObejctDataSource 这类数据来源控件,则Connecion.Close() 后,一定会自动抛弃联机对象、联机字符串…等信息。虽然后续其它人的联机,仍能在 Pooling 中重复使用这同一条联机,但无可避免的,一定要重复做建立联机对象、建立联机字符串…等一堆动作,而多多少少影响到AP Server、DB Server 的系统 performance,但这是ObejctDataSource 控件这类的特性,不管有没有下 Connecion.Dispose() 皆然。

因此除非用到 GridView 控件,非得用到 ObejctDataSource 控件不可,否则一般的数据库联机,最好手动自己写 ADO.NET 程序,应避免用ObejctDataSource、SqlDataSource 控件,可提升程序 performance,节省 AP Server、DB Server 内存和硬件资源耗用。



  回复  引用  查看