3.7 Connector/J 的连接池

连接池是一种创建和管理连接池的技术,连接池可供任何 需要它们的线程使用。连接池可以大大提高 Java 应用程序的性能,同时减少总体资源使用。

连接池的工作原理

大多数应用程序在主动处理事务 时只需要一个线程就可以访问 JDBC 连接 ,而事务通常只需几毫秒即可完成。当不处理事务时,连接处于空闲状态。连接池使空闲连接能够被其他线程用来做有用的工作。

实际上,当一个线程需要使用 JDBC 对 MySQL 或其他数据库进行操作时,它会从池中请求一个连接。当线程使用完连接后,它会将连接返回到池中,以便其他线程可以使用它。

当连接从池中借出时,它由请求它的线程独占使用。从编程的角度来看,这与您的线程 DriverManager.getConnection()每次需要 JDBC 连接时调用它是一样的。使用连接池,您的线程可能最终使用新连接或已存在的连接。

连接池的好处

连接池的主要好处是:

  • 减少连接创建时间。

    虽然与其他数据库相比,这通常不是 MySQL 提供的快速连接设置的问题,但创建新的 JDBC 连接仍然会产生网络和 JDBC 驱动程序开销,如果连接被回收,这些开销将被避免。

  • 简化的编程模型。

    使用连接池时,每个单独的线程都可以像创建自己的 JDBC 连接一样工作,从而允许您使用直接的 JDBC 编程技术。

  • 受控的资源使用。

    如果您在线程每次需要一个连接时都创建一个新连接而不是使用连接池,那么您的应用程序的资源使用可能会造成浪费,并且可能会导致您的应用程序在负载很重时出现不可预测的行为。

将连接池与 Connector/J 结合使用

JDBC 中连接池的概念已通过 JDBC 2.0 可选接口标准化,所有主要应用程序服务器都实现了这些与 MySQL Connector/J 一起工作的 API。

通常,您在应用程序服务器配置文件中配置一个连接池,并通过 Java 命名和目录接口 (JNDI) 访问它。以下代码显示了如何使用部署在 J2EE 应用程序服务器中的应用程序的连接池:

示例 3.14 连接器/J:将连接池与 J2EE 应用程序服务器一起使用

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.InitialContext;
import javax.sql.DataSource;
public class MyServletJspOrEjb {
    public void doSomething() throws Exception {
        /*
         * Create a JNDI Initial context to be able to
         *  lookup  the DataSource
         *
         * In production-level code, this should be cached as
         * an instance or static variable, as it can
         * be quite expensive to create a JNDI context.
         *
         * Note: This code only works when you are using servlets
         * or EJBs in a J2EE application server. If you are
         * using connection pooling in standalone Java code, you
         * will have to create/configure datasources using whatever
         * mechanisms your particular connection pooling library
         * provides.
         */
        InitialContext ctx = new InitialContext();
         /*
          * Lookup the DataSource, which will be backed by a pool
          * that the application server provides. DataSource instances
          * are also a good candidate for caching as an instance
          * variable, as JNDI lookups can be expensive as well.
          */
        DataSource ds =
          (DataSource)ctx.lookup("java:comp/env/jdbc/MySQLDB");
        /*
         * The following code is what would actually be in your
         * Servlet, JSP or EJB 'service' method...where you need
         * to work with a JDBC connection.
         */
        Connection conn = null;
        Statement stmt = null;
        try {
            conn = ds.getConnection();
            /*
             * Now, use normal JDBC programming to work with
             * MySQL, making sure to close each resource when you're
             * finished with it, which permits the connection pool
             * resources to be recovered as quickly as possible
             */
            stmt = conn.createStatement();
            stmt.execute("SOME SQL QUERY");
            stmt.close();
            stmt = null;
            conn.close();
            conn = null;
        } finally {
            /*
             * close any jdbc instances here that weren't
             * explicitly closed during normal code path, so
             * that we don't 'leak' resources...
             */
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (sqlexception sqlex) {
                    // ignore, as we can't do anything about it here
                }
                stmt = null;
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (sqlexception sqlex) {
                    // ignore, as we can't do anything about it here
                }
                conn = null;
            }
        }
    }
}


如上例所示,在获得 JNDI InitialContext并查找 之后 DataSource,其余代码遵循熟悉的 JDBC 约定。

使用连接池时,请始终确保关闭连接以及由它们创建的任何内容(例如语句或结果集)。无论您的代码中发生什么(异常、控制流等),此规则都适用。当这些对象被关闭时,它们可以被重新使用;否则,它们将被搁浅,这意味着它们所代表的 MySQL 服务器资源(例如缓冲区、锁或套接字)会被捆绑一段时间,或者在最坏的情况下可能永远被捆绑。

调整连接池大小

每个与 MySQL 的连接在客户端和服务器端都有开销(内存、CPU、上下文切换等)。每个连接都会限制您的应用程序和 MySQL 服务器可用的资源数量。无论连接是否真的在做任何有用的工作,这些资源中的许多都会被使用!可以调整连接池以最大限度地提高性能,同时将资源利用率保持在应用程序开始失败的点以下,而不仅仅是运行速度变慢。

连接池的最佳大小取决于预期负载和平均数据库事务时间。实际上,最佳连接池大小可能比您预期的要小。如果您以 Oracle 的 Java Petstore 蓝图应用程序为例,一个包含 15-20 个连接的连接池可以使用 MySQL 和 Tomcat 以可接受的响应时间服务于相对适中的负载(600 个并发用户)。

要为您的应用程序正确调整连接池的大小,请使用 Apache JMeter 或 The Grinder 等工具创建负载测试脚本,并对您的应用程序进行负载测试。

确定起点的一种简单方法是将连接池的最大连接数配置为无限制,运行负载测试,并测量同时使用的最大连接数。然后,您可以从那里向后工作,以确定最小和最大池连接的哪些值可以为您的特定应用程序提供最佳性能。

验证连接

MySQL Connector/J 可以通过对服务器执行轻量级 ping 来验证连接。在负载平衡连接的情况下,这是针对保留的所有活动池内部连接执行的。这有利于使用连接池的 Java 应用程序,因为连接池可以使用此功能来验证连接。根据您的连接池和配置,可以在不同时间执行此验证:

  1. 在池返回到应用程序的连接之前。

  2. 当应用程序将连接返回到池中时。

  3. 在定期检查空闲连接期间。

要使用此功能,请在您的连接池中指定一个以 开头的验证查询/* ping */。请注意,语法必须与指定的完全一致。这将导致驱动程序向服务器发送一个 ping 并返回一个虚拟的轻量级结果集。使用ReplicationConnectionor LoadBalancedConnection时,将在所有活动连接中发送 ping。

正确指定语法至关重要。出于效率原因,语法需要准确,因为此测试是针对执行的每个语句完成的:

protected static final String PING_MARKER = "/* ping */";
...
if (sql.charAt(0) == '/') {
if (sql.startsWith(PING_MARKER)) {
doPingInstead();
...

以下代码段均无效,因为 ping 语法对空格、大写和位置敏感:

sql = "/* PING */ SELECT 1";
sql = "SELECT 1 /* ping*/";
sql = "/*ping*/ SELECT 1";
sql = " /* ping */ SELECT 1";
sql = "/*to ping or not to ping*/ SELECT 1";

前面的语句都会发出正常的 SELECT语句, 不会转化为轻量级的ping。此外,对于负载平衡连接,该语句将针对内部池中的一个连接执行,而不是验证每个底层物理连接。这会导致非活动物理连接呈现陈旧状态,并且它们可能会死亡。如果 Connector/J 然后重新平衡,它可能会选择死连接,从而导致将异常传递给应用程序。为了帮助防止这种情况,您可以 loadBalanceValidateConnectionOnSwapServer在使用前使用验证连接。

如果您的 Connector/J 部署使用允许您指定验证查询的连接池,请利用它,但确保查询完全/* ping */. 如果您正在使用 Connector/J 的负载平衡或复制感知功能,这一点尤其重要,因为它有助于保持活动连接,否则这些连接将变得陈旧和死亡,从而导致以后出现问题。