相信大家在使用mysql的时候,会感叹mysql非常灵活的字符集设置,可以将字符集在多个层次上设置,如server,客户端,数据库,表,以及字段上设置字符集。而往往越是灵活,越容易搞混乱,也更容易遇到各种字符集的问题。 我们可以通过设置客户端字符集,或者使用命令set names xxx等等,来处理字符集的问题。也通常认为,utf8可以搞定任何字符,容纳任何字符。如果utf8都搞不定,utf8mb4肯定可以。
本次对字符集的源码学习,来自于一次扎心的数据导入:有一部分表从db2 通过exp 命令,导出成文本文件,文本文件总共180G ,然后再通过load 命令导入到mysql.期间经历列分隔符被干扰的情况, 处理之后,发现行分割符也有被字符类型的列中的数据干扰。 将这些情况都处理之后,然后信心满满地认为,这次应该可以成功将所有数据导入了吧 。 结果,结果还是挺令人悲催,两个超级大表依然出现失败。
报错信息如图:
从db2导出设置为utf8, mysql的表也设置为utf8, 但又出现这样的幺蛾子,怎么整 ?
这肯定是跨平台的问题? 或者db2utf8跟mysql utf8的范围不一样。 xxx,表面上&嘴上没有反驳,但心里嘀咕,utf8不是统一的字符集莫? 哪里还分db2跟oracle,或者mysql.
没有办法,为了这个“服”字(让自己服),只能撸代码来找真正的答案。慢慢地寻根问底,找到了处理/验证utf8字符的函数。
staticint my_valid_mbcharlen_utf8(const CHARSET_INFO *cs MY_ATTRIBUTE((unused)), const uchar *s, const uchar *e){ uchar c; if (s >= e) return MY_CS_TOOSMALL; c= s[0]; if (c < 0xf0) return my_valid_mbcharlen_utf8mb3(s, e);#ifdef UNICODE_32BIT if (c < 0xf8 && sizeof(my_wc_t)*8 >= 32) { if (s+4 > e) /* We need 4 characters */ return MY_CS_TOOSMALL4; if (!(IS_CONTINUATION_BYTE(s[1]) && IS_CONTINUATION_BYTE(s[2]) && IS_CONTINUATION_BYTE(s[3]) && (c >= 0xf1 || s[1] >= 0x90))) return MY_CS_ILSEQ; return 4; } if (c < 0xfc && sizeof(my_wc_t)*8 >= 32) { if (s+5 >e) /* We need 5 characters */ return MY_CS_TOOSMALL5; if (!(IS_CONTINUATION_BYTE(s[1]) && IS_CONTINUATION_BYTE(s[2]) && IS_CONTINUATION_BYTE(s[3]) && IS_CONTINUATION_BYTE(s[4]) && (c >= 0xf9 || s[1] >= 0x88))) return MY_CS_ILSEQ; return 5; } if (c < 0xfe && sizeof(my_wc_t)*8 >= 32) { if ( s+6 >e ) /* We need 6 characters */ return MY_CS_TOOSMALL6; if (!(IS_CONTINUATION_BYTE(s[1]) && IS_CONTINUATION_BYTE(s[2]) && IS_CONTINUATION_BYTE(s[3]) && IS_CONTINUATION_BYTE(s[4]) && IS_CONTINUATION_BYTE(s[5]) && (c >= 0xfd || s[1] >= 0x84))) return MY_CS_ILSEQ; return 6; }#endif return MY_CS_ILSEQ;}
通过上面的代码段,我们可以看到,当文字的第一个字节小于0xf0时,才调用函数my_valid_mbcharlen_utf8mb3(s, e);进行处理3字节的utf8
(有发现没?所有报错的数据的二进制码都是以f0开头), 否则,如果定义了宏UNICODE_32BIT, 才进入4字节以上(含)字符的处理流程,如果没有定义该宏,则直接返回MY_CI_ILSEQ. 也就是无效的字符。
那采用utf8mb4是否可以呢? 答案依然是no, 这些字符都被解析成? 再后来,直接修改mysql源码文件中CmakeList.txt文件,将宏UNICODE_32BIT指定,将mysql重新编译,然后重新执行,数据成功insert. 请看这个特殊字符插入成功后的效果。
目前已经将该case 提交给mysql官方,看看能否得到官方的确认跟反馈,针对这类字符该怎么弄? bug id 88529 ,url https://bugs.mysql.com/bug.php?id=88529
有兴趣了解这个case,以及最后的处理方式,请关注这个bug更新。
对了,好像作者较少在文章末尾加入邀请各位读者关注该作者公众号的文字,这次加不加?
点击原文连接,从mysql官网了解该case的进展。