Пулы операторов JDBC с DB2 не имеют значительной разницы во времени
Я использую драйвер JDBC db2, также известный как JT400, для подключения к серверу db2 в Application System / 400, компьютерной системе среднего уровня.
Моя цель состоит в том, чтобы insert into
три таблицы, расположенные за пределами мэйнфрейма IBM, которые будут экземпляром облака (например, Amazon WS).
Чтобы улучшить производительность
1) Я использую уже установленные соединения для подключения к db2.
(с помощью org.apache.commons.dbcp.BasicDataSource
или com.ibm.as400.access.AS400JDBCManagedConnectionPoolDataSource, оба работают нормально.)
public class AS400JDBCManagedConnectionPoolDataSource extends AS400JDBCManagedDataSource implements ConnectionPoolDataSource, Referenceable, Serializable {
}
public class AS400JDBCManagedDataSource extends ToolboxWrapper implements DataSource, Referenceable, Serializable, Cloneable {
}
2) хочу кешировать insert into
операторы для всех трех таблиц, так что мне не нужно каждый раз отправлять запрос и каждый раз компилировать, что дорого. Я бы просто передал только параметры. (Очевидно, я делаю это с помощью JDBC prepared statements
)
Основываясь на официальном документе IBM " Оптимизировать доступ к DB2 для i5/OS из Java и WebSphere", стр. 17-20 - Включение расширенной динамической поддержки позволяет кэшировать оператор с помощью AS400JDBCManagedConnectionPoolDataSource
,
НО проблема в том, insert into
запросы компилируются каждый раз, что требует 200ms * 3 queries = 600ms
каждый раз.
Пример, который я использую,
public class CustomerOrderEventHandler extends MultiEventHandler {
private static Logger logger = LogManager.getLogger(CustomerOrderEventHandler.class);
//private BasicDataSource establishedConnections = new BasicDataSource();
//private DB2SimpleDataSource nativeEstablishedConnections = new DB2SimpleDataSource();
private AS400JDBCManagedConnectionPoolDataSource dynamicEstablishedConnections =
new AS400JDBCManagedConnectionPoolDataSource();
private State3 orderState3;
private State2 orderState2;
private State1 orderState1;
public CustomerOrderEventHandler() throws SQLException {
dynamicEstablishedConnections.setServerName(State.server);
dynamicEstablishedConnections.setDatabaseName(State.DATABASE);
dynamicEstablishedConnections.setUser(State.user);
dynamicEstablishedConnections.setPassword(State.password);
dynamicEstablishedConnections.setSavePasswordWhenSerialized(true);
dynamicEstablishedConnections.setPrompt(false);
dynamicEstablishedConnections.setMinPoolSize(3);
dynamicEstablishedConnections.setInitialPoolSize(5);
dynamicEstablishedConnections.setMaxPoolSize(50);
dynamicEstablishedConnections.setExtendedDynamic(true);
Connection connection = dynamicEstablishedConnections.getConnection();
connection.close();
}
public void onEvent(CustomerOrder orderEvent){
long start = System.currentTimeMillis();
Connection dbConnection = null;
try {
dbConnection = dynamicEstablishedConnections.getConnection();
long connectionSetupTime = System.currentTimeMillis() - start;
state3 = new State3(dbConnection);
state2 = new State2(dbConnection);
state1 = new State1(dbConnection);
long initialisation = System.currentTimeMillis() - start - connectionSetupTime;
int[] state3Result = state3.apply(orderEvent);
int[] state2Result = state2.apply(orderEvent);
long state1Result = state1.apply(orderEvent);
dbConnection.commit();
logger.info("eventId="+ getEventId(orderEvent) +
",connectionSetupTime=" + connectionSetupTime +
",queryPreCompilation=" + initialisation +
",insertionOnlyTimeTaken=" +
(System.currentTimeMillis() - (start + connectionSetupTime + initialisation)) +
",insertionTotalTimeTaken=" + (System.currentTimeMillis() - start));
} catch (SQLException e) {
logger.error("Error updating the order states.", e);
if(dbConnection != null) {
try {
dbConnection.rollback();
} catch (SQLException e1) {
logger.error("Error rolling back the state.", e1);
}
}
throw new CustomerOrderEventHandlerRuntimeException("Error updating the customer order states.", e);
}
}
private Long getEventId(CustomerOrder order) {
return Long.valueOf(order.getMessageHeader().getCorrelationId());
}
}
И состояния с командами вставки выглядят как ниже,
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class State2 extends State {
private static Logger logger = LogManager.getLogger(DetailState.class);
Connection connection;
PreparedStatement preparedStatement;
String detailsCompiledQuery = "INSERT INTO " + DATABASE + "." + getStateName() +
"(" + DetailState.EVENT_ID + ", " +
State2.ORDER_NUMBER + ", " +
State2.SKU_ID + ", " +
State2.SKU_ORDERED_QTY + ") VALUES(?, ?, ?, ?)";
public State2(Connection connection) throws SQLException {
this.connection = connection;
this.preparedStatement = this.connection.prepareStatement(detailsCompiledQuery); // this is taking ~200ms each time
this.preparedStatement.setPoolable(true); //might not be required, not sure
}
public int[] apply(CustomerOrder event) throws StateException {
event.getMessageBody().getDetails().forEach(detail -> {
try {
preparedStatement.setLong(1, getEventId(event));
preparedStatement.setString(2, getOrderNo(event));
preparedStatement.setInt(3, detail.getSkuId());
preparedStatement.setInt(4, detail.getQty());
preparedStatement.addBatch();
} catch (SQLException e) {
logger.error(e);
throw new StateException("Error setting up data", e);
}
});
long startedTime = System.currentTimeMillis();
int[] inserted = new int[0];
try {
inserted = preparedStatement.executeBatch();
} catch (SQLException e) {
throw new StateException("Error updating allocations data", e);
}
logger.info("eventId="+ getEventId(event) +
",state=details,insertionTimeTaken=" + (System.currentTimeMillis() - startedTime));
return inserted;
}
@Override
protected String getStateName() {
return properties.getProperty("state.order.details.name");
}
}
Таким образом, поток каждый раз, когда событие получено (например, CustomerOrder
), он получает установленное соединение и затем просит состояния инициализировать их statement
s.
Метрики для синхронизации выглядят как ниже,
для первого события требуется 580ms
создать preparedStatement
с на 3 таблицы.
{"timeMillis":1489982655836,"thread":"ScalaTest-run-running-CustomerOrderEventHandlerSpecs","level":"INFO","loggerName":"com.xyz.customerorder.events.handler.CustomerOrderEventHandler",
"message":"eventId=1489982654314,connectionSetupTime=1,queryPreCompilation=580,insertionOnlyTimeTaken=938,insertionTotalTimeTaken=1519","endOfBatch":false,"loggerFqcn":"org.apache.logging.log4j.spi.AbstractLogger","threadId":1,"threadPriority":5}
для второго события, занимает 470ms
подготовить выписки для 3 таблиц, что меньше, чем первое событие, но просто < 100ms
Я предполагаю, что это будет значительно меньше, так как он не должен даже дойти до компиляции.
{"timeMillis":1489982667243,"thread":"ScalaTest-run-running-PurchaseOrderEventHandlerSpecs","level":"INFO","loggerName":"com.xyz.customerorder.events.handler.CustomerOrderEventHandler",
"message":"eventId=1489982665456,connectionSetupTime=0,queryPreCompilation=417,insertionOnlyTimeTaken=1363,insertionTotalTimeTaken=1780","endOfBatch":false,"loggerFqcn":"org.apache.logging.log4j.spi.AbstractLogger","threadId":1,"threadPriority":5}
Что я думаю, так как я закрываюсь preparedStatement
для этого конкретного соединения оно даже не существует для нового соединения. Если это так, то зачем вообще кэшировать операторы в многопоточной среде?
В документации есть аналогичный пример, где ее совершение транзакций внутри одинаково connection
что не так для меня, так как мне нужно иметь несколько соединений одновременно.
Вопросы
первичный
В1) Кеширует ли драйвер DB2 JDBC операторы между несколькими соединениями? Потому что я не вижу большой разницы при подготовке заявления. (см. пример, первый ~600ms
, второй занимает ~500ms
)
Рекомендации
ODP = открытый путь к данным
Пакеты SQL - это постоянные объекты, используемые для хранения информации, связанной с подготовленными операторами SQL. Они могут использоваться драйвером IBM JDBC IBM iSeries Access для IBM Toolbox for Java. Они также используются приложениями, которые используют интерфейс API QSQPRCED (SQL Process Extended Dynamic).
В случае JDBC существование пакета SQL проверяется, когда клиентское приложение выдает первую подготовку оператора SQL. Если пакет не существует, он создается в это время (даже если он еще не содержит операторов SQL)
Конфигурация пула соединений Tomcat jdbc - DB2 на iSeries(AS400)
Драйвер IBM Data Server для кэширования операторов JDBC и SQLJ
2 ответа
Обратите внимание на пару важных моментов, касающихся кэширования операторов:
- Так как
Statement
объекты являются дочерними объектами данногоConnection
, однаждыConnection
закрыты все дочерние объекты (например, всеStatement
объекты) также должны быть закрыты. - Невозможно связать оператор из одного соединения с другим соединением.
- Пул операторов может или не может быть сделан данным драйвером JDBC. Пул операторов также может выполняться уровнем управления соединениями (т. Е. Сервером приложений)
- Согласно спецификации JDBC, значение по умолчанию для
Statement.isPoolable() == false
а такжеPreparedStatement.isPoolable() == true
Однако этот флаг является только подсказкой для драйвера JDBC. В спецификации нет гарантии, что пул операторов произойдет.
Во-первых, я не уверен, что драйвер JT400 выполняет кэширование операторов. Документ, на который вы ссылались в комментарии к вопросу " Оптимизировать доступ к DB2 для i5/OS из Java и WebSphere", посвящен использованию драйвера JTBC JT400 с сервером приложений WebSphere, а на слайде № 3 он указывает, что кэширование операторов происходит из соединения WebSphere. уровень управления, а не собственный уровень драйвера JDBC. Учитывая это, я собираюсь предположить, что JDBC-драйвер JT400 сам по себе не поддерживает кэширование операторов.
Таким образом, в этот момент вы, вероятно, захотите подключиться к какому-то серверу приложений (если только вы не хотите реализовать кеширование операторов самостоятельно, что является своего рода переизобретением колеса). Я точно знаю, что оба продукта WebSphere Application Server (традиционный и Liberty) поддерживают кэширование операторов для любого драйвера JDBC.
Для WebSphere Liberty (более новый продукт) конфигурация источника данных выглядит следующим образом:
<dataSource jndiName="jdbc/myDS" statementCacheSize="10">
<jdbcDriver libraryRef="DB2iToolboxLib"/>
<properties.db2.i.toolbox databaseName="YOURDB" serverName="localhost"/>
</dataSource>
<library id="DB2iToolboxLib">
<fileset dir="/path/to/jdbc/driver/dir" includes="jt400.jar"/>
</library>
Ключевой бит, являющийся statementCacheSize
атрибут <dataSource>
, который имеет значение по умолчанию 10.
(Отказ от ответственности, я разработчик WebSphere, поэтому я собираюсь рассказать о том, что я знаю)
Прежде всего, документация по IBM i Java находится здесь: IBM Toolbox for Java
Во-вторых, я не вижу, где вы устанавливаете свойство "extended dynamic" в true, которое обеспечивает
механизм кэширования динамических операторов SQL на сервере. При первой подготовке определенного оператора SQL он сохраняется в пакете SQL на сервере. Если пакет не существует, он создается автоматически. При последующих подготовках того же оператора SQL сервер может пропустить значительную часть обработки, используя информацию, хранящуюся в пакете SQL. Если для этого параметра установлено значение "true", то имя пакета должно быть установлено с помощью свойства "package".
Я думаю, что вам не хватает некоторых шагов в использовании управляемого пула... вот первый пример в документации IBM
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import com.ibm.as400.access.AS400JDBCManagedConnectionPoolDataSource;
import com.ibm.as400.access.AS400JDBCManagedDataSource;
public class TestJDBCConnPoolSnippet
{
void test()
{
AS400JDBCManagedConnectionPoolDataSource cpds0 = new AS400JDBCManagedConnectionPoolDataSource();
// Set general datasource properties. Note that both connection pool datasource (CPDS) and managed
// datasource (MDS) have these properties, and they might have different values.
cpds0.setServerName(host);
cpds0.setDatabaseName(host);//iasp can be here
cpds0.setUser(userid);
cpds0.setPassword(password);
cpds0.setSavePasswordWhenSerialized(true);
// Set connection pooling-specific properties.
cpds0.setInitialPoolSize(initialPoolSize_);
cpds0.setMinPoolSize(minPoolSize_);
cpds0.setMaxPoolSize(maxPoolSize_);
cpds0.setMaxLifetime((int)(maxLifetime_/1000)); // convert to seconds
cpds0.setMaxIdleTime((int)(maxIdleTime_/1000)); // convert to seconds
cpds0.setPropertyCycle((int)(propertyCycle_/1000)); // convert to seconds
//cpds0.setReuseConnections(false); // do not re-use connections
// Set the initial context factory to use.
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
// Get the JNDI Initial Context.
Context ctx = new InitialContext();
// Note: The following is an alternative way to set context properties locally:
// Properties env = new Properties();
// env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
// Context ctx = new InitialContext(env);
ctx.rebind("mydatasource", cpds0); // We can now do lookups on cpds, by the name "mydatasource".
// Create a standard DataSource object that references it.
AS400JDBCManagedDataSource mds0 = new AS400JDBCManagedDataSource();
mds0.setDescription("DataSource supporting connection pooling");
mds0.setDataSourceName("mydatasource");
ctx.rebind("ConnectionPoolingDataSource", mds0);
DataSource dataSource_ = (DataSource)ctx.lookup("ConnectionPoolingDataSource");
AS400JDBCManagedDataSource mds_ = (AS400JDBCManagedDataSource)dataSource_;
boolean isHealthy = mds_.checkPoolHealth(false); //check pool health
Connection c = dataSource_.getConnection();
}
}