我可以安全地使用带有 utf8 列的 utf8mb4 连接吗?

Posted

技术标签:

【中文标题】我可以安全地使用带有 utf8 列的 utf8mb4 连接吗?【英文标题】:Can I safely use a utf8mb4 connection with utf8 columns? 【发布时间】:2016-04-08 07:41:09 【问题描述】:

我有一些带有 utf8mb4 字段的 mysql 表,还有一些带有 utf8 字段的 MySQL 表。

在所有表的 PDO 连接字符串中使用 utf8mb4 是否安全?还是我必须将所有内容都转换为 utf8mb4,或者启动两个不同的 PDO 连接?


编辑:问题不是 “我可以将 4 字节字符存储到 utf8 列中吗?” 我们已经知道我们不能,这不取决于连接,所以如果一个列是 utf8,这意味着它不会接收 4 字节字符,例如国家或货币代码、电子邮件地址、用户名……输入由应用程序验证。

【问题讨论】:

问题含糊不清。您是否会存储 4 字节 utf8mb4 字符,例如 ???????????? ? 【参考方案1】:

这可以很容易地使用以下脚本进行测试:

<?php

$pdo = new PDO('mysql:host=localhost;dbname=test', 'test', '');

$pdo->exec("
    drop table if exists utf8_test;
    create table utf8_test(
        conn varchar(50) collate ascii_bin,
        column_latin1  varchar(50) collate latin1_general_ci,
        column_utf8    varchar(50) collate utf8_unicode_ci,
        column_utf8mb4 varchar(50) collate utf8mb4_unicode_ci
    );
");

$latin = 'abc äŒé';
$utf8  = '♔♕';
$mb4   = '? ?';

$pdo->exec("set names utf8");

$pdo->exec("
    insert into utf8_test(conn, column_latin1, column_utf8, column_utf8mb4)
     values ('utf8', '$latin', '$latin $utf8', '$latin $utf8 $mb4')
");

$pdo->exec("set names utf8mb4");

$pdo->exec("
    insert into utf8_test(conn, column_latin1, column_utf8, column_utf8mb4)
     values ('utf8mb4', '$latin', '$latin $utf8', '$latin $utf8 $mb4')
");

$result = $pdo->query('select * from utf8_test')->fetchAll(PDO::FETCH_ASSOC);

var_export($result);

结果如下:

array (
  0 => 
  array (
    'conn' => 'utf8',
    'column_latin1' => 'abc äŒé',
    'column_utf8' => 'abc äŒé ♔♕',
    'column_utf8mb4' => 'abc äŒé ♔♕ ???? ????',
  ),
  1 => 
  array (
    'conn' => 'utf8mb4',
    'column_latin1' => 'abc äŒé',
    'column_utf8' => 'abc äŒé ♔♕',
    'column_utf8mb4' => 'abc äŒé ♔♕ ? ?',
  ),
)

如您所见,当我们使用utf8mb4 列时,我们不能使用utf8 作为连接字符集(请参阅????)。但是在使用utf8 列时,我们可以使用utf8mb4 进行连接。写入和读取latinascii 列也没有问题。

原因是您可以在utf8mb4 中编码任何utf8latinascii 字符,但反之则不行。所以在这种情况下使用utf8mb4作为连接字符集是安全的。

【讨论】:

谢谢保罗,我通过这样做发现了同样的情况。但是由于我必须为企业项目评估这个,我不能依赖测试:正如我在赏金中所说,我正在“寻找来自可靠和/或官方来源的答案。”,其中“官方”是关键字. @Dr.GianluigiZaneZanetini - 什么比可重复的测试更可信?您甚至可以使用所有 utf8(3 字节)字符运行测试。 更可信的是官方文档说明了这一点。 “让我们试试看会发生什么”的问题是你总是会错过一些测试用例。 @Dr.GianluigiZaneZanetini - 我怀疑你会找到类似“使用字符集 X 进行连接和字符集 Y 存储数据是安全的,如果你只使用来自是”。他们需要为每对字符集编写它。你甚至不会找到一个声明,它告诉你可以安全地使用 utf8mb4 连接来获取 utf8mb4 数据。 (不-这不是错字-我的意思是相同的字符集)您需要解释文档。我的解释是我回答的最后一句话。阅读Chapter 10 - 完全。【参考方案2】:

简短回答: 是的,如果您只使用 3 字节(或更短)的 UTF-8 字符。

或者... ,如果您打算使用 4 字节 UTF-8 字符,例如 ???。

长答案:

(我将解释为什么“不”可能是正确答案。)

连接确定客户端正在使用什么编码。

列上的CHARACTER SET(或默认情况下,来自表)确定可以将什么编码放入列中。

CHARACTER SET utf8utf8mb4 的子集。也就是说,utf8(通过连接或列)可以接受的所有字符都可以为utf8mb4 接受。换句话说,MySQL 的utf8mb4(与外界的UTF-8 相同)具有完整的 4 字节 utf-8 编码,比 MySQL 最多 3 字节的 utf8 包含更多的 Emoji、更多的中文等(又名“BMP”)

(从技术上讲,utf8mb4 最多只能处理 4 个字节,但UTF-8 可以处理更长的字符。但是,我怀疑在我的一生中是否会出现 5 个字节的字符。)

因此,假设连接为 utf8mb4 且表中的列仅为 utf8,则客户端中任何 3 字节(或更短)的 UTF-8 字符会发生以下情况:每个字符都进出服务器没有转换,没有错误。注意:问题出现在INSERT,而不是SELECT;但是,在您执行SELECT 之前,您可能不会注意到问题。

但是,如果您在客户端中有表情符号怎么办?现在你会得到一个错误。 (或截断的字符串)(或问号)这是因为 4 字节的 Emoji(例如?)无法压缩到 3 字节的“utf8”(或“1 字节 latin1”或... )。

如果您运行的是 5.5 或 5.6,您可能会遇到 767(或 191)问题。我在here 中提供了几种解决方法。没有一个是完美的。

至于反转(utf8 连接但 utf8mb4 列):如果您设法将一些 4 字节字符放入表中,SELECT 可能会遇到麻烦。

“官方来源”——祝你好运。我花了十年时间试图梳理字符处理的来龙去脉,然后将其简化为可操作的句子。大部分时间都在想我有所有的答案,结果却遇到了另一个失败的测试用例。常见情况在Trouble with UTF-8 characters; what I see is not what I stored 中列出。但是,这并不能直接解决您的问题!

来自评论

mysql> SHOW CREATE TABLE emoji\G
*************************** 1. row ***************************
       Table: emoji
Create Table: CREATE TABLE `emoji` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `text` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

mysql> insert into emoji (text) values ("abc");
Query OK, 1 row affected (0.01 sec)

mysql> show variables like 'char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8                       |
| character_set_connection | utf8                       |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8                       |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)

上面说“连接”(认为“客户端”)使用的是 utf8,而不是 utf8mb4。

mysql> insert into emoji (text) values ("???");  -- 4-byte Emoji
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> show warnings;
+---------+------+----------------------------------------------------------------------------------+
| Level   | Code | Message                                                                          |
+---------+------+----------------------------------------------------------------------------------+
| Warning | 1366 | Incorrect string value: '\xF0\x9F\x98\x85\xF0\x9F...' for column 'text' at row 1 |
+---------+------+----------------------------------------------------------------------------------+
1 row in set (0.00 sec)

现在,将“连接”更改为utf8mb4

mysql> SET NAMES utf8mb4;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into emoji (text) values ("???");
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM emoji;
+----+--------------+
| id | text         |
+----+--------------+
|  1 | ? ? ? ?      |
|  2 | abc          |
|  3 | ???????????? |   -- from when "utf8" was in use
|  4 | ???             |  -- Success with utf8mb4 in use
+----+--------------+
4 rows in set (0.01 sec)

【讨论】:

谢谢!只是澄清一下:“鉴于连接是 utf8mb4 并且表中的列只有 utf8” @Dr.GianluigiZaneZanetini - 连接参数说明 client 的编码是什么。如果错误,可能会发生各种错误(请参阅我链接到的其他错误)。如果连接指定您在客户端中只有 3 字节的“utf8”,那么在客户端中使用 4 字节的 Emoji 是一种“错误”形式。 @Dr.GianluigiZaneZanetini - 也许我刚刚添加到我的答案中的内容直接解决了您的评论。【参考方案3】:

简短回答:,这不安全。

如果您的数据包含utf8mb4 字符并且您使用的是 MySQL utf8 字符集连接,那么您将遇到问题,因为 MySQL utf8 字符集仅支持 BMP 字符(最多 3 个字节字符)。

我的建议是将所有表转换为 utf8mb4 以获得完整的 UTF-8 支持。此外,utf8mb4 utf8 向后兼容

【讨论】:

向后兼容但存在一些问题:InnoDB 的最大可索引长度从 255 下降到 191 个字符,固定长度字段(例如 CHAR)每个字符多使用一个字节 @the_nuts 说的好。还有更多信息here 你回答了倒置的问题。不,将 utf8 连接与 utf8mb4 列一起使用是不安全的,但它不应该反过来正常工作,这是 OP 所要求的吗? @Accountantم 你说得对,我接受它是因为它是唯一的,但它没有回答问题 实际上我接受了它,但最后我使用的是处理一些 utf8 和拉丁列的 utf8mb4 连接,没有任何问题

以上是关于我可以安全地使用带有 utf8 列的 utf8mb4 连接吗?的主要内容,如果未能解决你的问题,请参考以下文章

utf-8无bom和utf-8啥区别

排序规则将 utf8mb4_unicode_ci 更改为 utf8mb4_general_ci

utf8mb4_unicode_ci 与 utf8mb4_bin

尝试存储表情符号时出错

utf8mb4_unicode_ci 在 PhpMyAdmin 中选择,但 WordPress 表使用 utf8mb4_unicode_520_ci 排序规则

错误 1115 (42000):未知字符集:'utf8mb4'