說到數據庫連接,這個大家都很熟悉了。但是熟悉一般來自於下面三種情況
* 剛開始學編程的時候,老師就說用完的數據庫連接一定要關閉,不然會有嚴重的後果。
* 編程一段時間後,大家都說要用連接池來優化數據庫連接。
* 編程幾年後,老大們說要考慮一台服務器mysql的並發連接數與負載等。
所以不停留在聽說的層面,深入去學習與研究下mysql的連接機制與.net mysql驅動對連接的管理也挺有必要的。
打開navicate for mysql->選擇“工具”->服務器監控->勾選要監控的服務器
就可以看到這個服務器的實時連接情況,每隔5秒刷新一次。
每打開一個連接多一個,打開數據庫的時候對一個,打開查詢的時候也會多一個
如圖的:1463 1465 1466就是剛才打開的。
在navicate中查詢:show variables like '%max_connections%';
可以看到默認的數值為:max_connections:151
為了便於測試我們修改my.ini文件中的max_connections參數,修改為10。一定要重啟mysql服務才能生效
然後我們運行web項目,當打開頁面的時候執行。注意這裡注釋了關閉連接語句:
這時我們在chrome中訪問頁面,當打開第8個頁面時出現:Too many connections
的異常。
為什麼時第8個呢:因為之前說了打開數據庫以及查詢占了3個,所以8+3>10。自然就報mysql異常了。
我們看看服務器的監控:
都連接了2000多秒,還在一直連著,導致其他請求無法建立連接。這就是不釋放連接的最直接的後果。
下面我們來手動結束那些沒有被關閉的連接(右擊連接->結束進程或者停止vs運行)。
接下來測試正常情況,取消連接關閉的注釋
我們發現訪問網頁後,服務器中多了一個連接,進程id為1479。但是明明執行了connection.Close()
,但結果好像不是我們預料的那樣。
等待幾十秒後發現隨著訪問的結束,這個連接的閒置時間為109秒沒有被關閉掉。
但當我們重新打開一個網頁後,發現
這個連接的閒置時間被重置了為0了。
這種現象跟mysql並沒有太多原因,這是由mysql 的.net驅動控制的。當我們調用connection.Close()
時,與mysql建立的socket連接被沒有真正的關閉掉,而是被保留並置為空閒狀態。
當新的請求連接過來後,會直接使用這個連接而不會建立新的socket連接。這是mysql驅動自帶的連接池功能,來優化性能與資源。
我們來翻下mysql.data的源代碼:
大部分情況下我們調用connection.Close()
只是調用:this.SetState(ConnectionState.Close,true)
,改變一下連接的狀態而已。並非真的關閉
真正的關閉時調用CloseFully()
mysql驅動申請的連接真正關閉是通過超時實現的。經過測試如果閒置300秒左右,這個連接就會被徹底關閉了
所以交互流程圖是這樣的:
正常情況下自然是好,但是如果:
這時候出現的狀況就比未手動關閉數據庫連接還要嚴重,程序已經失去控制權了。會出現一批處於空閒中的數據庫連接無法被正常釋放掉。如果網絡時好時壞,就有可能是一批接一批的。
所以mysql服務內部也有一個超時機制,當一個socket連接長期空閒的時候mysql服務端會強制關閉這個連接。如果等mysql的超時機制來處理的問題,
超時時間通過:show global variables like '%timeout%';
如上圖所示的wait_timeout
就是控制等待空閒超時的參數,默認為28800秒也就是8小時
通常在配置連接字符串的使用連接池,如我們配置
var connection = new MySqlConnection(@"Data Source=121.40.64.127;port=3306; User ID=root;Password=root; database=spring;Persist Security Info=false; Connect Timeout=30;Min Pool Size=2;Max Pool Size=5;");
運行項目後,會一次多出兩個連接,如下圖:
而且這兩個連接只要iis沒有回收,不會被銷毀。
任何請求過來後。只要這兩個連接有空閒,就不會創建新的連接。
如果Min Pool Size
設置的數量不夠用,也就是並發量大與2的時候,那麼mysql驅動就會申請更多的連接。
我們來實際測試一下,使用fiddler我們來模擬下“瞬間的大量請求”:
打開fiddler->浏覽器中請求網頁->在filddler找到那個請求->按住shift->點擊菜單中的Replay->輸入50->點擊ok
這樣就會異步同時發出50個請求。
我們看下服務器的監控信息:
這時就會同時創建5個連接,因為2個不夠 5個最大,所以只能創建5各連接供使用,後面的請求只能等其他請求釋放連接以後才能進入處理。
如果你在連接關閉前設置:Thread.Sleep(5000)
,讓連接延遲5秒再關閉。就能明顯的感覺到大量數據庫請求阻塞,因為沒有額外的連接來處理請求了。
那是不是實際場景中,之前講的max_connections
與Max Pool Size
都設置最大好了?
當然不是,服務器的性能有限啊。你設置最大,高並發的時候連接限制倒是沒有,但整個服務器估計就掛了。
繼續剛才的Max Pool Size,申請的五個連接,如果閒置300秒左右。有3個會被回收掉,只會保留Min Pool Size
生成的2個。請看截圖
也就是連接符中設置的Min Pool Size
會永久的生成兩個連接在那裡,不會被關閉。Max Pool Size
只在Min Pool Size
不夠的時候生成,用完(閒置超時後)會被關閉掉。
程序中無論多少並發請求,mysql驅動提供給當前應用的連接數都不會超過Max Pool Size
。這樣看起來Max Pool Size
也有保護服務器不被拖垮的作用
未完待續……