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

时间:2016-01-04 16:26:19

标签: php mysql pdo utf-8 utf8mb4

我有一些带有 utf8mb4 字段的MySQL表,以及带有 utf8 的其他表。

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

编辑:问题不在于"我可以将4字节字符存储到utf8列中吗?" 我们已经知道我们不能这样做,而不是' t取决于连接,因此如果列是utf8,则意味着它不会收到4个字节的字符,例如国家或货币代码,电子邮件地址,用户名...应用程序验证输入的位置。

3 个答案:

答案 0 :(得分:5)

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

<?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 äŒé ♔♕  ',
  ),
)

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

原因是您可以在utf8中对任何latinasciiutf8mb4字符进行编码,但不能相反。因此,在这种情况下,使用utf8mb4作为连接的字符集是安全的。

答案 1 :(得分:3)

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

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

详细答案:

(我将说明为什么“不” 可以是正确的答案。)

连接建立客户端使用的编码。

一列上的CHARACTER SET(默认情况下来自表)确定可以在该列中添加哪种编码。

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

(从技术上讲,utf8mb4仅可处理4个字节,而UTF-8可处理更长的字符。但是,我怀疑5字节字符是否会在我的一生中发生。)

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

但是,如果客户端中有表情符号怎么办?现在,您将得到一个错误。 (或截断的字符串)(或问号),这是因为无法将4字节的表情符号(例如)压缩为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)

答案 2 :(得分:2)

简答:,这是不安全的。

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

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