1. 程式人生 > >Mysql JDBC-mysql-Driver queryTimeout分析

Mysql JDBC-mysql-Driver queryTimeout分析

erro ecan nag get ava timer oca element mysql

Mysql jdbc的queryTimeout分析

Mysql的jdbc-driver

com.mysql.jdbc.Driver

設置queryTimeout方法

com.mysql.jdbc.StatementImpl.setQueryTimeout
StatementImpl實例有一個field:timeoutInMillis

public void setQueryTimeout(int seconds) throws SQLException {
    synchronized(this.checkClosed().getConnectionMutex()) {
        if(seconds < 0) {
            throw SQLError.createSQLException(Messages.getString("Statement.21"), "S1009", this.getExceptionInterceptor());
        } else {
            this.timeoutInMillis = seconds * 1000;
        }
    }
}

queryTimeout使用場景示例:

com.mysql.jdbc.StatementImpl.executeQuery

ResultSet executeQuery(String sql) throws SQLException;

executeQuery有一個較復雜的邏輯:

  • 獲取connection的互斥鎖
  • 校驗、初始化一些配置,是否為ping請求
  • sql轉義,防sql註入
  • 判斷timeout是否有效,有效時創建一個CancelTask
  • 將cancelTask放入Timer中延遲執行
    locallyScopedConn.getCancelTimer().schedule(timeoutTask, (long)this.timeoutInMillis);
    也就是在當前時間的timeoutInMillis後會執行這個Task
  • 執行sql語句,獲取結果
  • 超時任務判斷,如果有超時任務,分為兩種情況:1 超時異常已經拋出,直接返回異常;1 超時任務未執行,cancel超時任務

     this.results = locallyScopedConn.execSQL(this, sql, this.maxRows, (Buffer)null, this.resultSetType, this.resultSetConcurrency, this.createStreamingResultSet(), this.currentCatalog, cachedFields);
    
     if(timeoutTask != null) {
         if(timeoutTask.caughtWhileCancelling != null) {
             throw timeoutTask.caughtWhileCancelling;
         }
    
         timeoutTask.cancel();
         locallyScopedConn.getCancelTimer().purge();
         timeoutTask = null;
     }
  • 獲取lastInsertId
  • 返回results

StatementImpl.CancelTask

class CancelTask extends TimerTask {
    SQLException caughtWhileCancelling = null;
    StatementImpl toCancel;
    Properties origConnProps = null;
    String origConnURL = "";
    long origConnId = 0L;

    CancelTask(StatementImpl cancellee) throws SQLException {
        this.toCancel = cancellee;
        this.origConnProps = new Properties();
        Properties props = StatementImpl.this.connection.getProperties();
        Enumeration keys = props.propertyNames();

        while(keys.hasMoreElements()) {
            String key = keys.nextElement().toString();
            this.origConnProps.setProperty(key, props.getProperty(key));
        }

        this.origConnURL = StatementImpl.this.connection.getURL();
        this.origConnId = StatementImpl.this.connection.getId();
    }

    public void run() {
        Thread cancelThread = new Thread() {
            public void run() {
                Connection cancelConn = null;
                java.sql.Statement cancelStmt = null;

                try {
                    MySQLConnection npe = (MySQLConnection)StatementImpl.this.physicalConnection.get();
                    if(npe != null) {
                        if(npe.getQueryTimeoutKillsConnection()) {
                            CancelTask.this.toCancel.wasCancelled = true;
                            CancelTask.this.toCancel.wasCancelledByTimeout = true;
                            npe.realClose(false, false, true, new MySQLStatementCancelledException(Messages.getString("Statement.ConnectionKilledDueToTimeout")));
                        } else {
                            Object var4 = StatementImpl.this.cancelTimeoutMutex;
                            synchronized(StatementImpl.this.cancelTimeoutMutex) {
                                if(CancelTask.this.origConnURL.equals(npe.getURL())) {
                                    cancelConn = npe.duplicate();
                                    cancelStmt = cancelConn.createStatement();
                                    cancelStmt.execute("KILL QUERY " + npe.getId());
                                } else {
                                    try {
                                        cancelConn = (Connection)DriverManager.getConnection(CancelTask.this.origConnURL, CancelTask.this.origConnProps);
                                        cancelStmt = cancelConn.createStatement();
                                        cancelStmt.execute("KILL QUERY " + CancelTask.this.origConnId);
                                    } catch (NullPointerException var25) {
                                        ;
                                    }
                                }

                                CancelTask.this.toCancel.wasCancelled = true;
                                CancelTask.this.toCancel.wasCancelledByTimeout = true;
                            }
                        }
                    }
                } catch (SQLException var27) {
                    CancelTask.this.caughtWhileCancelling = var27;
                } catch (NullPointerException var28) {
                    ;
                } finally {
                    if(cancelStmt != null) {
                        try {
                            cancelStmt.close();
                        } catch (SQLException var24) {
                            throw new RuntimeException(var24.toString());
                        }
                    }

                    if(cancelConn != null) {
                        try {
                            cancelConn.close();
                        } catch (SQLException var23) {
                            throw new RuntimeException(var23.toString());
                        }
                    }

                    CancelTask.this.toCancel = null;
                    CancelTask.this.origConnProps = null;
                    CancelTask.this.origConnURL = null;
                }

            }
        };
        cancelThread.start();
    }
}

timeout後執行的操作主要為:

  • cancelConn = npe.duplicate(); //復制一個當前連接配置相同的連接
  • cancelStmt = cancelConn.createStatement(); //創建一個Statement對象,用來發送sql語句到數據庫
  • cancelStmt.execute("KILL QUERY " + npe.getId()); //殺掉已經timeout的語句

可以看到,只要CancelTask執行,除了執行sql的連接壓根沒有成功生成外,都會執行KILL QUERY操作,裏面不做任何請求是否已成功的判斷。
原因也比較明顯,凡是執行到CancelTask,說明確實超時了。

Mysql JDBC-mysql-Driver queryTimeout分析