1. 程式人生 > >Jdbc Url 設定allowMultiQueries為true和false時底層處理機制研究

Jdbc Url 設定allowMultiQueries為true和false時底層處理機制研究

一個mysql jdbc待解之謎 關於jdbc  url引數 allowMultiQueries

如下的一個普通JDBC示例:

String user ="root";
String password = "root";
String url = "jdbc:mysql://localhost:3306"; Connection conn = java.sql.DriverManager.getConnection(url , user, password); Statement stmt = conn.createStatement(); String sql = "select 'hello';select 'world'"; stmt.execute(sql);

執行時會報錯:

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select 'world'' at line 1 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)

若一個sql中通過分號分割(或包含)了多個獨立sql的話,如:

select 'hello';select 'world'

預設就會報上述錯誤,當若顯式設定allowMultiQueries為true的話,就可以正常執行不會報錯.如下所示:

String url = "jdbc:mysql://localhost:3306?allowMultiQueries=true";

官方文件解釋:

allowMultiQueries
Allow the use of ';' to delimit multiple queries during one statement (true/false), defaults to 'false', and does not affect the addBatch() and executeBatch() methods, which instead rely on rewriteBatchStatements. Default: false Since version: 3.1.1

想要看看當該引數設定為true和false時,原始碼中到底哪裡有不同.經過斷點除錯發現了在下面程式碼處會有不同的結果:

   //所屬類和方法:void java.net.SocketOutputStream.socketWrite(byte[] b, int off, int len) throws IOException
   socketWrite0(fd, b, off, len);
   bytesWritten = len;

當設定為true時,執行完socketWrite0方法後查詢query log,會看到這樣的輸出:

   23 Query	select 'hello';
   23 Query	select 'world'

當設定為false時,執行完socketWrite0後.查詢query log沒有任何輸出.

但奇怪的是它們的引數都一樣,為什麼一個有輸出一個就沒有呢?如下所示:

設定為true引數資訊

設定為false時的引數資訊:

補充: 即使當allowMultiQueries為false時,服務端也收到了查詢請求,只不過沒有在query log中輸出而已.

如下抓包工具(Wireshark)所示:

又經過進一步除錯,發現如下幾處程式碼可能比較關鍵,如下所示:

/**
*
* 所屬: void com.mysql.jdbc.MysqlIO.proceedHandshakeWithPluggableAuthentication(String user, String 
* password, String database, Buffer challenge) throws SQLException
*/
// We allow the user to configure whether
// or not they want to support multiple queries
// (by default, this is disabled).
if (this.connection.getAllowMultiQueries()) { //1701行 this.clientParam |= CLIENT_MULTI_STATEMENTS; } last_sent = new Buffer(packLength); last_sent.writeLong(this.clientParam); //1876行

執行完上述語句後,對應的query log為:

150507 21:23:16	   19 Connect	[email protected]localhost on

似乎是在這一互動過程中通知了伺服器端客戶端允許一次查詢多條語句.通過抓包工具(wireshark)可以看到在建立連線過程中更多的互動資訊,如下所示:

但在除錯過程中又遇到了一個新問題,若在上述程式碼處加上了斷點,就會出現如下異常:

Caused by: java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost. at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3161) at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3615) ... 43 more

尚未找到該問題原因(如在何處設定的超時時間).但是實驗發現,在建立連線過程中不能有debug除錯(即不能有暫停),否則就會有fin包(不知這是Mysql的協議還是TCP的協議?).如下所示:

關於fin包的描述見wiki

但在成功建立連線後進行斷點除錯沒有問題.

補充: 通過telnet來模擬上述現象.

# telnet 127.0.0.1 3306
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'. J 5.6.17 <4/-7j1S- � 18fwG:-nh=66mysql_native_passwordConnection closed by foreign host.

對應的抓包資訊:

因未能及時輸入密碼,導致服務端發出FIN包中斷了連線.

或者也可以通過如下Java程式碼來模擬此現象:

Socket socket = new  Socket("127.0.0.1",3306);
System.in.read();

此時看到的抓包資訊如下所示:

補充:

1. 如何檢視query log:

mysql> show variables like 'general_log%';
+------------------+--------------------------------------------------------------------+
| Variable_name    | Value                                                              |
+------------------+--------------------------------------------------------------------+
| general_log      | OFF                                                                 |
| general_log_file | /opt/mysql/server-5.6/data/zhuguowei-Presario-CQ43-Notebook-PC.log |
+------------------+--------------------------------------------------------------------+
mysql> set global general_log = 'on' ; #開啟另一終端 tail -f /opt/mysql/server-5.6/data/zhuguowei-Presario-CQ43-Notebook-PC.log

2. 關於使用抓包工具(Wireshark)

參考文件:

http://www.maketecheasier.com/using-wireshark-ubuntu/

http://floss.zoomquiet.io/data/20120511105248/index.html

注意:

本地除錯的話Interface(網絡卡)選擇any(或lo), 過濾條件如下所示:

 

其他問題:

1. 為什麼在mysql互動終端中執行的命令不能被Wireshark捕捉到?

需要顯式指定-h引數 如

 mysql -u root -p -h 127.0.0.1

注意若為-h localhost的話,仍不能被Wireshark捕捉到,不知兩者有什麼區別?通過StackOverFlow中提問得到解答.

On Unix, MySQL programs treat the host name localhost specially, in a way that is likely different from what you expect compared to other network-based programs. For connections to localhost, MySQL programs attempt to connect to the local server by using a Unix socket file. This occurs even if a --port or -P option is given to specify a port number. To ensure that the client makes a TCP/IP connection to the local server, use --host or -h to specify a host name value of 127.0.0.1, or the IP address or name of the local server. You can also specify the connection protocol explicitly, even for localhost, by using the --protocol=TCP option.

摘自: https://dev.mysql.com/doc/refman/5.0/en/connecting.html

2. 在mysql互動終端中是否預設設定了allowMultiQueries=true, 能有辦法將其改為false嗎?

確實預設設定了allowMultiQueries=true, 如下所示:

上圖是通過Wireshark抓取終端mysql登入時(mysql -u root -p -h 127.0.0.1)抓取的包.

似乎沒有修改的辦法.

3. 如何在telent 127.0.0.1 3306後輸入密碼? 或者如何設定使得不用輸入密碼,試過使用--skip-grant-tables 啟動mysql服務,但不起作用.