Запрос SQL через соединение JDBC медленнее в зависимости от того, где осуществляется доступ к объекту DataSource - PullRequest
0 голосов
/ 08 октября 2018

Я работаю над проектом, который использует шаблон Service Locator, а также имеет статический класс, который содержит объект DataSource, который мы используем для выполнения всех транзакций нашей базы данных.Общая настройка выглядит следующим образом:

public class Environment {
    //multiple app instances on one server
    private static final HashMap<String, DataSource> appDatasources = new HashMap<>();

    public static DataSource getDataSource(String appName){
        return appDatasources.get(appName);
    }
    public static DataSource getDataSource() {
        return appDatasources.get(getApplicationName());
    }
    public static String getApplicationName(){
        return ServiceLocator.getAppName();
    }

    public static void createDatasource(String jdbc, String appName){
        org.apache.tomcat.dbcp.dbcp.BasicDataSource ds = new org.apache.tomcat.dbcp.dbcp.BasicDataSource();
        ds.setDriveClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        ds.setUrl(jdbc);
        ds.setMaxActive(100);
        ds.setMaxIdle(50);
        ds.setInitialSize(10);
        ds.setRemoveAbandoned(true);
        ds.setRemoveAbandonedTimeout(10);
        ds.setLogAbandoned(true);

        appDatasources.put(appName, ds);
    }
}

}
public class ServiceLocatorFactory(String appName) {

    public static void registerServices(){
        DataSource ds = Environment.getDataSource(appName);
        ServiceLocator.replaceService("Service", new Service(ds);
        ...
        ...
    }
}

Мы передаем DataSource в наши службы, и они передаются нашим объектам доступа к данным и сохраняются как переменные-члены, которые должны быть ссылкой на тот жеОбъект DataSource, созданный нами в Environment.

public class Service {
    private ServiceDAO dao;

    public Service(DataSource ds){
        dao = new ServiceDAO(ds);
    }
}

public class ServiceDAO extends AbstractDAOService{
    public ServiceDAO(DataSource ds){
        super(ds);
    }
}

public abstract class AbstractDAOService {
    private final DataSource datasource;

    public AbstractDAOService(DataSource ds){
        this.datasource = ds;
    }

    protected DataSource getDataSource(){
        return this.datasource;
    }

    protected int queryGetCount(ParameterQuery qry, String countColName){
        ConnectionQuery q = new ConnectionQuery(getDataSource(), qry);
        int retval = 0;
        try {
            ResultSet rs = q.getResultSet();
            try {
                if(rs.next()){
                    retval = rs.getInt(countColName);
                }
            } finally {
                rs.close();
            }
        } catch(Exception ex) {
            //handle exception
        }
        q.close();
        return retval;
    }
}

. Объект запроса соединения получает соединение из источника данных, а затем создает подготовленный оператор и выполняет его, чтобы получить набор результатов.Проблема, с которой мы сталкиваемся, заключается в том, что существует запрос, выполнение которого по этому конвейеру занимало огромное количество времени.Мы видели время выполнения в диапазоне от 17 секунд до 90 (!) Секунд для довольно простого запроса.При выполнении через SQL Server Management Studio запрос выполняется в миллисекундах.Мы добавили протоколирование, чтобы точно определить, где в коде что-то ломалось, и медлительность была на PreparedStatament.execute().

Мы заметили, что если мы заменим метод getDataSource() в AbstractDAOSerice на следующий

protected DataSource getDataSource(){
    return Environment.getDataSource();
}

тогда запрос выполняется с той же скоростью, что и при выполнении через SSMS.Насколько мы понимаем, оба эти решения должны ссылаться на один и тот же объект, поэтому, хотя мы исправили проблему, мы хотим лучше понять , почему это была проблема с самого начала и как наши изменения исправили проблему.Любое руководство от экспертов Java приветствуется.

Для справки мы находимся на Java jdk1.8.0_144

Редактировать:

public class ConnectionQuery {
    private Connection con;
    private PreparedStatement stmt;
    private ResultSet rs;
    private final DataSource datasource;
    private ParameterQuery qry;

    public ConnectionQuery(DatasSource ds, ParamterQuery qry) {
        this.datasource = ds;
        this.qry = qry;
    }

    public ResultSet getResultSet() throws SQLException {
        try {
            Connection c = defineConnection(true);
            this.stmt = this.getQuery.makeStatement(c, false);
            this.stmt.execute();
            this.rs = this.stmt.getResultSet(); 
        } catch(SQLException se) {
            this.close();
            throw se;
        }
        return this.rs;
    }

    private Connection defineConnection(boolean readOnly) throws SQLException {
        if(this.con == null || this.con.isClosed()) {
            this.con = this.datasource.getConnection();
        }
        this.con.setReadOnly(readOnly);
        if(this.transactionIsolation != 777){
            this.con.setTransactionIsolation(this.transactionIsolation);
        }
        return this.con;
    }

    /**
     * close all parts of the connection, the RecordSet, the Statement, and the Connection
     */
    public void close() {
        if(this.rs != null) {
            try{
                this.rs.close();
                this.rs = null;
            } catch (SQLException e) {
                //warn
            }
        }
        if(this.stmt != null) {
            try{
                this.stmt.close();
                this.stmt = null;
            } catch (SQLException e) {
                //warn
            }
        }
        if(this.con != null) {
            try{
                this.con.close();
                this.con = null;
            } catch (SQLException e) {
                //warn
            }
        }
    }
}

1 Ответ

0 голосов
/ 08 октября 2018

В итоге мы нашли решение этой проблемы, обновив его на случай, если кто-то столкнется с тем же.Изменение кода Java было красной сельдью и не имело ничего общего с исправлением производительности запроса.

То, с чем мы столкнулись, было Измерение параметров .

Краткое описание этой проблемы: когда SQL кэширует план выполнения для запроса с набором параметров, с которым он работает, а затем повторно использует этот план выполнения с другим набором параметров, которые вызывают основныепроблемы с производительностью.Брент Озар (Brent Ozar) углубляется в эту проблему в приведенной выше ссылке.

Причина, по которой мы столкнулись с этим и из-за изменения кода на самом деле создалось впечатление, что он работает, заключается в том, что выполняемый нами запрос былиспользуется для определения того, используются ли определенные объекты в нашей системе конечными пользователями или нет.Если первый проверенный нами объект не используется, тогда SQL будет кэшировать план выполнения для объекта без результатов, а при его использовании и для объекта с результатом более 14 000 (в таблице с 108 000 строк) запрос выполняется значительно дольше.Мы принудительно запустили наш запрос с большим результирующим набором вместе с планом выполнения другого запроса и воспроизвели его в SQL Server Management Studio, чтобы убедиться в том, что в наших результатах не было никаких сомнений.

Решение, которое мы продвигаемся впередВ нашем проекте мы рассмотрим и оптимизируем структуру запроса / таблицы, чтобы план выполнения был постоянным для разных входных параметров этого запроса, а время выполнения также оставалось неизменным.

Надеюсь, эта информация полезна.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...