MySQL 连接器/J 8.0 开发人员指南  /  第 15 章连接器/J 应用程序故障排除

第 15 章连接器/J 应用程序故障排除

本节介绍使用 MySQL Connector/J 的应用程序最常遇到的问题的症状和解决方案。

问题

  • 15.1: 当我尝试使用 MySQL Connector/J 连接到数据库时,出现以下异常:

    SQLException: Server configuration denies access to data source
    SQLState: 08001
    VendorError: 0

    到底是怎么回事?我可以很好地连接 MySQL 命令行客户端。

  • 15.2: 我的应用程序抛出 SQLException 'No Suitable Driver'。为什么会这样?

  • 15.3: 我试图在一个小程序或应用程序中使用 MySQL Connector/J,但我得到一个类似于以下的异常:

    SQLException: Cannot connect to MySQL server on host:3306.
    Is there a MySQL server running on the machine/port you
    are trying to connect to?
    
    (java.security.AccessControlException)
    SQLState: 08S01
    VendorError: 0
  • 15.4: 我有一个可以正常工作一天的 servlet/应用程序,然后在一夜之间停止工作

  • 15.5: 我无法使用 Connector/J 连接到 MySQL 服务器,但我确定连接参数是正确的。

  • 15.6: 更新包含 主键FLOAT复合主键FLOAT 的表无法更新表并引发异常。

  • 15.7: 我得到一个 ER_NET_PACKET_TOO_LARGE 异常,即使我想使用 JDBC 插入的二进制 blob 大小安全地低于该 max_allowed_packet大小。

  • 15.8: 如果我收到类似于以下的错误消息,我应该怎么办:通信链接失败——最后一个发送到服务器的数据包是 X 毫秒前

  • 15.9: 为什么连接器/J 在通信失败后不重新连接到 MySQL 并重新发出语句而不是抛出异常,即使我使用了 autoReconnect连接字符串选项?

  • 15.10: 如何将 3 字节 UTF8 与 Connector/J 一起使用?

  • 15.11: 如何将 4 字节 UTF8 ( utf8mb4) 与 Connector/J 一起使用?

  • 15.12:在插入 BLOB 时, 使用useServerPrepStmts=false和某些字符编码会导致损坏。如何避免这种情况?

问题和解答

15.1: 当我尝试使用 MySQL Connector/J 连接到数据库时,出现以下异常:

SQLException: Server configuration denies access to data source
SQLState: 08001
VendorError: 0

到底是怎么回事?我可以很好地连接 MySQL 命令行客户端。

Connector/J 通常使用 TCP/IP 套接字连接到 MySQL(有关例外情况,请参阅第 6.10 节,“使用 Unix 域套接字连接”第 6.11 节,“使用命名管道连接”)。MySQL 服务器上的安全管理器使用其授权表来确定是否允许 TCP/IP 连接。因此,您必须通过向您的 MySQL 服务器发出一条GRANT语句来向 MySQL 服务器添加必要的安全凭证以进行连接。有关详细信息,请参阅GRANT 语句

警告

在 MySQL 上不正确地更改特权和权限可能会导致您的服务器安装具有非最佳安全属性。

笔记

测试你与 mysql命令行客户端的连接将不起作用,除非你添加标志,并使用主机--host 以外的东西 。localhost如果您使用特殊主机名, mysql命令行客户端将尝试使用 Unix 域套接字 localhost。如果您正在测试与 的 TCP/IP 连接,请改为localhost使用 127.0.0.1as 主机名。

15.2: 我的应用程序抛出 SQLException 'No Suitable Driver'。为什么会这样?

导致此错误的可能原因有以下三种:

  • Connector/J 驱动程序不在您的目录中 CLASSPATH,请参阅 第 4 章,Connector/J 安装

  • 您的连接 URL 格式不正确,或者您引用了错误的 JDBC 驱动程序。

  • 使用 DriverManager 时, jdbc.drivers系统属性尚未填充 Connector/J 驱动程序的位置。

15.3: 我试图在一个小程序或应用程序中使用 MySQL Connector/J,但我得到一个类似于以下的异常:

SQLException: Cannot connect to MySQL server on host:3306.
Is there a MySQL server running on the machine/port you
are trying to connect to?

(java.security.AccessControlException)
SQLState: 08S01
VendorError: 0

要么您正在运行一个 Applet,要么您的 MySQL 服务器已经安装并 skip_networking启用了系统变量,要么您的 MySQL 服务器前面有一个防火墙。

Applet 只能将网络连接返回到运行为 applet 提供 .class 文件的 Web 服务器的机器。这意味着 MySQL 必须在同一台机器上运行(或者你必须有某种端口重定向)才能工作。这也意味着您将无法从本地文件系统测试小程序,但必须始终将它们部署到 Web 服务器。

Connector/J 通常使用 TCP/IP 套接字连接到 MySQL(有关例外情况,请参阅第 6.10 节,“使用 Unix 域套接字连接”第 6.11 节,“使用命名管道连接”)。与 MySQL 的 TCP/IP 通信可能会受到 skip_networking系统变量或服务器防火墙的影响。如果 MySQL 已在skip_networking 启用状态下启动,则需要在文件中将其注释掉 /etc/mysql/my.cnf/etc/my.cnfTCP/IP 连接才能工作。(请注意,您的服务器配置文件也可能存在于data你的 MySQL 服务器的目录,或者其他地方,这取决于 MySQL 是如何编译的;Oracle 创建的二进制文件总是寻找 /etc/my.cnfand datadir/my.cnf;有关详细信息,请参阅使用选项文件。)如果您的 MySQL 服务器已设置防火墙,则需要将防火墙配置为允许从运行 Java 代码的主机到 MySQL 侦听端口上的 MySQL 服务器的 TCP/IP 连接到(默认情况下,3306)。

15.4: 我有一个可以正常工作一天的 servlet/应用程序,然后在一夜之间停止工作

MySQL 在 8 小时不活动后关闭连接。您要么需要使用处理过时连接的连接池,要么使用autoReconnect 参数(请参阅 第 6.3 节,“配置属性”)。

此外,捕获SQLExceptions您的应用程序并处理它们,而不是一直传播它们直到您的应用程序退出。这只是良好的编程习惯。MySQL Connector/J 将 在处理查询期间遇到网络连接问题时将SQLState(请参阅 java.sql.SQLException.getSQLState()您的 API 文档)设置为。08S01此时尝试重新连接到 MySQL。

以下(简单的)示例显示了可以处理这些异常的代码可能如下所示:

示例 15.1 连接器/J:具有重试逻辑的事务示例

public void doBusinessOp() throws SQLException {
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;

    //
    // How many times do you want to retry the transaction
    // (or at least _getting_ a connection)?
    //
    int retryCount = 5;

    boolean transactionCompleted = false;

    do {
        try {
            conn = getConnection(); // assume getting this from a
                                    // javax.sql.DataSource, or the
                                    // java.sql.DriverManager

            conn.setAutoCommit(false);

            //
            // Okay, at this point, the 'retry-ability' of the
            // transaction really depends on your application logic,
            // whether or not you're using autocommit (in this case
            // not), and whether you're using transactional storage
            // engines
            //
            // For this example, we'll assume that it's _not_ safe
            // to retry the entire transaction, so we set retry
            // count to 0 at this point
            //
            // If you were using exclusively transaction-safe tables,
            // or your application could recover from a connection going
            // bad in the middle of an operation, then you would not
            // touch 'retryCount' here, and just let the loop repeat
            // until retryCount == 0.
            //
            retryCount = 0;

            stmt = conn.createStatement();

            String query = "SELECT foo FROM bar ORDER BY baz";

            rs = stmt.executeQuery(query);

            while (rs.next()) {
            }

            rs.close();
            rs = null;

            stmt.close();
            stmt = null;

            conn.commit();
            conn.close();
            conn = null;

            transactionCompleted = true;
        } catch (SQLException sqlEx) {

            //
            // The two SQL states that are 'retry-able' are 08S01
            // for a communications error, and 40001 for deadlock.
            //
            // Only retry if the error was due to a stale connection,
            // communications problem or deadlock
            //

            String sqlState = sqlEx.getSQLState();

            if ("08S01".equals(sqlState) || "40001".equals(sqlState)) {
                retryCount -= 1;
            } else {
                retryCount = 0;
            }
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException sqlEx) {
                    // You'd probably want to log this...
                }
            }

            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException sqlEx) {
                    // You'd probably want to log this as well...
                }
            }

            if (conn != null) {
                try {
                    //
                    // If we got here, and conn is not null, the
                    // transaction should be rolled back, as not
                    // all work has been done

                    try {
                        conn.rollback();
                    } finally {
                        conn.close();
                    }
                } catch (SQLException sqlEx) {
                    //
                    // If we got an exception here, something
                    // pretty serious is going on, so we better
                    // pass it up the stack, rather than just
                    // logging it...

                    throw sqlEx;
                }
            }
        }
    } while (!transactionCompleted && (retryCount > 0));
}


笔记

不建议使用该autoReconnect选项,因为没有安全的方法可以在不冒连接状态或数据库状态信息损坏的风险的情况下重新连接到 MySQL 服务器。相反,使用连接池,这将使您的应用程序能够使用池中的可用连接连接到 MySQL 服务器。该 autoReconnect设施已弃用,并可能在未来的版本中删除。

15.5: 我无法使用 Connector/J 连接到 MySQL 服务器,但我确定连接参数是正确的。

确保 skip_networking系统变量尚未在您的服务器上启用。Connector/J 必须能够通过 TCP/IP 与您的服务器通信;不支持命名套接字。还要确保您没有通过防火墙或其他网络安全系统过滤连接。有关详细信息,请参阅 无法连接到 [本地] MySQL 服务器

15.6: 更新包含 主键FLOAT复合主键FLOAT 的表无法更新表并引发异常。

Connector/JWHERE 在 an 期间向子句添加条件以UPDATE检查主键的旧值。如果不匹配,则 Connector/J 认为这是失败条件并引发异常。

问题是提供的值和存储在数据库中的值之间的舍入差异可能意味着这些值永远不匹配,因此更新失败。该问题将影响所有查询,而不仅仅是来自 Connector/J 的查询。

为防止出现此问题,请使用不使用 FLOAT. 如果您必须在主键中使用浮点列,请使用 DOUBLEDECIMAL类型代替 FLOAT.

15.7: 我得到一个 ER_NET_PACKET_TOO_LARGE 异常,即使我想使用 JDBC 插入的二进制 blob 大小安全地低于该 max_allowed_packet大小。

这是因为中的hexEscapeBlock() 方法 com.mysql.cj.AbstractPreparedQuery.streamToBytes() 可能会使您的数据大小几乎翻倍。

15.8: 如果我收到类似于以下的错误消息,我应该怎么办:通信链接失败——最后一个发送到服务器的数据包是 X 毫秒前

一般来说,这个错误表明网络连接已经关闭。可能有几个根本原因:

  • 防火墙或路由器可能会限制空闲连接(MySQL 客户端/服务器协议不 ping)。

  • MySQL 服务器可能正在关闭超过wait_timeoutinteractive_timeout阈值的空闲连接。

尽管网络连接可能不稳定,但以下内容有助于避免出现问题:

  • 确保从连接池使用时连接有效。使用以 开头的查询 /* ping */来执行轻量级 ping 而不是完整查询。请注意,ping 的语法需要与此处指定的完全相同。

  • 最小化连接对象在执行其他应用程序逻辑时保持空闲的持续时间。

  • 如果连接已长时间闲置,则在使用连接之前明确验证连接。

  • 确保wait_timeoutinteractive_timeout设置得足够高。

  • 确保tcpKeepalive已启用。

  • 确保任何可配置的防火墙或路由器超时设置允许最长的预期连接空闲时间。

笔记

如果连接闲置了一段时间,不要指望能够毫无问题地重用连接。如果要在空闲一段时间后重用连接,请确保在重用之前明确测试它。

15.9: 为什么连接器/J 在通信失败后不重新连接到 MySQL 并重新发出语句而不是抛出异常,即使我使用了 autoReconnect连接字符串选项?

有几个原因。首先是交易完整性。MySQL 参考手册指出没有安全的方法可以在不冒连接状态或数据库状态信息损坏的风险的情况下重新连接到 MySQL 服务器。例如,考虑以下一系列语句:

conn.createStatement().execute(
  "UPDATE checking_account SET balance = balance - 1000.00 WHERE customer='Smith'");
conn.createStatement().execute(
  "UPDATE savings_account SET balance = balance + 1000.00 WHERE customer='Smith'");
conn.commit();

考虑在 to 之后与服务器的连接失败的UPDATE情况 checking_account。如果没有抛出异常,并且应用程序永远不会了解问题,它将继续执行。但是,在这种情况下,服务器没有提交第一个事务,因此会回滚。但是执行会继续下一个事务,并增加 savings_account余额增加 1000。应用程序没有收到异常,因此它继续执行,最终提交第二个事务,因为提交仅适用于在新连接中所做的更改。在这个例子中没有进行转账,而是进行了存款。

请注意,在autocommit启用的情况下运行并不能解决此问题。当 Connector/J 遇到通信问题时,无法确定服务器是否处理了当前正在执行的语句。以下理论状态同样可能:

  • 服务器没有收到该语句,因此服务器上没有进行相关处理。

  • 服务器收到语句并完整执行,但客户端未收到响应。

如果autocommit 启用运行,则无法保证在遇到通信异常时服务器上的数据状态。该语句可能已经到达服务器,也可能没有。您所知道的是,在客户端从服务器收到确认(或数据)之前,通信在某个时刻失败了。不过,这不仅会影响 autocommit语句。如果在 期间出现通信问题 Connection.commit(),就会出现通信失败之前是否在服务器上提交了事务,或者服务器是否收到了提交请求的问题。

生成异常的第二个原因是事务范围的上下文数据可能容易受到攻击,例如:

  • 临时表。

  • 用户定义的变量。

  • 服务器端准备好的语句。

当连接失败时,这些项目将丢失,并且如果连接以静默方式重新连接而不产生异常,这可能不利于应用程序的正确执行。

总而言之,通信错误生成的条件对于 Connector/J 来说可能是不安全的,可以通过静默重新连接来简单地忽略。通知申请是必要的。然后由应用程序开发人员决定在出现连接错误和失败时如何处理。

15.10: 如何将 3 字节 UTF8 与 Connector/J 一起使用?

对于 8.0.12 及更早版本:使用 3 字节 UTF8 在连接字符串中 characterEncoding=utf8设置 useUnicode=true

对于 8.0.13 及更高版本:由于没有 utfmb3可与连接选项一起使用的用作连接字符集 charaterEncoding的唯一方法排序规则(例如, ) option,强制使用字符集。有关详细信息,请参见 第 6.7 节 “使用字符集和 Unicode”utf8mb3utf8mb3utf8_general_ciconnectionCollationutf8mb3

15.11: 如何将 4 字节 UTF8 ( utf8mb4) 与 Connector/J 一起使用?

要将 4 字节 UTF8 与 Connector/J 一起使用,请将 MySQL 服务器配置为 character_set_server=utf8mb4. Connector/J 然后将使用该设置,如果 characterEncoding并且 connectionCollation尚未在连接字符串中设置。这相当于字符集的自动检测。有关详细信息,请参见 第 6.7 节 “使用字符集和 Unicode”对于 8.0.13 及更高版本:您可以使用characterEncoding=UTF-8utf8mb4即使 character_set_server在服务器上已设置为其他内容。

15.12:在插入 BLOB 时, 使用useServerPrepStmts=false和某些字符编码会导致损坏。如何避免这种情况?

使用某些字符编码(例如 SJIS、CP932 和 BIG5)时,BLOB 数据可能包含可解释为控制字符的字符,例如反斜杠、“\”。在将 BLOB 插入数据库时​​,这会导致数据损坏。为了避免这种情况,需要做两件事:

  1. 将连接字符串选项设置 useServerPrepStmtstrue

  2. 设置SQL_MODENO_BACKSLASH_ESCAPES