1. Mysql如何支持UTF8?
端配置
原来mysql支持的 utf8 编码最大字符长度为 3 字节,如果遇到 4 字节的宽字符就会插入异常了。
Mysql从5.5.3开始支持,通过utf8mb4(UTF-8most bytes 4)字符集支持4-byte的UTF8字符。
对utf8mb4的支持
在服务端支持utf8mb4之后,JDBC客户端也相应的进行了升级。从笔者最近的实践来看,建议使用5.1.47以上版本。
官方对JDBC驱动的说明如下:
JDBC client与Mysqlserver默认是自动进行检测的。如果服务器端指定了character_set_server变量, 则 JDBC 驱动会自动使用该字符集(在不指定 JDBC URL 参数characterEncoding和connectionCollation的情况下)。 可以通过characterEncoding (该参数值是使用 Java 风格的形式指定. 例如 UTF-8 )来进行手工指定, 而不是自动检测。 为了在 MySQL JDBC 驱动版本 5.1.46 及之前的版本中使用 utf8mb4, 则服务器端必须配置character_set_server=utf8mb4, 否则JDBC URL参数characterEncoding=UTF-8 表示的是 MySQL 的 utf8, 而不是 utf8mb4。 |
2. Mysql Server不重启无法使用utf8mb4的分析
然而,在笔者进行测试过程中发现,不同版本JDBC驱动在Mysql Server设置了字符集参数“重启/不重启”不同情况下,能否支持utf8mb4有不同的表现。
重启与否,不同JDBC版本的表现
Mysql JDBC客户端(MysqlConnector-j)在不同的版本中对字符集的支持有一定差异。版本的分界线在5.1.46和5.1.47。
测试过程中,在重启Server情况下字符集都可以生效,而不重启Server的情况下只有5.1.47在客户端设置了字符集情况下才生效。具体情况如下表:
JDBC客户端 | Mysql Server | ||
版本 | characterEncoding参数 | 不重启 | 重启 |
5.1.46 | UTF-8 | × | √ |
未设置 | × | √ | |
5.1.47 | UTF-8 | √ | √ |
未设置 | × | √ |
是否重启,到底会影响什么?
官方文档中提到,Server端的character_set_server=utf8mb4设置完成后,客户端如果没有配置“characterEncoding”会使用服务端配置的utf8mb4字符集。
那为什么Mysql Server重启和不重启,会对字符集有影响呢?
MysqlIO.serverCharsetIndex的使用
从JDBC驱动的源码中可以看到,在com.mysql.jdbc.ConnectionImpl类的configureClientCharacterSet()设置字符集方法中用到了MysqlServer返回的服务端字符集,该字符集参数存储于”io”成员变量的”serverCharsetIndex”属性中。
|
MysqlIO.serverCharsetIndex的获取
对于serverCharsetIndex的赋值,是在com.mysql.jdbc.MysqlIO.doHandshake()方法中。
} |
从该方法的名称即可发现,在JDBC客户端与Mysqlserver进行握手通讯的时候,已经完成了server相关信息的获取。
通过wireshark抓取到的交互报文如下:
通过JDBC报文规范,解析后的报文内容如下,可以看到在未重启Mysql Server的情况下,返回的“character set”还是“33”,即“utf8”。
字段 | 取值 | 报文 |
protocolVersion | 10 | 0a |
serverVersion | 5.7.18-log | 35 2e 37 2e 31 38 2d 6c 6f 67 00 |
threadId | 4751059 | d3 7e 48 00 |
auth-plugin-data-part | mDv>JkJ | 05 6d 44 76 3e 4a 6b 4a |
filler ([00]) | 00 | |
serverCapabilities | 63487 | ff f7 |
character set | 33 | 21 |
serverStatus | 2 | 02 |
“33”映射为“utf8”,在“com.mysql.jdbc.CharsetMapping”类中指定的字符集映射,源码如下:
|
2.2.3. Mysql不重启,为什么返回报文中还是utf8?
MysqlServer 在执行send_server_handshake_packet()方法中,返回给客户端的字符集从“default_charset_info”变量中获取。
static boolsend_server_handshake_packet(MPVIO_EXT *mpvio, const char *data, uintdata_len) { int2store(end, mpvio->client_capabilities); /* write server characteristics: up to 16 bytes allowed */ end[2]= (char) default_charset_info->number; int2store(end + 3, mpvio->server_status[0]); } |
default_charset_info仅在MySQL Server启动的时候进行初始化使用,其值为 character-set-server 的参数值。修改正在运行的数据库的编码并不会触发 default_charset_info 的更新, 返回给客户端协议包中的编码就还是以前的编码。
使用5.1.46及之前版本、不重启Mysql Server的解决方案
应用如果使用JDBC的5.1.46以及之前版本,由于种种原因无法重启Mysql Server的情况下同时又不升级JDBC驱动到5.1.47的情况下,如果需要支持utf8mb4,则可以在JDBC链接字符串中添加“com.mysql.jdbc.faultInjection.serverCharsetIndex=45”,直接指定“服务器字符集”。
具体参数设置如下:
|
为什么设置为“45”,则是在“com.mysql.jdbc.CharsetMapping”类中指定的字符集映射,源码如下:
|
当指定了该参数后,com.mysql.jdbc.ConnectionImpl的configureClientCharacterSet()方法会覆盖从Mysql server获取到的字符集,具体源码如下:
} |
3. 源码解析,解密JDBC不同版本的区别
官方升级说明
官方升级说明中强调,只要JDBC链接字符串中指定了“characterEncoding=UTF-8
”,即使MysqlServer设置了其它字符集,客户端也会使用utf8mb4。
Functionality Added or Changed
|
字符集设置源码解析
5.1.46中,通过Mysql服务器返回的“charset”设置是否使用”utf8mb4”字符集,可参考如下流程图:
源码参考com.mysql.jdbc.ConnectionImpl类的configureClientCharacterSet()方法,如下所示:
if (getUseUnicode()) { //1.如果JDBC链接字符串指定了”characterEncoding=UTF-8”,根据Mysql Server返回的字符集确定是使用utf8或者utf8mb4 if (realJavaEncoding != null) { // Now, inform the server what character set we will be using from now-on... if (realJavaEncoding.equalsIgnoreCase("UTF-8") || realJavaEncoding.equalsIgnoreCase("UTF8")) { boolean utf8mb4Supported = versionMeetsMinimum(5, 5, 2); Boolean useutf8mb4 = utf8mb4Supported && (CharsetMapping.UTF8MB4_INDEXES.contains(this.io.serverCharsetIndex)); } } else if (getEncoding() != null) { //2.如果JDBC链接字符串未指定”characterEncoding”参数,则会使用Mysql Server返回字符集 String mysqlCharsetName = getServerCharset(); if (getUseOldUTF8Behavior()) { mysqlCharsetName = "latin1"; } } } |
字符集设置源码解析
5.1.47中,如果JDBC链接字符串中指定了”characterEncoding=UTF-8”,则会默认使用utf8mb4字符集,不使用server返回的字符集属性;否则,未指定使用server返回字符集。
源码参考com.mysql.jdbc.ConnectionImpl类的configureClientCharacterSet()方法,如下所示:
if (getUseUnicode()) { //1.如果JDBC链接字符串指定了”characterEncoding=UTF-8”,则会默认使用utf8mb4字符集 ` (realJavaEncoding != null) { // Now, inform the server what character set we will be using from now-on... if (realJavaEncoding.equalsIgnoreCase("UTF-8") || realJavaEncoding.equalsIgnoreCase("UTF8")) { // charset names are case-sensitive boolean utf8mb4Supported = versionMeetsMinimum(5, 5, 2); String utf8CharsetName = connectionCollationSuffix.length() > 0 ? connectionCollationCharset : (utf8mb4Supported ? "utf8mb4" : "utf8"); } } else if (getEncoding() != null) { //2.如果JDBC链接字符串未指定”characterEncoding”参数,则会使用Mysql Server返回字符集 // Tell the server we'll use the server default charset to send our queries from now on.... String mysqlCharsetName = connectionCollationSuffix.length() > 0 ? connectionCollationCharset : (getUseOldUTF8Behavior() ? "latin1" : getServerCharset()); } } |
4. 其他字符集问题
字符集参数该使用utf8,UTF8,utf-8,UTF-8中的哪个?
在JDBC链接字符串中,通过“characterEncoding”设置字符集,那么我们应该选择“utf8、UTF8、utf-8、UTF-8”中的哪一个?
实际上,上述4种设置方式都可以。
在JDBC的源码“com.mysql.jdbc.ConnectionImpl.configureClientCharacterSet()”方法中,对这四种配置方式都进行了兼容。
|
设置为utf8mb4为什么报错?
有人会尝试将JDBC链接字符串“characterEncoding”设置为“utf8mb4”,以此来支持UTF8,却收获了如下报错:
|
其实,在JDBC的“com.mysql.jdbc.ConnectionPropertiesImpl”类中,对配置的字符集通过“StringUtils.getBytes(testString, testEncoding)”进行了检查,代码如下:
} |
而com.mysql.jdbc.StringUtils最终调用了java标注类库里“java.nio.charset.Charset”类的findCharset方法,“Charset.forName(alias)”方法无法找到“utf8mb4”。
|
通过如下代码可以打印系统支持的字符集:
|
在windows 64位操作系统,jdk8中执行后获得字符集如下:
GB2312 GBK IBM-Thai IBMxxxxxx ISO-2022-xx ISO-8859-xxx JIS_X0201 JIS_X0212-1990 KOI8-R KOI8-U | Shift_JIS TIS-620 US-ASCII UTF-16 UTF-16BE UTF-16LE UTF-32 UTF-32BE UTF-32LE UTF-8 | Big5 Big5-HKSCS CESU-8 EUC-JP EUC-KR GB18030 windows-xxxx x-Big5-HKSCS-2001 x-Big5-Solaris x-euc-jp-linux x-EUC-TW x-eucJP-Open x-IBMxxxxx | x-ISCII91 x-ISO-2022-CN-CNS x-ISO-2022-CN-GB x-iso-8859-11 x-JIS0208 x-JISAutoDetect x-Johab x-MacArabic x-Macxxxxxxxx x-MS932_0213 x-MS950-HKSCS x-MS950-HKSCS-XP | x-mswin-936 x-PCK x-SJIS_0213 x-UTF-16LE-BOM X-UTF-32BE-BOM X-UTF-32LE-BOM x-windows-50220 x-windows-50221 x-windows-874 x-windows-949 x-windows-950 x-windows-iso2022jp |