mysqli_set_charset不一定靠谱

起因来自最近测试MySQL的宽字符支持,也就是使用4字节存储,但是其utf8编码单字符应该最多只支持三Bytes。具体可以参考https://dev.mysql.com/doc/refman/5.7/en/charset-unicode-utf8.html,这里需要明确一下MySQL的utf8和标准的UTF-8在拼写上都是不一样的,标准的定义是:

Unicode Transformation Format with 8-bit units

按照MySQL的文档写的,以下语言分别用到这些个bytes位数,

  • Basic Latin letters, digits, and punctuation signs use one byte.单个的拉丁字母、数字、英文符号
  • Most European and Middle East script letters fit into a 2-byte sequence: extended Latin letters (with tilde, macron, acute, grave and other accents), Cyrillic, Greek, Armenian, Hebrew, Arabic, Syriac, and others.主要是一些单词
  • Korean, Chinese, and Japanese ideographs use 3-byte or 4-byte sequences.韩语、中文、日语等。

而MySQL的utf8默认是3-Byte UTF-8 Unicode Encoding,它也有个别名就叫The utf8mb3 Character Set。问题这时候就出来了,我们要想MySQL能支持4bytes的存储,那么编码就不能使用utf8了,其实MySQL也是支持4bytes编码的,但这个时候字符集不叫utf8了换了个名字叫utf8mb4。英文叫4-Byte UTF-8 Unicode Encoding。

继续测试,我们配置了MySQL服务端,让它支持了这个4位的编码utf8mb4,但是从客户端连接却惊奇的发现还是不支持,当时我是震惊了,后来一想我们用了MySQL的代理是不是代理不支持呢,然后从原生的环境连接代理发现可以写进去,那问题就是我们改过的非原生环境了。(我们的原生环境分为php5.3的版本和php5.6的版本)。

这么一看就是MySQL客户端的问题,没想到问题还会出现在它身上,之后一路寻找,发现使用了mysqli_set_charset这个函数,但是却并没有成功的设置编码。我们找到php的源码,这么写的

PHP_FUNCTION(mysqli_set_charset)
{
        MY_MYSQL        *mysql;
        zval            *mysql_link;
        char            *cs_name;
        size_t          csname_len;

        if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &mysql_link, mysqli_link_class_entry, &cs_name, &csname_len) == FAILURE) {
                return;
        }
        MYSQLI_FETCH_RESOURCE_CONN(mysql, mysql_link, MYSQLI_STATUS_VALID);

        if (mysql_set_character_set(mysql->mysql, cs_name)) {
                RETURN_FALSE;
        }
        RETURN_TRUE;
}

可以看到直接调用了libmysql的mysql_set_character_set,PS我们的MySQL的驱动是libmysql,然后就看mysql_set_character_set了,函数原型是int mysql_set_character_set(MYSQL *mysql, char *csname),这样的该函数用于为当前连接设置默认的字符集。字符串csname指定了1个有效的字符集名称。连接校对成为字符集的默认校对。该函数的工作方式与SET NAMES语句类似,但它还能设置mysql->charset的值,从而影响了由mysql_real_escape_string()设置的字符集。该函数是在MySQL 5.0.7中增加的。该函数0表示成功,非0值表示出现错误。

从测试的代码中打印出php调用的结果却是是false,那是为啥呢?还是看下实现吧https://github.com/mysql/mysql-server/blob/09ddec8757b57893ccd2f2c2482b3eec5ca811e5/sql-common/client.c#L5898这时候具体寻找的细节我就不展开说了,但是值得一说的是说明文件中有支持的所有编码,如果是个rpm包的方式安装,可以像这么执行

rpm -ql mysql-libs-5.1.73-3.el6_5.x86_64

出来所有的文件要寻找的是/usr/share/mysql/charsets/Index.xml这样子的,这个xml里面有libmysql客户端支持的所有编码(在你安装的版本下),这时候我们vim打开以下,查找。先找个utf8试试,果然是有的

<charset name="utf8">
  <family>Unicode</family>
  <description>UTF-8 Unicode</description>
  <alias>utf-8</alias>
  <collation name="utf8_general_ci"     id="33">
   <flag>primary</flag>
   <flag>compiled</flag>
  </collation>
  <collation name="utf8_bin"            id="83">
    <flag>binary</flag>
    <flag>compiled</flag>
  </collation>
</charset>

再找utf8mb4,果然找不到

所以看到这时候是libmysql客户端的问题,怎么办呢,那就乖乖用SET NAMES 执行编码了, 看到别人写了好多文章巴拉巴拉还说什么优劣的,我觉得没啥区别,反而set names在通用环境下更合适,反正就告诉服务端我给你什么编码,你也给我返回什么编码就好了。

  1. 注册立送88元᧖
    功夫
    易发
    鸿运国际
    澳门财神
    皇都娱乐城
    澳门VIP
    博伊德
    加Q:1813712617