5

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

展示

我们最近遇到了一个问题,即内容将U+00a0(不间断空格)字符插入到带有latin1字符集的 DB 列中。只需在SELECT列中打印出“”。我不确定这是选择的产物还是显示器的产物,但我相信是前者。 SELECT BINARY col而是打印出“”,因为我的外壳有$LANG = en_US.utf8.

一个更明显的例子是“â„¢”与“™”

使用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列上的 实际存储/显示有什么作用?
4

2 回答 2

3

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

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

首先,我们记得在 MySQL latin1 中真正的意思是 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 字节产生的字符: â „ ¢
    • “带抑扬符的拉丁小写字母 a”、“双低 9 引号”、“分号”
    • 注意:Latin-1 和 Unicode 根本没有对 Windows-1252 定义为“双低 9 引号”的十六进制字节 84 进行解码。Unicode 在远离那里的代码点 U+201E 处对“双低 9 引号”进行编码。
  • 将不间断空格 UTF-8 十六进制字节解释为 Windows 1252 字节产生的字符: Â [不间断空格]
    • “带有抑扬符的拉丁大写字母 a”、“不间断空格”
  • 将商标符号 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 字节。

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

于 2013-04-25T17:47:32.303 回答
1

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

E284A2

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

99

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

通过连接与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。(但是,这有点猜测。我不完全确定“不经意间正确”的转换发生在什么时候。)

当然,MySQL99以类似但相反的方式友好地处理拉丁文。

于 2013-04-25T18:53:43.400 回答