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

Posted

技术标签:

【中文标题】自动杀死长时间运行的查询(MySql),Apache Tomcat DataSource【英文标题】:Automatically kill long running queries (MySql), Apache Tomcat DataSource 【发布时间】:2017-09-11 11:31:53 【问题描述】:

我们的 Java Web 应用程序具有搜索功能,允许用户在大型数据库中搜索记录。

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

这是一个 Web 应用程序,因此他们一次又一次地尝试,并且查询占用了所有资源,从而导致严重的性能问题。

如果查询运行时间过长或占用过多 CPU,有什么方法可以自动终止查询?

【问题讨论】:

见***.com/questions/18253934/… 【参考方案1】:

对您来说最好的选择是捕获无法执行的搜索条件。

在从 5.7.8 开始的 mysql 中,有一个 max_execution_time setting。

您还可以提出一些 cron 脚本来检查 SHOW PROCESSLIST 并处理处理时间超过您的时间限制的查询。

【讨论】:

【参考方案2】:

此答案适用于 Apache tomcat-jdbc 数据源提供程序。

首先你需要了解 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();

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

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

org.apache.tomcat.jdbc.pool.PooledConnection.java 中添加方法 getConnectionId()

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;
    


【讨论】:

以上是关于自动杀死长时间运行的查询(MySql),Apache Tomcat DataSource的主要内容,如果未能解决你的问题,请参考以下文章

如果需要太长时间,如何停止 MySQL 查询?

MySQL管理长时间运行查询

如何使用在后端执行的长时间运行的 C/C++ 代码杀死 python 解释器?

MySQL 管理长时间运行查询

sql 在MySQL中查找长时间运行的查询

MySQL 5.5 中的长时间运行查询在 5.6 中非常快