Какое сопоставление лучше всего использовать для MySQL с PHP? [закрыто]

731

Мне интересно, есть ли «лучший» выбор для сортировки в MySQL для общего веб-сайта, где вы не уверены на 100%, что будет введено? Я понимаю, что все кодировки должны быть одинаковыми, например, MySQL, Apache, HTML и все, что находится внутри PHP.

В прошлом я устанавливал PHP для вывода в "UTF-8", но с каким сопоставлением это соответствует в MySQL? Я думаю , что это одна из UTF-8 из них, но я использовал utf8_unicode_ci, utf8_general_ciи utf8_binраньше.

Дэррил Хейн
источник
35
Примечание: «utf8» в MySQL не соответствует UTF-8 (не поддерживается 4-байтовые символы Юникода, такие как 𝌆), однако «utf8mb4» есть. При использовании utf8 поле будет вставлено при вставке, начиная с первого неподдерживаемого символа Unicode. mathiasbynens.be/notes/mysql-utf8mb4
basic6
6
Интересно, понадобятся ли нам когда-нибудь 5 байтов для всех этих смайликов ... вздох
Альваро Гонсалес
1
Смежный вопрос: stackoverflow.com/questions/38228335/… "Какой порядок сортировки MySQL в точности соответствует сравнению строк в PHP?"
Уильям Энтрикен
Для обзора вменяемых опций: monolune.com/mysql-utf8-charsets-and-collations-explained
Flux

Ответы:

618

Основным отличием является точность сортировки (при сравнении символов на языке) и производительность. Единственный специальный - это utf8_bin, который предназначен для сравнения символов в двоичном формате.

utf8_general_ciнесколько быстрее чем utf8_unicode_ci, но менее точно (для сортировки). Конкретный язык utf8 кодирование (например utf8_swedish_ci) содержит дополнительные правила языка , которые делают их наиболее точной для сортировки для этих языков. Большую часть времени я использую utf8_unicode_ci(я предпочитаю точность небольшим улучшениям производительности), если у меня нет веских причин предпочитать конкретный язык.

Вы можете прочитать больше о конкретных наборах символов Юникода в руководстве по MySQL - http://dev.mysql.com/doc/refman/5.0/en/charset-unicode-sets.html

Эран Гальперин
источник
4
небольшие улучшения производительности? ты уверен в этом ? publib.boulder.ibm.com/infocenter/db2luw/v9r5/index.jsp?topic=/… Выбор параметров сортировки может значительно повлиять на производительность запросов в базе данных.
Адам Рамадан
62
Это для DB2, а не MySQL. Кроме того, нет конкретных цифр или ориентиров, поэтому вы просто основываетесь на мнении автора.
Эран Гальперин
3
Обратите внимание, что если вы хотите использовать функции, в MySQL есть ошибка (большинство распространяемых в настоящее время версий), когда функции всегда возвращают строку, используя utf8_general_ci, вызывая проблемы, если вы используете другое сопоставление для своих строк - см. Bugs.mysql.com/ bug.php? id = 24690
Эль Йобо,
1
Из моего опыта работы с разными локалями я бы всегда использовалutf8_unicode_*
Шиплу Мокаддим
11
Обновление: для более новых версий рекомендуем utf8mb4и utf8mb4_unicode_520_ci. Они дают вам остальную часть китайского языка, а также улучшенную сортировку.
Рик Джеймс
129

На самом деле, вы, вероятно, хотите использовать utf8_unicode_ciили utf8_general_ci.

  • utf8_general_ci сортирует, удаляя все акценты и сортируя, как будто это ASCII
  • utf8_unicode_ci использует порядок сортировки Unicode, поэтому он сортирует правильно на нескольких языках

Однако, если вы используете это только для хранения английского текста, они не должны отличаться.

Вегард Ларсен
источник
1
Мне нравится ваше объяснение! Неплохо. Но мне нужно лучше понять, почему порядок сортировки в юникоде - лучший способ сортировки, чем удаление акцентов.
Weia Design
14
@ Adam Это действительно зависит от вашей целевой аудитории. Сортировка является сложной задачей для правильной локализации. Например, на норвежском языке буквы Æ Ø Å являются последними 3 алфавита. С utf8_general_ci, Ø и Å преобразуются в O и A, что ставит их в совершенно неправильное положение при сортировке (я не уверен, как обрабатывается Æ, так как это лигатура, а не акцентированный символ). Этот порядок сортировки различается практически на любом языке, например, норвежский и шведский имеют разные порядки (и несколько разные буквы, которые считаются равными): Æ Ø Å сортируется Å Æ Ø (фактические буквы Å Ä Ö). Юникод исправляет это.
Вегард Ларсен
Итак, что я в основном говорю, так это то, что вам, вероятно, следует использовать сортировку по конкретному языку, если это возможно, но в большинстве случаев это невозможно, поэтому перейдите к общей сортировке в Юникоде. На каком-то языке это все еще будет странно, но более правильно, чем ASCII.
Вегард Ларсен
3
@Manatax - при любом сопоставлении utf8_ данные сохраняются как utf8. Сравнение - это примерно то, какие символы считаются равными и как они упорядочены.
Фраймастер
2
@frymaster - неправда, согласно: mathiasbynens.be/notes/mysql-utf8mb4 «MySQL utf8 позволяет хранить только 5,88% всех возможных кодовых точек Unicode»
данные
120

Будьте очень, очень осведомлены об этой проблеме, которая может возникнуть при использовании utf8_general_ci.

MySQL не будет различать некоторые символы в операторах выбора, если используется utf8_general_ciсопоставление. Это может привести к очень неприятным ошибкам - особенно, например, когда задействованы имена пользователей. В зависимости от реализации, использующей таблицы базы данных, эта проблема может позволить злоумышленникам создать имя пользователя, соответствующее учетной записи администратора.

Эта проблема раскрывается как минимум в ранних версиях 5.x - я не уверен, изменилось ли это поведение позже.

Я не администратор баз данных, но чтобы избежать этой проблемы, я всегда использую utf8-binвместо регистронезависимого.

Сценарий ниже описывает проблему на примере.

-- first, create a sandbox to play in
CREATE DATABASE `sandbox`;
use `sandbox`;

-- next, make sure that your client connection is of the same 
-- character/collate type as the one we're going to test next:
charset utf8 collate utf8_general_ci

-- now, create the table and fill it with values
CREATE TABLE `test` (`key` VARCHAR(16), `value` VARCHAR(16) )
    CHARACTER SET utf8 COLLATE utf8_general_ci;

INSERT INTO `test` VALUES ('Key ONE', 'value'), ('Key TWO', 'valúe');

-- (verify)
SELECT * FROM `test`;

-- now, expose the problem/bug:
SELECT * FROM test WHERE `value` = 'value';

--
-- Note that we get BOTH keys here! MySQLs UTF8 collates that are 
-- case insensitive (ending with _ci) do not distinguish between 
-- both values!
--
-- collate 'utf8_bin' doesn't have this problem, as I'll show next:
--

-- first, reset the client connection charset/collate type
charset utf8 collate utf8_bin

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Note that we get just one key now, as you'd expect.
--
-- This problem appears to be specific to utf8. Next, I'll try to 
-- do the same with the 'latin1' charset:
--

-- first, reset the client connection charset/collate type
charset latin1 collate latin1_general_ci

-- next, convert the values that we've previously inserted
-- in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET latin1 COLLATE latin1_general_ci;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Again, only one key is returned (expected). This shows 
-- that the problem with utf8/utf8_generic_ci isn't present 
-- in latin1/latin1_general_ci
--
-- To complete the example, I'll check with the binary collate
-- of latin1 as well:

-- first, reset the client connection charset/collate type
charset latin1 collate latin1_bin

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET latin1 COLLATE latin1_bin;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Again, only one key is returned (expected).
--
-- Finally, I'll re-introduce the problem in the exact same 
-- way (for any sceptics out there):

-- first, reset the client connection charset/collate type
charset utf8 collate utf8_generic_ci

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;

-- now, re-check for the problem/bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Two keys.
--

DROP DATABASE sandbox;
Гус
источник
36
-1: это, безусловно, исправляется путем применения уникального ключа к соответствующему столбцу. Вы бы увидели такое же поведение, если бы два значения были 'value'и 'valUe'. Весь смысл сопоставления состоит в том, что он обеспечивает правила (среди прочего), когда две строки считаются равными друг другу.
Хаммерите
13
Это именно та проблема, которую я пытаюсь проиллюстрировать - сопоставление делает две вещи равными, хотя на самом деле они вовсе не предназначены для того, чтобы быть равными (и, таким образом, уникальное ограничение в точности противоположно тому, что вы хотели бы достичь)
Гус
18
Но вы описываете это как «проблему», ведущую к «ошибкам», когда поведение - именно то, для чего предназначена сортировка. Ваше описание верно, но только в том случае, если это ошибка со стороны администратора базы данных при выборе несоответствующей сортировки.
Hammerite
32
Дело в том, что, когда вы вводите два имени пользователя, которые считаются равными по сопоставлению, это не будет разрешено, если вы установили уникальное имя пользователя столбца, что, конечно, вы должны делать!
Студент Хогвартса
12
Я проголосовал как за этот ответ, так и за комментарий @ Hammerite, потому что оба они вместе помогли мне достичь понимания сопоставления.
Nacht - Восстановить Монику
86

Лучше всего использовать набор символов utf8mb4с сопоставлением utf8mb4_unicode_ci.

Набор символов, utf8поддерживает только небольшое количество кодовых точек UTF-8, около 6% возможных символов. utf8поддерживает только базовую многоязычную плоскость (BMP). Там 16 других самолетов. Каждая плоскость содержит 65 536 символов. utf8mb4поддерживает все 17 самолетов.

MySQL будет обрезать 4-байтовые символы UTF-8, что приведет к повреждению данных.

Набор utf8mb4символов был введен в MySQL 5.5.3 2010-03-24.

Некоторые из необходимых изменений для использования нового набора символов не являются тривиальными:

  • Возможно, потребуется внести изменения в адаптер базы данных приложения.
  • В my.cnf необходимо будет внести изменения, включая установку набора символов, сопоставление и переключение innodb_file_format на Barracuda.
  • Операторы SQL CREATE могут включать в себя: ROW_FORMAT=DYNAMIC
    • DYNAMIC требуется для индексов на VARCHAR (192) и выше.

ПРИМЕЧАНИЕ. Для переключения Barracudaс Antelope, возможно, потребуется перезапустить службу MySQL более одного раза. innodb_file_format_maxне изменится до тех пор , после того , как служба MySQL перезапущена для: innodb_file_format = barracuda.

MySQL использует старый Antelopeформат файла InnoDB. Barracudaподдерживает динамические форматы строк, которые вам понадобятся, если вы не хотите нажимать на ошибки SQL для создания индексов и ключей после переключения на кодировку:utf8mb4

  • # 1709 - Слишком большой размер столбца индекса. Максимальный размер столбца составляет 767 байт.
  • # 1071 - Указанный ключ был слишком длинным; максимальная длина ключа 767 байт

Следующий сценарий был протестирован на MySQL 5.6.17: по умолчанию MySQL настроен так:

SHOW VARIABLES;

innodb_large_prefix = OFF
innodb_file_format = Antelope

Остановите службу MySQL и добавьте параметры в существующий my.cnf:

[client]
default-character-set= utf8mb4

[mysqld]
explicit_defaults_for_timestamp = true
innodb_large_prefix = true
innodb_file_format = barracuda
innodb_file_format_max = barracuda
innodb_file_per_table = true

# Character collation
character_set_server=utf8mb4
collation_server=utf8mb4_unicode_ci

Пример оператора SQL CREATE:

CREATE TABLE Contacts (
 id INT AUTO_INCREMENT NOT NULL,
 ownerId INT DEFAULT NULL,
 created timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
 modified timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 contact VARCHAR(640) NOT NULL,
 prefix VARCHAR(128) NOT NULL,
 first VARCHAR(128) NOT NULL,
 middle VARCHAR(128) NOT NULL,
 last VARCHAR(128) NOT NULL,
 suffix VARCHAR(128) NOT NULL,
 notes MEDIUMTEXT NOT NULL,
 INDEX IDX_CA367725E05EFD25 (ownerId),
 INDEX created (created),
 INDEX modified_idx (modified),
 INDEX contact_idx (contact),
 PRIMARY KEY(id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB ROW_FORMAT=DYNAMIC;
  • Вы можете увидеть ошибку # 1709, сгенерированную для INDEX contact_idx (contact)if ROW_FORMAT=DYNAMIC, удаленную из оператора CREATE.

ПРИМЕЧАНИЕ. Изменение индекса до первых 128 символов contactисключает необходимость использования Barracuda сROW_FORMAT=DYNAMIC

INDEX contact_idx (contact(128)),

Также обратите внимание: когда говорится, что размер поля равен VARCHAR(128)128 байтам. Вы можете использовать 128, 4-байтовые символы или 128, 1-байтовые символы.

Этот INSERTоператор должен содержать 4-байтовый символ 'poo' в строке 2:

INSERT INTO `Contacts` (`id`, `ownerId`, `created`, `modified`, `contact`, `prefix`, `first`, `middle`, `last`, `suffix`, `notes`) VALUES
(1, NULL, '0000-00-00 00:00:00', '2014-08-25 03:00:36', '1234567890', '12345678901234567890', '1234567890123456789012345678901234567890', '1234567890123456789012345678901234567890', '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678', '', ''),
(2, NULL, '0000-00-00 00:00:00', '2014-08-25 03:05:57', 'poo', '12345678901234567890', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '', ''),
(3, NULL, '0000-00-00 00:00:00', '2014-08-25 03:05:57', 'poo', '12345678901234567890', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '123💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '', '');

Вы можете увидеть количество места, используемого в lastстолбце:

mysql> SELECT BIT_LENGTH(`last`), CHAR_LENGTH(`last`) FROM `Contacts`;
+--------------------+---------------------+
| BIT_LENGTH(`last`) | CHAR_LENGTH(`last`) |
+--------------------+---------------------+
|               1024 |                 128 | -- All characters are ASCII
|               4096 |                 128 | -- All characters are 4 bytes
|               4024 |                 128 | -- 3 characters are ASCII, 125 are 4 bytes
+--------------------+---------------------+

В вашем адаптере базы данных вы можете установить кодировку и параметры сортировки для вашего соединения:

SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'

В PHP это будет установлено для: \PDO::MYSQL_ATTR_INIT_COMMAND

Ссылки:

Джереми Постлетуэйт
источник
Более подробная информация о utf8mb4 в MySQL 5.6 Справочное руководство: 10.1.10.7 Набор символов utf8mb4 (4-байтовая кодировка UTF-8 Unicode)
Джереми Постлетвейт,
Дополнительная информация о Википедии: самолеты Unicode
Джереми Постлетуэйт
6
utf8mb4_unicode_ci должен быть абсолютно рекомендуемым сопоставлением для новых проектов в 2015 году.
Тревор Гехман
7
Обновление ... utf8mb4_unicode_520_ciлучше. В будущем будет utf8mb4_unicode_800_ci(или что-то в этом роде), поскольку MySQL будет соответствовать стандартам Unicode.
Рик Джеймс
46

Сопоставления влияют на то, как данные сортируются и как строки сравниваются друг с другом. Это означает, что вы должны использовать параметры сортировки, которые ожидают большинство ваших пользователей.

Пример из документации по кодировке Unicode :

utf8_general_ciтакже подходит для немецкого и французского языков, за исключением того, что «ß» равно «s», а не «ss». Если это приемлемо для вашего приложения, то вы должны использовать, utf8_general_ciпотому что это быстрее. В противном случае используйте, utf8_unicode_ciпотому что это более точно.

Так что - это зависит от вашей ожидаемой пользовательской базы и от того, насколько вам нужна правильная сортировка. Для английской пользовательской базы utf8_general_ciдолжно хватить, для других языков, таких как шведский, были созданы специальные сопоставления.

Томалак
источник
1
я использовал utf8_general_ci, и сортировка заняла пару секунд, а armscii_general_ci сделал это очень быстро. Почему это произошло? Еще один вопрос, как вы думаете, какое сопоставление используется сайтами социальных сетей
22

По сути, это зависит от того, как вы думаете о строке.

Я всегда использую utf8_bin из-за проблемы, выделенной Гусом. На мой взгляд, что касается базы данных, строка по-прежнему просто строка. Строка - это число символов UTF-8. Символ имеет двоичное представление, так почему ему нужно знать язык, который вы используете? Обычно люди будут создавать базы данных для систем с многоязычными сайтами. В этом весь смысл использования UTF-8 в качестве набора символов. Я немного приверженец чистоты, но я думаю, что ошибка может сильно перевесить небольшое преимущество, которое вы можете получить при индексации. Любые языковые правила должны выполняться на гораздо более высоком уровне, чем СУБД.

В моих книгах «ценность» никогда не должна равняться миллиону лет.

Если я хочу сохранить текстовое поле и выполнить поиск без учета регистра, я буду использовать строковые функции MYSQL с функциями PHP, такими как LOWER () и функция php strtolower ().

Фил
источник
9
Если двоичное сравнение строк является желаемым сравнением, тогда, конечно, вы должны использовать двоичное сопоставление; но отклонение альтернативных сопоставлений как «риска ошибок» или просто для удобства индексации предполагает, что вы не до конца понимаете смысл сопоставления.
Хаммерите
13

Для текстовой информации UTF-8, вы должны использовать, utf8_general_ciпотому что ...

  • utf8_bin: сравнить строки по двоичному значению каждого символа в строке

  • utf8_general_ci: сравнивать строки, используя общие правила языка и используя сравнения без учета регистра

иначе это должно сделать поиск и индексацию данных более быстрым / более эффективным / более полезным.

mepcotterell
источник
12

Принятый ответ довольно определенно предполагает использование utf8_unicode_ci, и хотя для новых проектов это здорово, я хотел бы рассказать о своем недавнем противоречивом опыте на тот случай, если он кого-нибудь сэкономит.

Поскольку utf8_general_ci является сопоставлением по умолчанию для Unicode в MySQL, если вы хотите использовать utf8_unicode_ci, вам придется указывать его во многих местах.

Например, все клиентские соединения имеют не только кодировку по умолчанию (имеет смысл для меня), но также и параметры сортировки по умолчанию (т. Е. Параметры сортировки всегда будут по умолчанию utf8_general_ci для юникода).

Вероятно, если вы используете utf8_unicode_ci для своих полей, ваши скрипты, которые подключаются к базе данных, должны быть обновлены, чтобы явно указывать желаемое сопоставление - в противном случае запросы, использующие текстовые строки, могут потерпеть неудачу, когда ваше соединение использует сопоставление по умолчанию.

В результате при преобразовании существующей системы любого размера в Unicode / utf8 может возникнуть необходимость использовать utf8_general_ci из-за того, что MySQL обрабатывает значения по умолчанию.

Джордж Лунд
источник
8

Для случая, выделенного Guus, я настоятельно рекомендую использовать utf8_unicode_cs (чувствительный к регистру, строгое соответствие, правильное упорядочение по большей части) вместо utf8_bin (строгое соответствие, неправильное упорядочение).

Если поле предназначено для поиска, а не для пользователя, то используйте utf8_general_ci или utf8_unicode_ci. Оба регистрозависимы, одно совпадение будет проигрышным («ß» равно «s», а не «ss»). Существуют также языковые версии, такие как utf8_german_ci, где сопоставление с потерями больше подходит для указанного языка.

[Редактировать - почти 6 лет спустя]

Я больше не рекомендую набор символов «utf8» в MySQL, а вместо этого рекомендую набор символов «utf8mb4». Они почти полностью совпадают, но допускают немного (намного) больше символов Юникода.

Реально, MySQL должен был обновить набор символов «utf8» и соответствующие параметры сортировки, чтобы соответствовать спецификации «utf8», но вместо этого - отдельный набор символов и соответствующие параметры сортировки, чтобы не влиять на обозначение хранилища для тех, кто уже использует их неполный набор символов «utf8». ,

SEoF
источник
5
К вашему сведению: utf8_unicode_csне существует. Единственный чувствительный к регистру utf8 это utf8_bin. Проблема в utf8_binсортировке неверна. См .: stackoverflow.com/questions/15218077/…
Коста,
1
Спасибо за обновление!
Прометей
5

Я нашел эти таблицы сравнения полезными. http://collation-charts.org/mysql60/ . Я не уверен, что используется utf8_general_ci, хотя.

Например, вот график для utf8_swedish_ci. Он показывает, какие символы он интерпретирует как одинаковые. http://collation-charts.org/mysql60/mysql604.utf8_swedish_ci.html

джайв
источник
Другой вид диаграммы: mysql.rjweb.org/utf8_collations.html
Рик Джеймс,
2

В файле загрузки базы данных добавьте следующую строку перед любой строкой:

SET NAMES utf8;

И ваша проблема должна быть решена.

тапос гош
источник
2
Прочитайте вопрос: в прошлом я устанавливал PHP для вывода в "UTF-8", но какое сопоставление соответствует этому в MySQL? Я думаю, что это один из UTF-8, но раньше я использовал utf8_unicode_ci, utf8_general_ci и utf8_bin.
Джитеш Соджитра
5
Этот ответ не имеет ничего общего с вопросом. Кроме того, выдача SET NAMESзапроса напрямую не дает клиенту знать о кодировке и может очень тонко нарушать некоторые функции, такие как подготовленные операторы.
Альваро Гонсалес