相信有過工作經驗的同學都知道資料庫連接是一個比較耗資源的操作。那麼資源到底是耗費在哪裡呢? 本文主要想探究一下連接資料庫的細節,尤其是在Web應用中要使用資料庫來連接池,以免每次發送一次請求就重新建立一次連接。對於這個問題,答案都是一致的,建立資料庫連接很耗時,但是這個耗時是都多少呢,又是分別在哪些 ...
相信有過工作經驗的同學都知道資料庫連接是一個比較耗資源的操作。那麼資源到底是耗費在哪裡呢?
本文主要想探究一下連接資料庫的細節,尤其是在Web
應用中要使用資料庫來連接池,以免每次發送一次請求就重新建立一次連接。對於這個問題,答案都是一致的,建立資料庫連接很耗時,但是這個耗時是都多少呢,又是分別在哪些方面產生的耗時呢?
本文以連接MySQL
資料庫為例,因為MySQL
資料庫是開源的,其通信協議是公開的,所以我們能夠詳細分析建立連接的整個過程。
在本文中,消耗資源的分析主要集中在網路上,當然,資源也包括記憶體、
CPU
等計算資源,使用的編程語言是Java
,但是不排除編程語言也會有一定的影響。
首先先看一下連接資料庫的Java
代碼,如下:
Class.forName("com.mysql.jdbc.Driver");
String name = "xttblog2";
String password = "123456";
String url = "jdbc:mysql://xxx:3306/xttblog2";
Connection conn = DriverManager.getConnection(url, name, password);
// 之後程式終止,連接被強制關閉
然後通過Wireshark
,分析整個連接的建立過程,如下:
本文已經收錄到Github倉庫,該倉庫包含電腦基礎、Java基礎、多線程、JVM、資料庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散式、微服務、設計模式、架構、校招社招分享等核心知識點,歡迎star~
如果訪問不了Github,可以訪問gitee地址。
Wireshark抓包
在上圖中顯示的連接過程中,可以看出MySQL
的通信協議是基於TCP
傳輸協議的,而且該協議是二進位協議,不是類似於HTTP
的文本協議,其中建立連接的過程具體如下:
- 第1步:建立
TCP
連接,通過三次握手實現; - 第2步:伺服器發送給客戶端「握手信息」 ,客戶端響應該握手消息;
- 第3步:客戶端「發送認證包」 ,用於用戶驗證,驗證成功後,伺服器返回
OK
響應,之後開始執行命令;
用戶驗證成功之後,會進行一些連接變數的設置,比如字元集、是否自動提交事務等,其間會有多次數據的交互。完成了這些步驟後,才會執行真正的數據查詢和更新等操作。
在本文的測試中,只用了5
行代碼來建立連接,但是並沒有通過該連接去執行任何操作,所以在程式執行完畢之後,連接不是通過Connection.close()
關閉的,而是由於程式執行完畢,導致進程終止,造成與資料庫的連接異常關閉,所以最後會出現TCP
的RST
報文。
在這個最簡單的代碼中,沒有設置任何額外的連接屬性,所以在設置屬性上占用的時間可以認為是最少的(其實,雖然我們沒有設置任何屬性,但是驅動仍然設置了字元集、事務自動提交等,這取決於具體的驅動實現),所以整個連接所使用的時間可以認為是最少的。
但從統計信息中可以看出,在不包括最後TCP
的RST
報文時(因為該報文不需要伺服器返回任何響應),但是其中仍需在客戶端和伺服器之間進行往返「7
」 次,「也就是說完成一次連接,可以認為,數據在客戶端和伺服器之間需要至少往返7
次」 ,從時間上來看,從開始TCP
的三次握手,到最終連接強制斷開為止(不包括最後的RST
報文),總共花費了:
10.416042 - 10.190799 = 0.225243s = 225.243ms!!!
這意味著,建立一次資料庫連接需要225ms
,而這還是還可以認為是最少的,當然「花費的時間可能受到網路狀況、資料庫伺服器性能以及應用代碼是否高效的影響」 ,但是這裡只是一個最簡單的例子,已經足夠說明問題了!
由於上面是程式異常終止了,但是在正常的應用程式中,連接的關閉一般都是通過Connection.close()
完成的,代碼如下:
Class.forName("com.mysql.jdbc.Driver");
String name = "shine_user";
String password = "123";
String url = "jdbc:mysql://xxx:3306/clever_mg_test";
Connection conn = DriverManager.getConnection(url, name, password);
conn.close();
這樣的話,情況發生了變化,主要體現在與資料庫連接的斷開,如下圖:
網路抓包
- 第1步:此時處於
MySQL
通信協議階段,客戶端發送關閉連接請求,而且不用等待服務端的響應; - 第2步:
TCP
斷開連接,4
次揮手完成連接斷開;
這裡是完整地完成了從資料庫連接的建立到關閉,整個過程花費了:
747.284311 - 747.100954 = 0.183357s = 183.357ms
這裡可能也有網路狀況的影響,比上述的225ms
少了,但是也幾乎達到了200ms
的級別。最全面的Java面試網站
那麼問題來了,想象一下這個場景,對於一個日活2萬
的網站來說,假設每個用戶只會發送5
個請求,那麼一天就是10萬
個請求,對於建立資料庫連接,我們保守一點計算為150ms
好了,那麼一天當中花費在建立資料庫連接的時間有(還不包括執行查詢和更新操作):
100000 * 150ms = 15000000ms = 15000s = 250min = 4.17h
也就說每天花費在建立資料庫連接上的時間已經達到「4個小時
」 ,所以說資料庫連接池是必須的,而且當日活增加時,單單使用資料庫連接池也不能完全保證你的服務能夠正常運行,還需要考慮其他的解決方案:
- 緩存
- SQL 的預編譯
- 負載均衡
- ……
總之,資料庫連接真的很耗時,所以不要頻繁的建立連接。
最後給大家分享一個Github倉庫,上面有大彬整理的300多本經典的電腦書籍PDF,包括C語言、C++、Java、Python、前端、資料庫、操作系統、電腦網路、數據結構和演算法、機器學習、編程人生等,可以star一下,下次找書直接在上面搜索,倉庫持續更新中~