我正在使用JDBC db2 driver(又名 JT400)连接到Application System/400上的 db2 服务器,这是一个中端计算机系统。
我的目标是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 官方文档Optimize Access to DB2 for i5/OS from Java and 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。
计时指标如下所示,
对于第一个事件,需要为 3 个表580ms创建preparedStatements。
{"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这对我来说不是这种情况,因为我需要同时有多个连接。
问题
基本的
Q1) DB2 JDBC 驱动程序是否在多个连接之间缓存语句?因为我在准备声明时没有看到太大的不同。(参见示例,第一个需要~600ms,第二个需要~500ms)
参考
ODP = 开放数据路径
SQL 包是永久对象,用于存储与准备好的 SQL 语句相关的信息。IBM iSeries Access 可以将它们用于 IBM Toolbox for Java JDBC 驱动程序。它们也被使用 QSQPRCED(SQL 进程扩展动态)API 接口的应用程序使用。
在 JDBC 的情况下,当客户端应用程序发出第一个 SQL 语句准备时,会检查 SQL 包的存在。如果包不存在,则在那时创建它(即使它可能还没有包含任何 SQL 语句)
