解释charset如何影响存储/显示

时间:2013-04-25 17:16:58

标签: mysql character-encoding

我觉得令人愤怒的是我还不明白这一点,但也许一些解释会有所帮助。这是一个两部分的问题,但希望这两个部分都很小且直接相关:

显示

我们最近遇到了一个问题,即内容将U+00a0(不间断空格)字符插入到具有latin1字符集的数据库列中。只需执行SELECT就会在列中打印出“”。我不确定这是选择还是显示器的产品,但我相信它是前者。 SELECT BINARY col代替打印出“”,因为我的shell有$LANG = en_US.utf8

一个更明显的例子是“┢“vs.”™“

使用SELECT CONVERT(col USING utf8)仍打印出“”和“┢“ - 我不一定会指望它做的不同,但问题源于何处?这是在存储时发生的问题吗?有没有办法让UTF8显示器从数据库中取出而不是依靠UI来正确显示它(如果这有意义的话?)

存储

为了尝试自己重现这个问题,我做了以下几点:

CREATE TABLE chrs (
    lat varchar(255) charset latin1,
    utf varchar(255) charset utf8
);
INSERT INTO chrs VALUES ('™', '™');
INSERT INTO chrs VALUES (' ', ' '); -- U+00a0

然而,这导致:

> SELECT * FROM chrs;
+------+------+
| lat  | utf  |
+------+------+
| ™    | ™    |
|      |      |
+------+------+

我希望lat能显示“”和“â”,“所以显然我有些不明白。

更重要的是:

 > SELECT BINARY lat, BINARY utf FROM chrs;
+------------+------------+
| BINARY lat | BINARY utf |
+------------+------------+
| �           | ™          |
| �           |            |
+------------+------------+

这表示值未正确存储(?)到lat

我注意到SELECT @@character_set_clientutf8,因此我将其更改为latin1并再次插入空格,但这会产生

|     |     |
两个列。 SELECT BINARY lat正确显示空格,但SELECT binary utf8仍打印出“”。我希望utf8列可以正常使用更多

总结:

  • 插入时,MySQL实际上对字符做了什么?它是否依赖于列charset,客户端集,两者或其他什么?
  • 由于上述不匹配,是否有可能在插入时搞砸数据?或者始终可以恢复最初插入的数据吗?
  • 关于存储/显示,列上的charset实际是什么?

2 个答案:

答案 0 :(得分:3)

简而言之,您的数据库似乎没问题,除非您通过将[@@ character_set_client]从[utf8]更改为[latin1]来明确告诉它行为异常。否则,我认为你在使用UTF-8和Windows-1252的软件组件之间看到了分歧的影响。

我们如何理解正在发生的事情?

首先,我们回想一下in MySQL latin1 really means Windows-1252,这是一种与“Latin-1”本身略有不同的编码,也称为ISO / IEC 8859-1。

现在让我们考虑以下有关商标标志和不间断空间的数据:

  • 字符:“商标标志”
  • Unicode点:U + 2122
  • UTF-8十六进制字节:E2 84 A2
  • Latin-1(ISO 8859-1)十六进制字节:此编码中没有此字符的代码
  • Windows 1252十六进制字节:8D

  • 字符:“不间断的空间”

  • Unicode点:U + 00A0
  • UTF-8十六进制字节:C2 A0
  • Latin-1(ISO 8859-1)十六进制字节:A0
  • Windows 1252十六进制字节:A0

出现问题的各种方法:

  • 解释商标符号UTF-8十六进制字节为Windows 1252字节时产生的字符:¢
    • “latin small letter a with circumflex”,“double low-9 quot quot”,“cent sign”
    • 注意:对于Windows-1252定义为“double low-9 quot quot mark”的十六进制字节84,Latin-1和Unicode根本没有解码。 Unicode在远离那里的代码点编码“double low-9 quot quot”,U + 201E。
  • 将非破坏空间UTF-8十六进制字节解释为Windows 1252字节所产生的字符:[不间断空格]
    • “latin large letter a with circumflex”,“non-breaking space”
  • 将商标符号Windows-1252十六进制字节解释为UTF-8字节所产生的字符:[无字符:显示平台的缺失字符标记,通常是问号标记的变体]

当您插入时,您的数据库会将商标符号存储为“latin1”作为十六进制字节8D,将“UTF-8”存储为十六进制字节E2 A4 A2。它将“latin1”中的非中断空间存储为十六进制字节“A0”,将UTF-8中的非中断空间存储为十六进制字节C2 A0。当您以交互方式执行常规SELECT时,“latin1”商标符号首先转换为Unicode点U + 2122,然后转换为UTF-8十六进制字节E2 84 A2,最终可能会被误解为它们是Windows-1252字节。

在哪里可以找到上面显示的关于字符的数据:

答案 1 :(得分:1)

如果链中的每个字符切换都支持UTF8,则该字符应存储为UTF8字段中的3个字节,其十六进制为:

E284A2

并且,在latan1字段中,为1字节,其十六进制为:

99

但是,您的客户端和连接在正确存储字符并将其显示为 as-stored 方面起着关键作用。

通过latin1连接与latin1客户端连接,我创建并插入了两行。更改为utf8客户端/连接并重新插入。结果如下:

从我的latin1连接中选择:

mysql> select *, hex(lat), hex(utf) from chrs;
+------+------+----------+----------------+
| lat  | utf  | hex(lat) | hex(utf)       |
+------+------+----------+----------------+
| ™  | ™  | E284A2   | C3A2E2809EC2A2 |
|      |      | 20       | 20             |
| ?    | ?    | 99       | E284A2         |
|      |      | 20       | 20             |
+------+------+----------+----------------+

从我的utf8连接中选择:

mysql> select *, hex(lat), hex(utf) from chrs;
+---------+---------+----------+----------------+
| lat     | utf     | hex(lat) | hex(utf)       |
+---------+---------+----------+----------------+
| â„¢     | â„¢     | E284A2   | C3A2E2809EC2A2 |
|         |         | 20       | 20             |
| ™       | ™       | 99       | E284A2         |
|         |         | 20       | 20             |
+---------+---------+----------+----------------+

在我看来,这里最令人困惑的行为是C3A2E2809EC2A2以某种方式在从latin1客户端和连接中选择时正确呈现。但是,考虑到该字段是UTF8,MySQL毫无疑问会将每组3个字节转换为相应的latin1字节进行传输,从而通过连接发送E284A2。而我的终端恰好将这三个字节解释为UTF8。 (但是,这有点推测。我不完全确定在哪里发生“无意中正确”的转换。)

当然,MySQL以类似但相反的方式处理拉丁语99