自动终止长时间运行的查询(MySql),Apache Tomcat DataSource

时间:2017-09-11 11:31:53

标签: java mysql apache tomcat jdbc

我们的java网络应用程序具有搜索功能,允许用户通过大型数据库搜索记录。

如果用户指定了错误的搜索参数,他们最终会得到一个看起来永远不会结束的查询,因为它需要几个小时才能运行。

它是一个Web应用程序,因此他们反复尝试,查询会导致所有资源陷入严重的性能问题。

如果查询运行时间过长或使用太多CPU,有没有办法自动终止查询?

2 个答案:

答案 0 :(得分:1)

最适合您的方法是捕捉无法执行的搜索条件。

在MySQL从5.7.8开始,有一个max_execution_time setting

此外,您可以提供一些cron脚本来检查SHOW PROCESSLIST并处理超出时间限制的正在处理的查询。

答案 1 :(得分:0)

这个答案适用于Apache tomcat-jdbc DataSource提供程序。

首先,您需要了解PoolProperties

  1. setRemoveAbandonedTimeout

  2. setRemoveAbandoned

  3. 当查询花费的时间超过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();
    

    将这些方法覆盖到所有实现类

    • DataSourceProxy.java
    • PoolProperties.java
    • org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.java

    将方法 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 DataSource时将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;
        }
    
    }