Можно ли безопасно использовать соединение utf8mb4 с столбцами utf8?

У меня есть некоторые таблицы MySQL с полями utf8mb4, а другие с utf8.

Безопасно ли использовать utf8mb4 в строке подключения PDO для всех таблиц? Или мне нужно преобразовать все в utf8mb4 или запустить два разных подключения PDO?


РЕДАКТИРОВАТЬ: Вопрос не в том, "могу ли я хранить 4-байтовые символы в столбцах utf8?" Мы уже знаем, что не можем, это не зависит от соединения, поэтому, если столбец имеет значение utf8, это означает, что он не получит 4-байтовые символы, например, коды страны или валюты, адреса электронной почты, имена пользователей... где ввод подтверждено приложением.

Ответ 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 äŒé ♔♕ 🛃 🔣',
  ),
)

Как видите, мы не можем использовать utf8 качестве кодировки соединения, когда работаем со столбцами utf8mb4 (см. ????). Но мы можем использовать utf8mb4 для соединения при работе со столбцами utf8. Также нет проблем с записью и чтением из latin или ascii столбцов.

Причина в том, что вы можете кодировать любой utf8, latin или ascii символ в utf8mb4 но не наоборот. Поэтому использование utf8mb4 качестве набора символов для соединения в этом случае безопасно.

Ответ 2

Короткий ответ: НЕТ, это не безопасно.

Если у ваших данных есть символы utf8mb4, и вы используете соединение charset MySQL utf8, вы столкнулись с проблемами, поскольку MySQL utf8 charset поддерживает только символы BMP (до 3 байтов символов).

Моя рекомендация состоит в том, чтобы преобразовать все таблицы в utf8mb4 для полной поддержки UTF-8. Кроме того, utf8mb4 имеет обратную совместимость с utf8.

Ответ 3

Краткий ответ: Да, если вы используете только 3-байтовые (или более короткие) символы UTF-8.

Или... Нет, если вы собираетесь работать с 4-байтовыми символами UTF-8, такими как 😅😘😍.

Длинный ответ:

(И я расскажу, почему "нет" может быть правильным ответом.)

Соединение устанавливает, какую кодировку использует клиент.

CHARACTER SET для столбца (или, по умолчанию, из таблицы) устанавливает, какую кодировку можно поместить в столбец.

CHARACTER SET utf8 является подмножеством utf8mb4. То есть все символы, приемлемые для utf8 (через соединение или столбец), приемлемы для utf8mb4. Иными словами, MySQL utf8mb4 (такой же, как внешний мир UTF-8) имеет полную 4-байтовую кодировку utf-8, которая включает в себя больше Emoji, больше китайского и т.д., Чем MySQL до 3-байтового utf8 (он же BMP) ")

(Технически utf8mb4 обрабатывает только до 4 байтов, но UTF-8 обрабатывает более длинные символы. Однако я сомневаюсь, что 5-байтовые символы появятся в моей жизни.)

Итак, вот что происходит с любым 3-байтовым (или более коротким) символом UTF-8 в клиенте, учитывая, что Connection имеет значение utf8mb4, а столбцы в таблицах - только utf8: каждый символ входит и выходит из сервера без преобразования и без ошибок. Примечание: проблема возникает на INSERT, а не на SELECT; однако вы можете не заметить проблему, пока не сделаете SELECT.

Но что, если у вас есть Emoji в клиенте? Теперь вы получите ошибку. (Или усеченная строка) (Или вопросительный знак (и)) Это связано с тем, что 4-байтовый Emoji (например, cannot) не может быть сжат в 3-байтовый "utf8" (или "1-байтовый latin1" или...).

Если вы используете 5.5 или 5.6, вы можете столкнуться с проблемой 767 (или 191). Я приведу несколько обходных путей здесь. Ни один не идеален.

Что касается инвертирования (соединение utf8, но столбцы utf8mb4): у SELECT могут возникнуть проблемы, если вам удастся получить некоторые 4-байтовые символы в таблице.

"Официальные источники" - удачи. Я потратил десятилетие, пытаясь разобраться в тонкостях обработки символов, а затем упростил их до практических предложений. Большую часть времени я думал, что у меня есть ответы на все вопросы, только чтобы встретить еще один неудачный тестовый пример. Распространенные случаи перечислены в Trouble с символами UTF-8; то, что я вижу, не то, что я храню Однако это не относится непосредственно к вашему вопросу!

Из комментария

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)