上周在项目开发中,遇到这样的一个问题,在前台展示页,两个WebPart调用后台的API进行数据的查询呈现。有时候两个数据报表都能呈现,而有时候,却只能呈现一个报表。使用IE的开发人员调试工具跟踪调试以后,发现了如上提到的问题“已有打开的与此命令相关联的 DataReader,必须首先将它关闭”。
起先以为是API中使用了DataReader进行数据读取,没有关闭,检查了一下代码,发现代码中并没有使用DataReader,然后,以为是SqlConnection没有关闭,又检查了下代码,发现所有的SqlConnection在使用完之后都进行了关闭,只不过在DBHelper(数据库访问类)里边SqlConnection数据库连接对象是一个静态的全局变量。如此这样检查了几遍仍是会报出这样的错误。
在网上查找了一些资料以后终于找到了问题之所在。原来在SqlCommand在每次执行ExecuteNonQuery()方法之后,内部会生成一个空的DataReader对象,该对象只有在数据库连接关闭之后,才会被释放掉,加上上边提到的,在DBHelper类中数据库连接对象是一个静态的全局变量。因为在同时调用API进行数据查询时,在第一个查询还未结束,数据库连接对象还未关闭,第二个查询却已经开始查询,所以才会出现DataReader没有关闭的情况。
那么该如何解决上边的问题呢?
方法有三:
第一种方法:
使用using的形式:
Using(SqlConnection sqlConn=new SqlConnection(“数据库连接字符串”)
{
sqlConn.Open();
SqlCommand com=new SqlCommnand(sqlCon,”sql”);
/执行访问数据库操作代码
}
Using指定了SqlConnection的对象作用的范围,且是独占使用的,当使用结束之后会自动将其进行释放,所以也就很好的解决了上边的问题,即使是同时多次访问,也不会有DataReader未关闭的情况,因为只有一次访问完,释放之后才会进行下一次的访问。
第二中方法:
在数据库连接字符串中添加MultipleActiveResultSets=true即可,示例如下:
server=.;Integrated Security = true;database=Test;MultipleActiveResultSets=true;
SQL Server数据库默认的只有一个活动的SqlDataReader,如果想要一个连接允许多个SqlDataReader,那就需要将MultipleActiveResultSets设置为true,其意义为:将数据库连接设置可复用,即可供多个SqlCommand同时使用。
如果在 MARS 连接下提交两个批处理,一个批处理包含 SELECT 语句,另一个包含 DML 语句,DML 可以在 SELECT 语句执行过程中开始执行。 但是,DML 语句必须运行完成,SELECT 语句才可以继续执行。 如果两个语句在相同事务下运行,读取操作将看不到 DML 语句在 SELECT 语句开始执行后所作的任何更改。
如果打开了启用了MARS会话连接,会创建一个逻辑会话,增加系统的开销,为了减小系统开销提升系统性能,SqlClient会将对MARS对话缓存在连接内,做多可缓存10个对话。
MARS的操作不是线程安全的。如果应用程序打开了两个连接一个为MARS连接一个为一般连接,则这两个连接分别位于独立的连接池中。使用MARS之后,并非不再需要在应用程序中使用多个连接,如果应用程序需要对服务器真正的执行并行命令,还是需要建立多个连接的。
所以,总的来说,虽然这种方法很简单,也能解决问题,但是还是不推荐使用这种方法的。
第三种方法:
之所以会出现上边报出的错误,往往都是因为数据连接对象是静态、全局的对象,相应有很多朋友为了避免多次的声明、创建对象,干脆将该数据连接对象设置为静态的全局的,这样做的确可以省下不少的功夫,但是带来的弊端也是显而易见的,也就是再高并发操作的情况下,会出现上边提到的错误。
因此,这里建议,将数据连接对象设置为局部的,且每次都new一个对象出来,这样做不过是多创建了几个对象,开了几个连接罢了,但是,即使是高并发也不会出现DataReader未关闭的情况。此外,事实上只有在第一次进行数据连接比较耗费时间和性能之外,以后进行的连接操作,所耗费的时间几乎是可以忽略不计的,因为SqlConnection还有连接池的机制,这也是下边要讲的一个内容。
在上一小节的最后提到了,只有在首次进行数据库连接比较耗费时间和性能之外,以后的连接时间几乎可以忽略不计,这主要是因为有了数据连接池的原因。
在首次连接时,因为需要解析字符串、授权、检查、和服务器握手等操作,所以相对来讲需要耗费一点时间,但是在首次连接建立之后,这些繁琐的操作便不会再做了,因为有了连接池机制,SqlConnection默认的开启了连接池的功能,也就是说当程序执行SqlConnection.Close()方法后,连接并不会被立即释放到,而是被存到了连接池中,当下一次再有连接请求时,直接从连接池中调出该连接即可。所以在首次连接成功之后的一段时间内,再次对数据库进行连接时,连接过程时不耗费任何连接时间的,但是,如果过了20分钟之后再去进行数据库的连接,这个时候,连接时间又会和首次连接所耗费的时间一样长。这是因为连接池和Season一样都会过期的,超过了一定的时间,就会被收回,一般默认的时间为5-10分钟,如果在此期间没有任何的连接数据库操作,该连接就会被释放。
有没有只需一次连接,变永远的保持连接状态不变的方法呢?事实上,在连接池中有一项设置为最小连接池大小,默认为0,只需将其值设置为大于0 即可永久的保持连接。
示例如下:
Data Source=.; Initial Catalog=Test; Integrated Security=True;Min Pool Size=1
有关多线程访问的问题(通过new SqlConnection()方式访问),如果后一个线程在前一线程关闭之前进行Open()操作,那么此时会创建一个新的物理连接,反之,后一个线程会使用前一个线程的物理连接。但是在实际的运用中,往往会有并发的情况出现,为了尽可能少的创建新的物理连接,我们可以将Min Pool Size的值适当的设的大一些,这样会适当的减轻服务器的压力。
好了,还有些没总结完,但是今天太累了,就先到这儿吧,希望能给大家带来一些帮助。