使用DB2的JDBC语句池没有明显的时间差

时间:2017-03-20 04:23:20

标签: jdbc db2 prepared-statement db2-400 jt400

我正在使用JDBC db2 driver,a.k.a。JT400连接到中型计算机系统Application System/400上的db2服务器。

我的目标是insert into来自IBM大型机外部的三个表,它们将是云实例(例如Amazon WS)。

使表现更好

1)我正在使用已建立的连接来连接到db2。 (使用org.apache.commons.dbcp.BasicDataSourcecom.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, page 17-20 - Enabling Extended Dynamic Support,可以使用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

时间指标如下所示

对于第一个事件,需要580ms为3个表创建preparedStatement

{"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,我认为它大大减少,因为它甚至不应该让它编译。<​​/ p>

{"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内进行交易,但对我来说并非如此,因为我需要同时拥有多个连接。

snapshot

问题

主要

Q1)DB2 JDBC驱动程序是否在多个连接之间缓存语句?因为在准备声明时我没有看到太大的区别。 (参见示例,第一个需要~600ms,第二个需要~500ms

参考

ODP =开放数据路径

SQL packages

  

SQL包是用于存储相关信息的永久对象   准备SQL语句。它们可以由IBM iSeries Access使用   对于IBM Toolbox for   Java JDBC驱动程序。它们也被使用它的应用程序使用   QSQPRCED(SQL Process Extended Dynamic)API接口。

     

在JDBC的情况下,SQL包的存在是   检查客户端应用程序何时发出SQL的第一个准备   声明。如果包不存在,则在那时创建它   (即使它可能还没有包含任何SQL语句)

Tomcat jdbc connection pool configuration - DB2 on iSeries(AS400)

IBM Data Server Driver for JDBC and SQLJ statement caching

2 个答案:

答案 0 :(得分:1)

有关语句缓存的一些重要注意事项:

  • 由于Statement个对象是给定Connection的子对象,因此一旦Connection关闭,所有子对象(例如所有Statement个对象)也必须关闭。
  • 无法将来自一个连接的语句与不同的连接相关联。
  • 语句池可能是也可能不是由给定的JDBC驱动程序完成。语句池也可以由连接管理层(即应用服务器)执行
  • 根据JDBC规范,Statement.isPoolable() == falsePreparedStatement.isPoolable() == true的默认值,但此标志只是JDBC驱动程序的提示。该规范无法保证会出现语句池。

首先,我不确定JT400驱动程序是否进行语句缓存。您在问题评论中引用的文档Optimize Access to DB2 for i5/OS from Java and WebSphere特定于将JT400 JDBC驱动程序与WebSphere应用程序服务器一起使用,而在幻灯片#3上,它指示语句高速缓存来自WebSphere连接管理层,而不是本机JDBC驱动程序层。鉴于此,我将假设JT400 JDBC驱动程序本身不支持语句缓存。

所以在这一点上你可能想要插入某种应用服务器(除非你想自己实现语句缓存,这有点重新发明轮子)。我确信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的{​​{1}}属性,其默认值为10.

(免责声明,我是一名WebSphere开发人员,所以我要谈谈我所知道的事情)

答案 1 :(得分:0)

首先,IBM i Java文档在此处:IBM Toolbox for Java

其次,我没有看到您将"extended dynamic"属性设置为true的位置

  

一种在服务器上缓存动态SQL语句的机制。首先   准备一个特定的SQL语句时,它存储在SQL中   包在服务器上。如果包不存在,那就是   自动创建。在后续准备相同的SQL   声明,服务器可以跳过很大一部分处理   使用存储在SQL包中的信息。如果设置为&#34; true&#34;,   然后必须使用&#34;包&#34;设置包名。属性。

我认为您错过了使用托管池的一些步骤...这是IBM docs

中的第一个示例
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(); 

    }

}