HikariPool и Firebird: снижение производительности при многопоточности
Дано: служба на джаве, посылающая в базу запросы (insert и update, иногда ещё select для проверки вставленного). Для соединения используем Hikari. Его инициализация:
HikariConfig config = new HikariConfig();
config.setJdbcUrl(connectionString + String.format(";poolId=%s", poolId));
config.setMinimumIdle(minConn);
config.setMaximumPoolSize(maxConn);
config.setConnectionTimeout(30 * 1000);
config.setAutoCommit(false);
config.setMaxLifetime(0);
config.setIdleTimeout(600 * 1000);
config.setTransactionIsolation("TRANSACTION_READ_COMMITTED");
if (connectionString.startsWith("jdbc:firebirdsql")) {
Properties props = new Properties();
props.setProperty("TRANSACTION_READ_COMMITTED", "isc_tpb_read_committed,isc_tpb_rec_version,isc_tpb_wait");
config.setDataSourceProperties(props);
}
connectionPool = new HikariPool(config);
Вот как пересылаем запрос в базу:
connection = getConnectionPool().getConnection();
preparedStatement = connection.prepareStatement(strQuery);
preparedStatement.setFetchSize(fetchSize);
if (strQuery.startsWith("exec")) {
preparedStatement.executeUpdate();
} else {
resultSet = preparedStatement.executeQuery();
}
if (resultSet != null && resultSet.next()) {
//...
}
//закрытие всего. всё обёрнуто try-catch так, что дальше некуда
if (resultSet != null) {
try {
if (!resultSet.isClosed()) {
try {
resultSet.clearWarnings();
} catch (SQLException ex) {
logger.warn();
}
resultSet.close();
}
} catch (SQLException ex) {
logger.error();
}
resultSet = null;
}
if (preparedStatement != null) {
try {
if (!preparedStatement.isClosed()) {
try {
preparedStatement.clearWarnings();
} catch (SQLException ex) {
logger.warn();
}
preparedStatement.clearBatch();
preparedStatement.close();
}
} catch (SQLException ex) {
logger.error();
}
preparedStatement = null;
}
try {
if (connection != null && !connection.isClosed()) {
try {
connection.clearWarnings();
} catch (SQLException ex) {
logger.warn();
}
connection.commit();
connection.close();
}
} catch (SQLException ex) {
logger.error();
}
Вроде бы всё работает. Сложности начинаются при многопоточности. Если у нас есть только один поток, то он всё пишет влёт даже при многих тысячах запросов. Если потоков два, то отправка в базу каждого отдельного запроса замедляется. При четырёх потоках запросы на вход службы поступают быстрее, чем служба вставляет их в базу (поэтому днём копится очередь в десятки тысяч запросов, а ночью при низкой нагрузке рассасывается).
Может быть, база или драйвер jdbc не успевают прожевать? Мы попробовали ввести семафор, чтобы из четырёх потоков одновременно в базу писал только один - по идее, это должно быть равносильно одному потоку. Но нет, запросы отрабатывают так же медленно, как при четырёх потоках без семафора, и очередь копится.
Вопрос: на чём могут быть потери? Понятно, что база тут первый кандидат, но наш базист говорит, что на файрбёрде отладиться всё равно нельзя. Может, есть какой-то способ определить, кто виноват - Firebird, Java или что-то на их стыке (например, Hikari или jdbc).
Ответы (1 шт):
но наш базист говорит, что на файрбёрде отладиться всё равно нельзя.
Гоните его в шею
Пишете два триггера
CREATE OR ALTER TRIGGER TR_LOG_BEF FOR MY_TABLE
ACTIVE BEFORE INSERT OR UPDATE OR DELETE POSITION 10000
AS
DECLARE VASRIABLE var_action VARCHAR(16);
BEGIN
var_action = CASE
WHEN INSERTING THEN 'INSERTING'
WHEN UPDATING THEN 'UPDATING'
WHEN DELETING THEN 'DELETING'
END;
IN AUTONOMOUS TRANSACTION DO
INSERT INTO logs (
"ID",
"CON",
"TRN",
"TIMESTAMP",
"MESSAGE"
) VALUES (\
GEN_ID(gen_log, 1),
CURRENT_CONNECTION,
CURRENT_TRANSACTION,
'NOW',
'Before ' || :var_action
);
END
CREATE OR ALTER TRIGGER TR_LOG_AFT FOR MY_TABLE
ACTIVE AFTER INSERT OR UPDATE OR DELETE POSITION 0
AS
DECLARE VASRIABLE var_action VARCHAR(16);
BEGIN
var_action = CASE
WHEN INSERTING THEN 'INSERTING'
WHEN UPDATING THEN 'UPDATING'
WHEN DELETING THEN 'DELETING'
END;
IN AUTONOMOUS TRANSACTION DO
INSERT INTO logs (
"ID",
"CON",
"TRN",
"TIMESTAMP",
"MESSAGE"
) VALUES (\
GEN_ID(gen_log, 1),
CURRENT_CONNECTION,
CURRENT_TRANSACTION,
'NOW', -- !! нельзя CURRENT_TIMESTAMP !!
'After ' || :var_action
);
END
Потом анализируете времена. Если следующий Before появляется строго после предыдущего After, значит кто-то выстраивает запросы последовательно. Если Before приходят одновременно, а After с увеличивающимся временем вставки, значит какие-то блокировки в самой таблице
Ну и каждый поток должен иметь свой коннект к базе