本节说明使用 MySQL Connector/J 的应用程序最常遇到的问题及其解决方法。
问题
-
16.1: 当我尝试使用 MySQL Connector/J 连接到数据库时,出现以下异常
SQLException: Server configuration denies access to data source SQLState: 08001 VendorError: 0
发生了什么?我可以用 MySQL 命令行客户端连接。
16.2: 我的应用程序抛出 SQLException '没有合适的驱动程序'。为什么会这样?
-
16.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
16.4: 我有一个 servlet/应用程序,它可以正常工作一天,然后在晚上停止工作
16.5: 我无法使用 Connector/J 连接到 MySQL 服务器,并且我确定连接参数是正确的。
16.6: 更新包含主键的表失败,该主键是 主键,要么是
FLOAT
,要么是使用FLOAT
的复合主键,更新表失败并引发异常。16.7: 我收到
ER_NET_PACKET_TOO_LARGE
异常,即使我使用 JDBC 插入的二进制 blob 大小安全地低于max_allowed_packet
大小。16.8: 如果我收到类似以下的错误消息,我该怎么办?“通信链接故障 - 发送到服务器的最后一个数据包是在 X 毫秒前”?
16.9: 为什么 Connector/J 在通信失败后不会重新连接到 MySQL 并重新发出语句,而是抛出异常,即使我使用
autoReconnect
连接字符串选项?16.10: 如何在 Connector/J 中使用 3 字节 UTF8?
16.11: 如何在 Connector/J 中使用 4 字节 UTF8 (
utf8mb4
)?16.12: 使用
useServerPrepStmts=false
和某些字符编码会导致在插入 BLOB 时出现损坏。如何避免这种情况?
问题与解答
16.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
语句来为连接添加必要的安全凭据。有关更多信息,请参见 GRANT 语句。
不正确地更改 MySQL 上的权限和许可可能会导致您的服务器安装具有非最佳的安全属性。
除非您添加 --host
标志并使用 localhost
以外的名称作为主机,否则测试与 mysql 命令行客户端的连接将不起作用。如果您使用特殊主机名 localhost
,mysql 命令行客户端将尝试使用 Unix 域套接字。如果您正在测试与 localhost
的 TCP/IP 连接,请使用 127.0.0.1
作为主机名。
16.2: 我的应用程序抛出 SQLException '没有合适的驱动程序'。为什么会这样?
此错误有三个可能的原因
Connector/J 驱动程序不在您的
CLASSPATH
中,请参见 第 4 章,Connector/J 安装。您的连接 URL 格式不正确,或者您引用了错误的 JDBC 驱动程序。
使用 DriverManager 时,
jdbc.drivers
系统属性尚未填充 Connector/J 驱动程序的位置。
16.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
您正在运行小程序,或者您的 MySQL 服务器已启用 skip_networking
系统变量,或者您的 MySQL 服务器前面有一个防火墙。
小程序只能建立到运行为小程序提供 .class 文件的 Web 服务器的机器的网络连接。这意味着 MySQL 必须运行在同一台机器上(或者您必须有某种端口重定向)才能使此方法正常工作。这也意味着您无法从本地文件系统测试小程序,而必须始终将它们部署到 Web 服务器。
Connector/J 通常使用 TCP/IP 套接字连接到 MySQL(有关例外情况,请参见 第 6.10 节,“使用 Unix 域套接字连接” 和 第 6.11 节,“使用命名管道连接”)。skip_networking
系统变量或服务器防火墙可能会影响与 MySQL 的 TCP/IP 通信。如果 MySQL 已使用 skip_networking
启用,您需要在 /etc/mysql/my.cnf
或 /etc/my.cnf
文件中将其注释掉,以使 TCP/IP 连接正常工作。(请注意,您的服务器配置文件也可能存在于 MySQL 服务器的 data
目录中或其他地方,具体取决于 MySQL 的编译方式;Oracle 创建的二进制文件始终在 /etc/my.cnf
和
中查找;有关详细信息,请参见 使用选项文件。)如果您的 MySQL 服务器已设置防火墙,则需要配置防火墙以允许从运行 Java 代码的主机到 MySQL 服务器上的监听端口(默认情况下为 3306)的 TCP/IP 连接。
datadir
/my.cnf
16.4: 我有一个 servlet/应用程序,它可以正常工作一天,然后在晚上停止工作
MySQL 在 8 小时不活动后会关闭连接。您需要使用处理陈旧连接的连接池,或者使用 autoReconnect
参数(参见 第 6.3 节,“配置属性”)。
此外,在应用程序中捕获 SQLExceptions
并进行处理,而不是一直传播到应用程序退出。这仅仅是良好的编程实践。当 MySQL Connector/J 在处理查询期间遇到网络连接问题时,它会将 SQLState
(参见 API 文档中的 java.sql.SQLException.getSQLState()
)设置为 08S01
。此时尝试重新连接到 MySQL。
以下(简单)示例展示了处理这些异常的代码可能是什么样子
示例 16.1 Connector/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
功能已弃用,可能会在将来的版本中删除。
16.5: 我无法使用 Connector/J 连接到 MySQL 服务器,并且我确信连接参数是正确的。
确保您的服务器上未启用 skip_networking
系统变量。Connector/J 必须能够通过 TCP/IP 与您的服务器通信;不支持命名套接字。还要确保您没有通过防火墙或其他网络安全系统过滤连接。有关更多信息,请参见 无法连接到 [本地] MySQL 服务器。
16.6: 更新包含 主键 的表,该主键是 FLOAT
或使用 FLOAT
的复合主键,无法更新表并引发异常。
Connector/J 在 UPDATE
期间向 WHERE
子句添加条件,以检查主键的旧值。如果不存在匹配项,则 Connector/J 将其视为失败条件并引发异常。
问题在于,提供的值与存储在数据库中的值之间的舍入差异可能意味着这些值永远不会匹配,因此更新失败。此问题会影响所有查询,而不仅仅是来自 Connector/J 的查询。
为了防止此问题,请使用不使用 FLOAT
的主键。如果您必须在主键中使用浮点列,请使用 DOUBLE
或 DECIMAL
类型来代替 FLOAT
。
16.7: 我收到 ER_NET_PACKET_TOO_LARGE
异常,即使我想要使用 JDBC 插入的二进制 Blob 大小安全地低于 max_allowed_packet
大小。
这是因为 com.mysql.cj.AbstractPreparedQuery.streamToBytes()
中的 hexEscapeBlock()
方法可能会使您的数据大小几乎翻倍。
16.8: 如果我收到类似于以下内容的错误消息,该怎么办?“通信链接故障 - 发送到服务器的最后一个数据包是在 X 毫秒前”?
一般来说,此错误表明网络连接已关闭。可能有多种根本原因
防火墙或路由器可能会对空闲连接进行限制(MySQL 客户端/服务器协议不执行 ping)。
MySQL 服务器可能会关闭超过
wait_timeout
或interactive_timeout
阈值的空闲连接。
尽管网络连接可能不稳定,但以下方法有助于避免问题
确保在从连接池中使用时连接有效。使用以
/* ping */
开头的查询来执行轻量级 ping 而不是完整查询。请注意,ping 语法必须与此处指定的一致。最大程度地减少连接对象在执行其他应用程序逻辑时处于空闲状态的持续时间。
如果连接已处于空闲状态很长时间,则在使用连接之前明确验证连接。
确保
wait_timeout
和interactive_timeout
设置得足够高。确保启用了
tcpKeepalive
。确保任何可配置的防火墙或路由器超时设置允许最大的预期连接空闲时间。
不要期望能够在连接处于空闲状态一段时间后毫无问题地重复使用连接。如果连接在处于空闲状态一段时间后要重复使用,请确保在重复使用之前明确对其进行测试。
16.9: 为什么 Connector/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();
考虑在更新 checking_account
之后连接到服务器失败的情况。如果没有引发异常,并且应用程序永远不会了解问题,它将继续执行。但是,在这种情况下,服务器没有提交第一个事务,因此该事务将回滚。但执行将继续进行下一个事务,并将 savings_account
余额增加 1000。应用程序没有收到异常,因此它继续执行,最终提交了第二个事务,因为提交只适用于新连接中做出的更改。在本例中,并非进行了转账,而是进行了存款。
请注意,启用 autocommit
并不能解决此问题。当 Connector/J 遇到通信问题时,无法确定服务器是否处理了当前正在执行的语句。以下理论状态是同样可能的
服务器从未收到该语句,因此服务器上没有发生任何相关处理。
服务器收到了该语句,并已完全执行,但客户端未收到响应。
如果您正在启用 autocommit
的情况下运行,则无法保证遇到通信异常时服务器上数据的状态。该语句可能已到达服务器,也可能没有。您只知道通信在某个时间点之前失败,这发生在客户端从服务器收到确认(或数据)之前。这不仅会影响 autocommit
语句。如果通信问题发生在 Connection.commit()
期间,就会出现问题,即事务是在通信失败之前在服务器上提交的,还是服务器根本没有收到提交请求。
生成异常的第二个原因是事务范围内的上下文数据可能容易受到攻击,例如
临时表。
用户定义的变量。
服务器端预处理语句。
连接失败时,这些项目会丢失,如果连接在不生成异常的情况下静默重新连接,这可能会对应用程序的正确执行产生不利影响。
总之,通信错误会产生可能对 Connector/J 来说不安全的条件,因为它无法通过静默重新连接来忽略这些错误。有必要通知应用程序。然后,应用程序开发人员需要决定如何在连接错误和故障的情况下继续执行。
16.10: 如何使用 Connector/J 使用 3 字节 UTF8?
由于没有用于 utfmb3
的 Java 风格字符集名称,您可以使用连接选项 charaterEncoding
,因此使用 utf8mb3
作为连接字符集的唯一方法是使用 utf8mb3
排序规则(例如,utf8_general_ci
)用于连接选项 connectionCollation
,这会强制使用 utf8mb3
字符集。有关详细信息,请参见 第 6.7 节,“使用字符集和 Unicode”。
16.11: 如何使用 Connector/J 使用 4 字节 UTF8 (utf8mb4
)?
要使用 Connector/J 使用 4 字节 UTF8,请使用 character_set_server=utf8mb4
配置 MySQL 服务器。如果连接字符串中没有设置 characterEncoding
和 connectionCollation
,则 Connector/J 会使用该设置。这等同于自动检测字符集。有关详细信息,请参见 第 6.7 节,“使用字符集和 Unicode”。您可以使用 characterEncoding=UTF-8
来使用 utf8mb4
,即使服务器上的 character_set_server
已设置为其他内容。
16.12: 使用 useServerPrepStmts=false
和某些字符编码会导致在插入 BLOB 时出现损坏。如何避免这种情况?
使用某些字符编码(例如 SJIS、CP932 和 BIG5)时,有可能 Blob 数据包含可以解释为控制字符的字符,例如反斜杠“\”。这会导致在将 Blob 插入数据库时数据损坏。要避免这种情况,需要做两件事
将连接字符串选项
useServerPrepStmts
设置为true
。将
SQL_MODE
设置为NO_BACKSLASH_ESCAPES
。