環境 : jdk1.8
架構 : Spring Boot 1.5.7
+ Mybatis 3.4.3
+ Druid
+ PostgreSQL JDBC Driver
我在使用 PostgreSQL 執行一些查詢時報了一個錯誤,花了半天時間用來搞懂這個錯誤產生的原因後,決定在這裏總結一下.
ERROR: canceling statement due to user request
異常拋出原因.
報這個錯誤的主要原因是和字面上的意義一致,“由於用戶的請求取消了當前查詢的狀態”.
Caused by: org.postgresql.util.PSQLException: ERROR: canceling statement due to user request
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2477)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2190)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:300)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:428)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:354)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:169)
at org.postgresql.jdbc.PgPreparedStatement.execute(PgPreparedStatement.java:158)
可能解釋的有點草率,但這裏也沒有辦法說明的更加詳細了.大致意思就是由於你代碼中設置了取消執行當前查詢的代碼從而導致拋出這個異常.
異常的原因有可能是執行了該函數java.sql.Statement
:
// java.sql.Statement:248-259
/**
* Cancels this <code>Statement</code> object if both the DBMS and
* driver support aborting an SQL statement.
* This method can be used by one thread to cancel a statement that
* is being executed by another thread.
*
* @exception SQLException if a database access error occurs or
* this method is called on a closed <code>Statement</code>
* @exception SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
*/
void cancel() throws SQLException;
在 PostgreSQL JDBC Driver
驅動中對該方法的實現代碼是:
//org.postgresql.jdbc.PgStatement:595-606
public void close() throws SQLException {
// closing an already closed Statement is a no-op.
if (isClosed) {
return;
}
cleanupTimer();
closeForNextExecution();
isClosed = true;
}
我目前遇到的兩種問題可能會導致爆出這個錯誤.
- 當用戶發起 Http 請求,當該請求觸發了 Sql 查詢後,當還沒有返回數據的時候,用戶取消了該請求會導致拋出該異常;
- 當在 Mybatis 的配置文件
mybatis-config.xml
中設置了defaultStatementTimeout
屬性(單位:秒)後當sql的查詢時間超過了這個設置時間後會拋出該異常;
第一種情況不太確定.但第二種情況下驅動代碼PostgreSQL JDBC Driver
首先會在查詢前基於defaultStatementTimeout
設置的時間創建一個定時器.
//org.postgresql.jdbc:872-888
private void startTimer() {
/*
* there shouldn't be any previous timer active, but better safe than sorry.
*/
cleanupTimer();
STATE_UPDATER.set(this, StatementCancelState.IN_QUERY);
if (timeout == 0) {
return;
}
TimerTask cancelTask = new TimerTask() {
public void run() {
try {
if (!CANCEL_TIMER_UPDATER.compareAndSet(PgStatement.this, this, null)) {
// Nothing to do here, statement has already finished and cleared
// cancelTimerTask reference
return;
}
PgStatement.this.cancel(); //**定時器中取消本次查詢的代碼**
} catch (SQLException e) {
}
}
};
CANCEL_TIMER_UPDATER.set(this, cancelTask);
connection.addTimerTask(cancelTask, timeout);//創建定時器,並且設置超時時間
}
在執行查詢時間超過定時器設置的時間後,定時器會啓動並且去關閉本次sql執行.在執行完成後,等待查詢的循環判斷得到取消查詢拋出了異常時,會將該異常包裝並且拋出.