论坛首页 综合技术论坛

让MySQL支持欧元字符

浏览 2585 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-11-14   最后修改:2010-11-14

本文说明一下MySQL中不支持欧元字符的原因及解决方法。

 

 

1、              问题描述

mysql中插入的字符串中若包含欧元字符(),会发现该字符及以后的字符串都变得“不可见”。实际上这里并非不可见,而是根本没有进入数据库中。简单描述步骤如下:

root@test 05:15:41>create table t(c char(32)) engine=innodb charset=gbk;

Query OK, 0 rows affected (0.01 sec)

 

root@test 05:16:29>insert into t values(concat('a', char(128), 'b'));

Query OK, 1 row affected, 1 warning (0.00 sec)

 

root@test 05:17:04>select * from t;

+------+

| c    |

+------+

| a    |

+------+

 

 

可以看到,我们试图插入三个字符(a, €, b) 但在查询中发现只有一个字符a.

Insert的一个warning内容为 Incorrect string value: '\x80b' for column 'c' at row 1

说明在插入0x80(€) 时出错,导致后面的字符也一起被丢弃.

 

2、              原因分析

我们知道,在mysql将语句中设置的值传给引擎时,需要先拷贝到一个临时结构中。但拷贝字符串只会以\0结束,0x80对于字符串拷贝过程应该是一个正常的字符。

 

拷贝函数

于是我们找到做值拷贝的过程, sql/sql_string.ccwell_formed_copy_nchars中,对应的两行代码如下

 

 

res= to_cs->cset->well_formed_len(to_cs, from, from + from_length, 

                                        nchars, &well_formed_error);

      memmove(to, from, res);

 

 

可以看到,拷贝内容时,先计算了可拷贝的长度,然后在用memmove. 调试发现,在我们上面的试验中,返回的res=1 因此memmove就只拷贝了一个字符(a).

 

 

计算长度

于是问题出在这个to_cs->cset->well_formed_len中。

由于MySQL要支持多种字符编码,因此定义了MY_CHARSET_HANDLER (include/m_ctype.h)中,这个结构体定义了各种编码下的特定函数接口。对应的实现实体在strings/ctyp-*.c中。

我们的例子中用到的是gbk,因此看strings/ctype-gbk.c 上述计算长度的函数,实际上调用的是本文件中的my_well_formed_len_gbk

 

 

 

static
size_t my_well_formed_len_gbk(CHARSET_INFO *cs __attribute__((unused)),
                              const char *b, const char *e,
                              size_t pos, int *error)
{ 
  const char *b0= b;
  const char *emb= e - 1; /* Last possible end of an MB character */

  *error= 0;
  while (pos-- && b < e)
  { 
    if ((uchar) b[0] <= 128)
    { 
      /* Single byte ascii character */
      b++;
    }
    else  if ((b < emb) && isgbkcode((uchar)*b, (uchar)b[1]))
    { 
      /* Double byte character */
      b+= 2;
    }
    else
    { 
      /* Wrong byte sequence */
      *error= 1;
      break;
    }
  }
  return (size_t) (b - b0);
}

   

第12行是对于英文字符的处理,第17行是对于中文字符的处理,其他的直接到第22行后判为error。我们看到,0x80就到了*error=1, 然后break

 

 

3、              修改和小结

实际上欧元字符也可以作为单字节处理, 将上面代码的第12行改为if ((uchar) b[0] <= 128) 重新编译后执行结果如下

mysql> insert into t values(concat('a', char(128), 'b'));

Query OK, 1 row affected (0.00 sec)

 

insert select hex(c) from t;

+--------+

| hex(c) |

+--------+

| 618062 |

+--------+

欧元字符在笔者客户端内显示不正常,但从hex(c)中看到三个字符已经正常存储在mysql中。 

其他字符编码也有此问题,都修改相应的string/ctype-*.c中的my_well_formed_len*即可。

   发表时间:2010-11-15  
需要这么复杂吗?
将表设置为unicode存储不就完事了
0 请登录后投票
   发表时间:2010-11-15   最后修改:2010-11-16
murusu 写道
需要这么复杂吗?
将表设置为unicode存储不就完事了

是说utf8吗

可以试下 也有问题

而且在原有数据上改表编码需要重做数据


有别的方法也可以提出下

不过这个只是提供一种解决思路而已
0 请登录后投票
   发表时间:2010-11-16  
把表的编码设置成unicode,然后插入没有任何问题的,已经测试过可以正常插入。
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics