问题说明
业务数据库实例的编码由 utf8 修改为 utf8mb4 后, java 业务插入表情符等宽字符(4 字节)的时候一直报错以下相关的错误:
### Cause:java.sql.SQLException:Incorrect string value:\xF0\x9F\x98\x8E for column nick_name at row 1 ;uncategorized SQLException for SQL[]; SQL state [HY000]; error code[1366];Incorrect string value: \xF0\x9F\x98\x8E for column nick_name at row 1
程序及数据库运行的版本及环境如下所示:
Centos 7.6 kernel-3.10.0-957.1.3.el7.x86_64 mysql-connector-java-5.1.46 Percona-Server-5.6.38-rel83.0-Linux
测试环境中使用同样的 mysql-connector-java
版本, 程序可以正常插入. 所不同的是测试环境修改完编码后重启了 MySQL 服务, 线上环境仅做以下修改, 重启程序而不重启 MySQL 服务:
set global character_set_client = utf8mb4; set global character_set_connection = utf8mb4; set global character_set_database = utf8mb4; set global character_set_results = utf8mb4; set global character_set_server = utf8mb4; set global collation_server = utf8mb4_general_ci; set global collation_database = utf8mb4_general_ci; set global collation_connection = utf8mb4_general_ci;
jdbc 配置说明
参考 connector-j-reference-charset 可以看到如果程序要插入 utf8mb4 字符, 需要满足以下条件:
Connector/J 5.1.47 及以上版本: 1. 指定 characterEncoding 参数为 UTF8/UTF-8 即可, 新版本直接映射到 utf8mb4 编码; 2. 如果 connectionCollation 指定的排序规则不是 utf8mb4 相关的, 则 characterEncoding 参数会重写为排序规则对应的编码; Connector/J 5.1.47 以下版本: 1. 设置 MySQL 参数变量 character_set_server=utf8mb4; 2. 指定 characterEncoding 参数为 UTF8/UTF-8, jdbc 程序会进行探测是否使用 utf8mb4;
所以对于 mysql-connector-java
版本来讲, 我们的条件已经满足, 不过还是插入失败. 另外 characterEncoding
参数的值只可以指定 connector-j-reference-charset 链接中 Table 5.3
提到的编码名, 指定其余的编码名, jdbc 在建立连接的时候就是失败报错.
问题分析说明
mysql-connect-java 如何处理编码
满足了官方文档中的条件还是插入失败, 而使用 python, perl 等脚本程序却可以正常插入 utf8mb4 字符, 这点很让人迷惑. 我们参考 mysql-connector-java-5.1.46
的源程序可以看到以下代码:
//src/com/mysql/jdbc/ConnectionImpl.java 1616 private boolean configureClientCharacterSet(boolean dontCheckServerMatch) throws SQLException { 1617 String realJavaEncoding = getEncoding(); ...... 1689 if (realJavaEncoding != null) { 1690 1691 // 1692 // Now, inform the server what character set we will be using from now-on... 1693 // 1694 if (realJavaEncoding.equalsIgnoreCase("UTF-8") || realJavaEncoding.equalsIgnoreCase("UTF8")) { 1695 // charset names are case-sensitive 1696 1697 boolean utf8mb4Supported = versionMeetsMinimum(5, 5, 2); 1698 boolean useutf8mb4 = utf8mb4Supported && (CharsetMapping.UTF8MB4_INDEXES.contains(this.io.serverCharsetIndex)); 1699 1700