什么是 MySQL JDBC 连接池中最高效的连接检测语句?

这篇具有很好参考价值的文章主要介绍了什么是 MySQL JDBC 连接池中最高效的连接检测语句?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在回答这个问题之前,首先我们看看 MySQL 中有哪些常用的 JDBC 连接池:

  • c3p0
  • DBCP
  • Druid
  • Tomcat JDBC Pool
  • HikariCP

这些连接池中,c3p0 是一个老牌的连接池,很多流行框架,在其老版本中,都将 c3p0 作为默认的连接池。

DBCP 和 Tomcat JDBC Pool(Tomcat 的默认连接池)是 Apache 开源的。

Druid 是阿里开源的,它不仅仅是个数据库连接池,还可以监控数据库的访问性能,支持数据库密码加密等。

HikariCP 是目前风头最劲的 JDBC 连接池,其号称性能最好。

从下图 HikariCP 官网给出的压测结果来看,也确实如此,性能上吊打 c3p0、DBCP2。

包括 SpringBoot 2.0 也将 HikariCP 作为默认的数据库连接池。

什么是 MySQL JDBC 连接池中最高效的连接检测语句?

MySQL JDBC连接池中最高效的连接检测语句

实际上,对于这个问题,c3p0 的官方文档(https://www.mchange.com/projects/c3p0/)中给出了答案。

When configuring Connection testing, first try to minimize the cost of each test. If you are using a JDBC driver that you are certain supports the new(ish) jdbc4 API — and if you are using c3p0-0.9.5 or higher! — let your driver handle this for you. jdbc4 Connections include a method called isValid() that should be implemented as a fast, reliable Connection test. By default, c3p0 will use that method if it is present.

However, if your driver does not support this new-ish API, c3p0's default behavior is to test Connections by calling the getTables() method on a Connection's associated DatabaseMetaData object. This has the advantage of being very robust and working with any database, regardless of the database schema. However, a call to DatabaseMetaData.getTables() is often much slower than a simple database query, and using this test may significantly impair your pool's performance.

The simplest way to speed up Connection testing under a JDBC 3 driver (or a pre-0.9.5 version of c3p0) is to define a test query with the preferredTestQuery parameter. Be careful, however. Setting preferredTestQuery will lead to errors as Connection tests fail if the query target table does not exist in your database prior to initialization of your DataSource. Depending on your database and JDBC driver, a table-independent query like SELECT 1 may (or may not) be sufficient to verify the Connection. If a table-independent query is not sufficient, instead of preferredTestQuery, you can set the parameter automaticTestTable. Using the name you provide, c3p0 will create an empty table, and make a simple query against it to test the database.

从上面的描述中可以看到,最高效的连接检测语句是 JDBC4 中引入的isValid方法 。

其次是通过 preferredTestQuery 设置一个简单的查询操作(例如SELECT 1),最后才是默认的getTables方法。

包括 HikariCP 的文档中,也推荐使用 isValid 方法。只有当驱动比较老,不支持 isValid 方法时,才建议通过 connectionTestQuery 自定义检测语句。

🔤connectionTestQuery

If your driver supports JDBC4 we strongly recommend not setting this property. This is for "legacy" drivers that do not support the JDBC4 Connection.isValid() API. This is the query that will be executed just before a connection is given to you from the pool to validate that the connection to the database is still alive. Again, try running the pool without this property, HikariCP will log an error if your driver is not JDBC4 compliant to let you know. Default: none

所以接下来,我们要弄懂以下几个问题:

  1. 什么是 isValid() 。

  2. 什么是 getTables()。

  3. 不同连接检测语句之间的性能对比情况。

  4. 为什么 isValid() 的性能最好,MySQL 服务端是如何处理的。

  5. 怎么设置才能使用 isValid() 进行连接检测?

什么是 isValid()

isValid 方法是在 JDBC4 中引入的。JDBC 是 Java 用于与关系型数据库通信的标准API。

JDBC4 指的是 Java Database Connectivity (JDBC) 的第 4 版本,该版本是在 Java 6(也被称为Java 1.6)中引入的。

所以只要程序使用的是 Java 1.6 及以上的版本,都支持 isValid 方法。

下面,我们看看这个方法的具体实现细节。

// src/main/user-impl/java/com/mysql/cj/jdbc/ConnectionImpl.java
@Override
public boolean isValid(int timeout) throws SQLException {
    synchronized (getConnectionMutex()) { // 获取与连接相关的锁
        if (isClosed()) {
            return false; // 如果连接已关闭,返回 false,表示连接无效
        }
        try {
            try {
                // 调用 pingInternal 方法,检查连接是否有效,timeout 参数以毫秒为单位
                pingInternal(false, timeout * 1000); 
            } catch (Throwable t) {
                try {
                    abortInternal(); // 如果 pingInternal 抛出异常,调用 abortInternal 方法中止连接
                } catch (Throwable ignoreThrown) { 
                    // we're dead now anyway // 忽略异常,因为连接已经无效
                }
                return false; // 返回 false,表示连接无效
            }
        } catch (Throwable t) {
            return false; // 如果在 try 块中的任何地方发生异常,返回 false,表示连接无效
        }
        return true; // 如果没有异常发生,表示连接有效,返回 true
    }
}

isValid 方法的核心是 pingInternal 方法。我们继续看看 pingInternal 方法的实现。

@Override
public void pingInternal(boolean checkForClosedConnection, int timeoutMillis) throws SQLException {
    this.session.ping(checkForClosedConnection, timeoutMillis);
}

方法中的 session 是个 NativeSession 对象。

以下是 NativeSession 类中 ping 方法的实现。

// src/main/core-impl/java/com/mysql/cj/NativeSession.java
public void ping(boolean checkForClosedConnection, int timeoutMillis) {
    if (checkForClosedConnection) { // 如果需要检查连接是否已关闭,调用 checkClosed 方法
        checkClosed();
    }
    ...
    // this.protocol.sendCommand 是发送命令,this.commandBuilder.buildComPing(null)是构造命令。
    this.protocol.sendCommand(this.commandBuilder.buildComPing(null), false, timeoutMillis); // it isn't safe to use a shared packet here
}

实现中的重点是 this.protocol.sendCommand 和 this.commandBuilder.buildComPing 这两个方法。前者用来发送命令,后者用来构造命令。

后者中的 commandBuilder 是个 NativeMessageBuilder 对象。

以下是 NativeMessageBuilder 类中 buildComPing 方法的实现。

// src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeMessageBuilder.java
public NativePacketPayload buildComPing(NativePacketPayload sharedPacket) {
    NativePacketPayload packet = sharedPacket != null ? sharedPacket : new NativePacketPayload(1);
    packet.writeInteger(IntegerDataType.INT1, NativeConstants.COM_PING);
    return packet;
}

NativePacketPayload 是与 MySQL 服务器通信的数据包,NativeConstants.COM_PING 即 MySQL 中的 COM_PING 命令包。

所以,实际上,isValid 方法封装的就是COM_PING命令包。

什么是 getTables()

getTables() 是 MySQL JDBC 驱动中 DatabaseMetaData 类中的一个方法,用来查询给定的库中是否有指定的表。

c3p0 使用这个方法检测时,只指定了表名 PROBABLYNOT。

库名因为设置的是 null,所以默认会使用 JDBC URL 中指定的数据库。如jdbc:mysql://10.0.0.198:3306/information_schema中的 information_schema。

{
    rs = c.getMetaData().getTables( null,
                                    null,
                                    "PROBABLYNOT",
                                    new String[] {"TABLE"} );
    return CONNECTION_IS_OKAY;
}

如果使用的驱动是 8.0 之前的版本,对应的检测语句是:

SHOW FULL TABLES FROM `information_schema` LIKE 'PROBABLYNOT'

如果使用的驱动是 8.0 之后的版本,对应的检测语句是:

SELECT TABLE_SCHEMA AS TABLE_CAT, NULL AS TABLE_SCHEM, TABLE_NAME, CASE WHEN TABLE_TYPE='BASE TABLE' THEN CASE WHEN TABLE_SCHEMA = 'mysql' OR TABLE_SCHEMA = 'performance_schema' THEN 'SYSTEM TABLE' ELSE 'TABLE' END WHEN TABLE_TYPE='TEMPORARY' THEN 'LOCAL_TEMPORARY' ELSE TABLE_TYPE END AS TABLE_TYPE, TABLE_COMMENT AS REMARKS, NULL AS TYPE_CAT, NULL AS TYPE_SCHEM, NULL AS TYPE_NAME, NULL AS SELF_REFERENCING_COL_NAME, NULL AS REF_GENERATION FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'PROBABLYNOT' HAVING TABLE_TYPE IN ('TABLE',null,null,null,null) ORDER BY TABLE_TYPE, TABLE_SCHEMA, TABLE_NAME

因为 c3p0 是一个老牌的连接池,它流行的时候 MySQL 8.0 还没发布。所以,对于 c3p0,见到更多的是前面这一个检测语句。

四个连接检测语句之间的性能对比

下面我们对比下PINGSELECT 1SHOW FULL TABLES FROM information_schema LIKE 'PROBABLYNOT' 和 INFORMATION_SCHEMA.TABLES这四个连接检测语句的执行耗时情况。

下面是具体的测试结果,每个语句循环执行了 100000 次。

PING time for 100000 iterations: 3.04852 seconds
SELECT 1 time for 100000 iterations: 12.61825 seconds
SHOW FULL TABLES time for 100000 iterations: 66.21564 seconds
INFORMATION_SCHEMA.TABLES time for 100000 iterations: 69.32230 seconds

为了避免网络的影响,测试使用的是 socket 连接。

测试脚本地址:https://github.com/slowtech/dba-toolkit/blob/master/mysql/connection_test_benchemark.py

测试脚本中,使用的是connection.ping(reconnect=False)命令。

这个 ping 命令跟 COM_PING 命令包有什么关系呢?

实际上,在 pymysql 中,ping 命令封装的就是 COM_PING 命令包。

def ping(self, reconnect=True):
    if self._sock is None:
        if reconnect:
            self.connect()
            reconnect = False
        else:
            raise err.Error("Already closed")
    try:
        self._execute_command(COMMAND.COM_PING, "")
        self._read_ok_packet()
    except Exception:
        if reconnect:
            self.connect()
            self.ping(False)
        else:
            raise

MySQL 服务端对于 COM_PING 的处理逻辑

以下是 MySQL 服务端处理 ping 命令的堆栈信息。

[mysqld] my_ok(THD *, unsigned long long, unsigned long long, const char *) sql_class.cc:3247
[mysqld] dispatch_command(THD *, const COM_DATA *, enum_server_command) sql_parse.cc:2268
[mysqld] do_command(THD *) sql_parse.cc:1362
[mysqld] handle_connection(void *) connection_handler_per_thread.cc:302
[mysqld] pfs_spawn_thread(void *) pfs.cc:2942
[libsystem_pthread.dylib] _pthread_start 0x0000000197d3bfa8

整个链路比较短,MySQL 服务端在接受到客户端请求后,首先会初始化一个线程。

线程初始化完毕后,会验证客户端用户的账号密码是否正确。

如果正确,则线程会循环从客户端连接中读取命令并执行。

不同的命令,会有不同的处理逻辑。

具体如何处理是在dispatch_command中定义的。

bool dispatch_command(THD *thd, const COM_DATA *com_data,
                      enum enum_server_command command) {
  ...
  switch (command) {
    case COM_INIT_DB: {
      LEX_STRING tmp;
      thd->status_var.com_stat[SQLCOM_CHANGE_DB]++;
      thd->convert_string(&tmp, system_charset_info,
                          com_data->com_init_db.db_name,
                          com_data->com_init_db.length, thd->charset());

      LEX_CSTRING tmp_cstr = {tmp.str, tmp.length};
      if (!mysql_change_db(thd, tmp_cstr, false)) {
        query_logger.general_log_write(thd, command, thd->db().str,
                                       thd->db().length);
        my_ok(thd);
      }
      break;
    }
    case COM_REGISTER_SLAVE: {
      // TODO: access of protocol_classic should be removed
      if (!register_replica(thd, thd->get_protocol_classic()->get_raw_packet(),
                            thd->get_protocol_classic()->get_packet_length()))
        my_ok(thd);
      break;
    }
    case COM_RESET_CONNECTION: {
      thd->status_var.com_other++;
      thd->cleanup_connection();
      my_ok(thd);
      break;
    }
    ...
    case COM_PING:
      thd->status_var.com_other++;
      my_ok(thd);  // Tell client we are alive
      break;
    ...
}

可以看到,对于 COM_PING 命令包,MySQL 服务端的处理比较简单,只是将 com_other(com_other 对应状态变量中的 Com_admin_commands)的值加 1,然后返回一个 OK 包。

反观SELECT 1命令,虽然看上去也足够简单,但毕竟是一个查询,是查询就要经过词法分析、语法分析、优化器、执行器等阶段。

以下是SELECT 1在执行阶段的堆栈信息,可以看到,它比 COM_PING 的堆栈信息要复杂不少。

[mysqld] FakeSingleRowIterator::Read() basic_row_iterators.h:284
[mysqld] Query_expression::ExecuteIteratorQuery(THD *) sql_union.cc:1290
[mysqld] Query_expression::execute(THD *) sql_union.cc:1343
[mysqld] Sql_cmd_dml::execute_inner(THD *) sql_select.cc:786
[mysqld] Sql_cmd_dml::execute(THD *) sql_select.cc:586
[mysqld] mysql_execute_command(THD *, bool) sql_parse.cc:4604
[mysqld] dispatch_sql_command(THD *, Parser_state *) sql_parse.cc:5239
[mysqld] dispatch_command(THD *, const COM_DATA *, enum_server_command) sql_parse.cc:1959
[mysqld] do_command(THD *) sql_parse.cc:1362
[mysqld] handle_connection(void *) connection_handler_per_thread.cc:302
[mysqld] pfs_spawn_thread(void *) pfs.cc:2942
[libsystem_pthread.dylib] _pthread_start 0x0000000181d53fa8

怎么设置才能使用 isValid() 进行连接检测

对于 HikariCP 连接池来说,不设置 connectionTestQuery 即可。

这一点,可从连接检测任务(KeepaliveTask)的代码中看出来。

try {
   final var validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;

   if (isUseJdbc4Validation) { 
      return !connection.isValid(validationSeconds);
   }

   try (var statement = connection.createStatement()) {
      if (isNetworkTimeoutSupported != TRUE) {
         setQueryTimeout(statement, validationSeconds);
      }

      statement.execute(config.getConnectionTestQuery());
   }
}

可以看到,是否使用 connection.isValid 是由 isUseJdbc4Validation 决定的。

而 isUseJdbc4Validation 是否为 true,是由配置中是否设置了connectionTestQuery决定的,该参数不设置则默认为 none。

this.isUseJdbc4Validation = config.getConnectionTestQuery() == null;

对于 c3p0 连接池来说,需使用 v0.9.5 及之后的版本,同时不要设置 preferredTestQuery 或者 automaticTestTable

注意,如果要定期对空闲连接进行检测,在 HikariCP 中,需要设置 keepaliveTime。而在 c3p0 中,则需设置 idleConnectionTestPeriod。这两个参数的默认值为 0,即不会对空闲连接进行定期检测。

mysqladmin ping 命令的实现逻辑

mysqladmin 中有个 ping 命令可以检查 MySQL 服务端的存活情况。

# mysqladmin --help | grep ping
  ping   Check if mysqld is alive
  
# mysqladmin -h127.0.0.1 -P3306 -uroot -p'123456' ping
mysqld is alive

这个 ping 命令实际上封装的就是 COM_PING 命令包。

// mysqladmin.cc
case ADMIN_PING:
  mysql->reconnect = false; /* We want to know of reconnects */
  if (!mysql_ping(mysql)) {
    if (option_silent < 2) puts("mysqld is alive");
  } else {
    if (mysql_errno(mysql) == CR_SERVER_GONE_ERROR) {
      mysql->reconnect = true;
      if (!mysql_ping(mysql))
        puts("connection was down, but mysqld is now alive");
    } else {
      my_printf_error(0, "mysqld doesn't answer to ping, error: '%s'",
                      error_flags, mysql_error(mysql));
      return -1;
    }
  }
  mysql->reconnect = true; /* Automatic reconnect is default */
  break;

// libmysql/libmysql.cc
int STDCALL mysql_ping(MYSQL *mysql) {
  int res;
  DBUG_TRACE;
  res = simple_command(mysql, COM_PING, nullptr, 0, 0);
  if (res == CR_SERVER_LOST && mysql->reconnect)
    res = simple_command(mysql, COM_PING, nullptr, 0, 0);
  return res;
}  

总结

  1. 连接检测语句,首选是 JDBC 驱动中的 isValid 方法,其次才是自定义查询语句。

  2. 虽然 isValid 方法是 JDBC 4 中才支持的,但 JDBC 4 早在 Java 6 中就引入了,所以,只要程序使用的是 Java 1.6 及以上的版本,都支持 isValid 方法。

  3. 从连接池性能的角度出发,不建议使用 c3p0。但有些框架,在比较老的版本中,还是将 c3p0 作为默认的连接池。

    如果使用的是 c3p0 v0.9.5 之前的版本,建议配置 preferredTestQuery。如果使用的是 v0.9.5 及之后的版本,推荐使用 isValid。

  4. SHOW FULL TABLES FROM xx LIKE 'PROBABLYNOT' 这个查询,在并发量较高的情况下,会对 MySQL 的性能产生较大的负面影响,线上慎用。文章来源地址https://www.toymoban.com/news/detail-760587.html

参考

  1. MySQL JDBC驱动地址:https://github.com/mysql/mysql-connector-j
  2. PyMySQL项目地址:https://github.com/PyMySQL/PyMySQL

到了这里,关于什么是 MySQL JDBC 连接池中最高效的连接检测语句?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JDBC连接数据库----Mysql七大步骤详解

             1、什么是jdbc?         JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发者能够编

    2023年04月12日
    浏览(70)
  • com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure(Hive连接MySQL报错)

    详细的错误日志为: NestedThrowables: java.sql.SQLException: Unable to open a test connection to the given database. JDBC url = jdbc:mysql://192.168.1.186:3306/hive?createDatabaseIfNotExist=trueuseSSL=false, username = hive. Terminating connection pool (set lazyInit to true if you expect to start your database after your app). Original Exception: ---

    2024年02月08日
    浏览(33)
  • Java的JDBC编程—连接Mysql数据库

    目录 一、 Java的数据库编程:JDBC 二、JDBC工作原理 三、 JDBC使用 四、JDBC使用步骤总结  五. JDBC常用接口和类 5.1 JDBC API 5.2 数据库连接Connection 5.3 Statement对象 5.4 ResultSet对象      JDBC,即Java Database Connectivity,java数据库连接。是一种用于执行SQL语句的Java API,它是 Java中的数据

    2024年02月05日
    浏览(50)
  • MySQL | JDBC连接数据库详细教程【全程干货】

    JDBC,即 Java Database Connectivity ,java数据库连接。是一种用于执行SQL语句的Java API,它是Java中的数据库连接规范。这个API由 java.sql.*,javax.sql.* 包中的一些类和接口组成,它为Java开发人员操作数据库提供了一个 标准的API ,可以为多种关系数据库提供统一访问 JDBC 为多种关系数据

    2024年02月06日
    浏览(52)
  • 记录JDBC连接MySQL数据库时遇到的问题

    记录使用 JDBC连接数据库的时候遇到的问题 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver 可以参考这篇 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver 博主总结的很全,就不赘述了~ com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure The last packet successfully received from the

    2024年02月10日
    浏览(40)
  • Java-JDBC建立数据库连接(MySQL)

    注意:连接数据需要先在JAVA中导入mysql的jar包。 1.1—下载JAR包 1、打开浏览器搜索MySQL,进入官网 2、点击DOWNLOADS    3、点击 MySQL Community (GPL) Downloads  4、点击Connector/J 5、点击Archieve 6、选择版本,和OS,然后点击下载即可。          版本号 下载地址 8.0.32 https://download

    2024年02月03日
    浏览(46)
  • Java连接mysql数据库方法及代码(jdbc)

    编译器使用IDEA 我的相关博客: java代码程序中连接mysql数据库的方法及代码 mysql数据库并发上锁问题,java代码 1.首先从mysql官网下载mysql-connector-java.jar包到本地。 这里注意要和你本地的mysql数据库版本相匹配! 下载相应的压缩包,在本地解压即可进行下一步操作。 2.打开自己

    2024年02月08日
    浏览(43)
  • java中连接数据库com.mysql.jdbc.Driver和com.mysql.cj.jdbc.Driver的区别?

    com.mysql.jdbc.Driver 和 com.mysql.cj.jdbc.Driver 是MySQL数据库连接驱动的不同版本。 com.mysql.jdbc.Driver :这是旧版的MySQL JDBC驱动(版本5.x)。它已经过时,不再推荐使用。如果您使用较旧的MySQL版本(如MySQL 5.7及以下),可以考虑使用此驱动。但请注意,该驱动在MySQL 8.0及更高版本上可

    2024年02月14日
    浏览(39)
  • 【解决MySQL-jdbc连接问题】com.mysql.jdbc.Driver was not found, trying direct instantiat

    启动服务时出现报错 而且接口有时候能访问成功,有时候的超时连接,异常的慢 经查询,是由于时区配置的有歧义 driver 目前用的是 com.mysql.jdbc.Driver,新版已经变为 com.mysql.cj.jdbc.Driver 作出一下修改: 问题解决:

    2024年02月07日
    浏览(36)
  • java代码实现,利用JDBC接口-连接Mysql数据库

    1、JDBC本质上是一个接口,也就是java语言操作数据库的一套API(应用程序编程接口), 接口就规则,也就是sun公司创建了一个jdbc接口,各个sql(数据库管理系统)去实现接口提供jar包。其优点JDBC不是指单一操作某一个数据库。各个厂商使用相同的接口。不同的sql厂家实现

    2024年02月09日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包