[Java,Sybase]jconn3.jar升級到jconn4.jar PreparedStatement Connection.prepareStatement(String)方法性能劣化

0x00 一句話

大量通過prepareStatement方法創建PreparedStatement實例的時候,如果預編譯開關是開着的,那麼每次調用都會產生和DBMS的通信,大量通信導致性能劣化。prepareStatement不是給大量調用而設計的,是爲重複使用而設計的,儘量避免在預編譯開關開着的時候大量調用prepareStatement。

0x01 前言

公司別組有項目是從Java1.6升級到Java1.8, 其中使用了Sybase的JDBC Driver(jconn3.jar)。這個jar包也需要從jconn3.jar升級到jconn4.jar。而升級之後出現了明顯的性能下降。筆者參與了性能劣化原因的調查並查明瞭原因。

性能下降現象:
		jconn3.jar:  20s/萬次插入。
		jconn4.jar:  70s/萬次插入。

0x02 劣化代碼定位

看到報告數字的時候筆者不敢相信升級會導致性能劣化這麼嚴重,於是開始着手讀代碼。
大概定位到性能劣化的代碼長相簡化後如下。

    // Connection conn = ...;
    // List<String> sqlLists = ...;
    // sqlLists規模萬以上。
	for (String sql : sqlLists)
	{
		PreparedStatement stmt = conn.prepareStatement(sql);//性能劣化行
		// Set parameters for stmt.
		// ...
		stmt.executeUpdate();
	}

對循環內部的幾句代碼做分析之後,分別測試了循環中幾句關鍵代碼的時間消耗之後,

PreparedStatement Connection.prepareStatement(String)
jconn3.jar: 0s/萬次
jconn4.jar: 50s/萬次

發現這個方法耗時嚴重,且剛好和劣化的時間能符合(50s/萬次)。

0x03 PreparedStatement Connection.prepareStatement(String) 性能劣化原因分析

定位到了性能有問題的代碼之後,開始着手分析爲什麼升級jconn會導致該方法性能劣化如此嚴重。
因爲JDBC是一套標準接口,各個廠商會自己實現這套接口,jconn3.jar就是這套接口的一個實現(implement),而jconn4.jar是另一個實現。也就是說在實現這個接口方法的時候,Sybase提供的JDBC Driver內部實現變更了。
這就提供了兩個調查方向給筆者。

  1. PreparedStatement Connection.prepareStatement(String)的JDBC標準定義[1]
  2. Sybase升級的時候對這個方法的實現做了什麼改動。[2]

那麼看一下prepareStatement的JDBC標準定義(文檔)吧

prepareStatement JDBC doc
這裏的Note裏提到了,有的driver會使用預編譯(precompilation),所以會在被調用的時候去把接收到的SQL送到DB端(DBMS)去做預編譯,有的driver不會做這個事情。
這個描述非常符合筆者這次遇到的情況。可以幾乎猜想到大量PrepareStatement被送到DBMS做預編譯,大量通信所以導致性能劣化

0x04 Sybase 更新證明

上面筆者通過接口定義大致猜到了因爲預編譯的通信導致性能劣化,也就是說jconn3.jar的時候不會去做預編譯,而jconn4.jar的時候會做預編譯。報告的時候需要證據來證明筆者的結論,所以不得不去Sybase的官方文檔翻…

Sybase PrepareStatement Doc
jConnect 6.05之後,該方法能夠支持返回兩種PreparedStatement,一種是預編譯過,一種是未預編譯過的。通過對Connection設置DYNAMIC_PREPARE屬性指定。

  • DYNAMIC_PREPARE = True : 返回預編譯的PreparedStatement。
  • DYNAMIC_PREPARE = False : 返回未預編譯的PreparedStatement。

原來如此,筆者檢查了一下別組的創建Connection的代碼,發現並沒有使用這個屬性。
大概率是升級的時候這個屬性默認值(Default Value)改變了。查了一下參考文獻[3][4],果然…

jar jConnect DYNAMIC_PREPARE 默認值
jconn3.jar jConnect 6.05 False
jconn4.jar jConnect 7.0 False
jconn4.jar jConnect 7.07 True
jconn4.jar jConnect 7.07 SP100 True
jconn4.jar jConnect 7.07 SP110 True
jconn4.jar jConnect 16 True

另:如果想知道jconnX.jar內部的jConnect版本,執行這個jar即可。

	java -jar jconnX.jar

結果查明瞭,DYNAMIC_PREPARE 默認值變更導致了原代碼性能下降。

0x05 感想

  1. PrepareStatement預編譯可以在大量執行的時候提供非常好的性能優化。但是如果每次都去生成一個PrepareStatement並且該實例只用一次的話,不開預編譯也許是更好的選擇。
  2. 這個代碼是前人寫的,後被接手,更新性能劣化嚴重導致升級組的人被罵得很慘。結果是前人埋的巨坑,後面的人埋單。TMD, PrepareStatement不重複使用的小夥子你出來,看筆者不錘爆你的頭…

最後希望大家都能提高自己的寫碼水平,避免不必要的時間損失。下手寫碼前多看官方文檔和Best Practice,對於寫出高質量高可讀可維護的代碼會有幫助。

參考文獻:
[1] Connection, Oracle Corporation (2019/03/21) Retrieved https://docs.oracle.com/javase/jp/8/docs/api/java/sql/PreparedStatement.html
[2] Sybase documentation, SAP Corporation (2019/03/21) Retrieved http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.help.ase.16.0/doc/html/title.html
[3]jConnect 6.0.5 - DYNAMIC_PREPARE connection property, SAP Corporation (2019/03/21) Retrieved http://infocenter.sybase.com/help/topic/com.sybase.infocenter.dc39001.0605/html/prjdbc/X30815.htm
[4]jConnect 16.0 - DYNAMIC_PREPARE connection property, SAP Corporation (2019/03/21) Retrieved http://infocenter.sybase.com/help/topic/com.sybase.infocenter.dc39001.1600/doc/html/san1353997987255.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章