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

一個mysql jdbc待解之謎 關於jdbc  url參數 allowMultiQuerieshtml

以下的一個普通JDBC示例:java

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);

執行時會報錯:mysql

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的話,如:sql

select 'hello';select 'world'

默認就會報上述錯誤,當若顯式設置allowMultiQueries爲true的話,就能夠正常執行不會報錯.以下所示:shell

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

官方文檔解釋:ubuntu

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,會看到這樣的輸出:socket

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

當設置爲false時,執行完socketWrite0後.查詢query log沒有任何輸出.工具

但奇怪的是它們的參數都同樣,爲何一個有輸出一個就沒有呢?以下所示:ui

設置爲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	root@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服務,但不起做用.

相關文章
相關標籤/搜索