每日學習總結:"已有打開的與此命令相關聯的 DataReader,必須首先將它關閉。"、Sql Server 連接池機制


2013-5-25

1.    已有打開的與此命令相關聯的 DataReader,必須首先將它關閉。"問題.

上周在項目開發中,遇到這樣的一個問題,在前台展示頁,兩個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還有連接池的機制,這也是下邊要講的一個內容。

2.    Sql Server 連接池機制

在上一小節的最后提到了,只有在首次進行數據庫連接比較耗費時間和性能之外,以后的連接時間幾乎可以忽略不計,這主要是因為有了數據連接池的原因。

在首次連接時,因為需要解析字符串、授權、檢查、和服務器握手等操作,所以相對來講需要耗費一點時間,但是在首次連接建立之后,這些繁瑣的操作便不會再做了,因為有了連接池機制,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的值適當的設的大一些,這樣會適當的減輕服務器的壓力。

好了,還有些沒總結完,但是今天太累了,就先到這兒吧,希望能給大家帶來一些幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM