我们的Java Web应用程序具有searchfunction,允许用户通过大型数据库searchlogging。
如果用户指定了错误的search参数,他们最终会看到一个查询,因为它需要几个小时才能运行。
这是一个Web应用程序,所以他们一次又一次地尝试,并查询所有资源,造成严重的性能问题。
有没有办法自动杀死一个查询,如果它运行太长或使用太多的CPU?
最好的选择是捕捉无法执行的搜索条件。
在从5.7.8开始的MySQL中,有一个max_execution_time设置 。
你也可以想出一些cron脚本来检查SHOW PROCESSLIST
并处理正在被处理的查询,而不是你的时间限制。
这个答案适用于Apache Tomcat-jdbc DataSource提供程序。
首先你需要了解PoolProperties
setRemoveAbandonedTimeout
setRemoveAbandoned
当查询花费的时间超过setRemoveAbandonedTimeout(int)中指定的时间时,执行此查询的连接将被标记为Abandon并调用java.sql.Connection.close()方法,这将在释放连接之前继续等待查询完成。
我们可以实现自己的处理程序来处理被遗弃的连接 以下是变化
首先我们需要添加一个接口
package org.apache.tomcat.jdbc.pool; public interface AbandonedConnectionHandler { public void handleQuery(Long connectionId); }
tomcat-jdbc文件的变化:
PoolConfiguration.java (接口)
添加getter和setter方法。
public void setAbandonedConnectionHandler(AbandonedConnectionHandler abandonedConnectionHandler); public AbandonedConnectionHandler getAbandonedConnectionHandler();
将这些方法覆盖到所有的实现类
将一个方法getConnectionId()添加到org.apache.tomcat.jdbc.pool.PooledConnection.java
public Long getConnectionId() { try { //jdbc impl has getId() Method method = this.connection.getClass().getSuperclass().getMethod("getId"); return (Long)method.invoke(this.connection); } catch (Exception e) { log.warn(" Abandoned QueryHandler failed to initialize connection id "); } return null; }
上面的反射代码可能会有所不同,如果不同的MySQL驱动程序。
现在我们需要在调用org.apache.tomcat.jdbc.pool.ConnectionPool.java中的java.sql.Connection.close()方法之前,
将启动废弃的连接清理程序的ConnectionPool.java方法是
protected void abandon(PooledConnection con)
在调用release(con)之前,在这个方法里面添加下面的代码;
if(getPoolProperties().getAbandonedConnectionHandler() != null) { con.lock(); getPoolProperties().getAbandonedConnectionHandler().handleQuery(con.getConnectionId()); }
现在,您只需在创建tomcat-jdbc数据源时将您的handerInstance和PoolProperties一起传递。
p.setAbandonedConnectionHandler(new ConnectionHandler(true));
这是我的AbandonedConnectionHandler实现。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.jdbc.pool.AbandonedConnectionHandler; import org.apache.tomcat.jdbc.pool.PoolConfiguration; public class ConnectionHandler implements AbandonedConnectionHandler{ private static final Log log = LogFactory.getLog(ConnectionHandler.class); private Boolean isAllowedToKill; private PoolConfiguration poolProperties; public ConnectionHandler(Boolean isAllowedToKill) { this.isAllowedToKill = isAllowedToKill; } @Override public void handleQuery(Long connectionId) { Connection conn = null; Statement stmt = null; if(this.isAllowedToKill) { try{ Class.forName(poolProperties.getDriverClassName()); conn = DriverManager.getConnection(poolProperties.getUrl(),poolProperties.getUsername(),poolProperties.getPassword()); Statement statement = conn.createStatement(); ResultSet result = statement.executeQuery("SELECT ID, INFO, USER, TIME FROM information_schema.PROCESSLIST WHERE ID=" + connectionId); if(result.next()) { if(isFetchQuery(result.getString(2))) { statement.execute("Kill "+connectionId); } } statement.close(); conn.close(); } catch(Exception e) { e.printStackTrace(); } finally { try { if(stmt != null && !stmt.isClosed()) stmt.close(); } catch (SQLException e) { log.warn("Exception while closing Statement "); } try { if(conn != null && !conn.isClosed() ) conn.close(); } catch (SQLException e) { log.warn("Exception while closing Connection "); } } } } private Boolean isFetchQuery(String query) { if(query == null) { return true; } query = query.trim(); return "SELECT".equalsIgnoreCase(query.substring(0, query.indexOf(' '))); } public PoolConfiguration getPoolProperties() { return poolProperties; } public void setPoolProperties(PoolConfiguration poolProperties) { this.poolProperties = poolProperties; } }