Яке найкраще порівняння використовувати для 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+ байтових символів Unicode, як 𝌆), проте "utf8mb4" є. З utf8 поле для вставки буде усічене, починаючи з першого непідтримуваного символу Unicode. mathiasbynens.be/notes/mysql-utf8mb4
basic6

6
Цікаво, чи нам коли-небудь знадобиться 5 байт для всіх цих емоцій ... зітхання
Альваро Гонсалес

1
Питання, пов’язані з цим: stackoverflow.com/questions/38228335/… "Яке порівняння MySQL точно відповідає порівнянню рядків PHP?"
Вільям Ентрікен

Огляд варіантів розумного
Flux

Відповіді:


617

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

utf8_general_ciдещо швидше utf8_unicode_ci, але менш точне (для сортування). Конкретний мову utf8 кодування (наприклад utf8_swedish_ci) містить додаткові правила мови , які роблять їх найбільш точною для сортування для цих мов. Більшу частину часу я використовую utf8_unicode_ci(я віддаю перевагу точності перед невеликими покращеннями продуктивності), якщо у мене немає вагомих причин віддати перевагу певній мові.

Докладніше про конкретні набори символів unicode можна прочитати в посібнику MySQL - http://dev.mysql.com/doc/refman/5.0/uk/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
El Yobo

1
З мого досвіду роботи з різними utf8_unicode_*
локальними місцями, які

11
Оновлення: для новіших версій рекомендуйте utf8mb4та utf8mb4_unicode_520_ci. Вони дають вам решту китайців, а також покращене співвідношення.
Рік Джеймс

128

Насправді ви, мабуть, хочете використовувати utf8_unicode_ciабо utf8_general_ci.

  • utf8_general_ci сортує, знімаючи всі наголоси і сортуючи, як ніби це ASCII
  • utf8_unicode_ci використовує порядок сортування Unicode, тому він сортує правильно на кількох мовах

Однак якщо ви використовуєте це лише для зберігання англійського тексту, вони не повинні відрізнятися.


1
Мені подобається ваше пояснення! Хороший. Але мені потрібно краще зрозуміти, чому саме порядок сортування Unicode є кращим способом сортування, ніж знімання акцентів.
weia design

14
@Adam Це дійсно залежить від вашої цільової аудиторії. Сортування - складна проблема, щоб правильно локалізувати. Наприклад, в норвезькій мові букви Æ Ø Å є останніми 3 алфавіту. З utf8_general_ci, Ø і Å перетворюються на O і A, що ставить їх у абсолютно неправильне положення при сортуванні (я не впевнений, як Æ обробляється, оскільки це лігатура, а не наголошений символ). Цей порядок сортування відрізняється майже будь-якою мовою, наприклад, норвезька та шведська мають різні порядки (і трохи інші букви, які вважаються рівними): Æ Ø Å сортується Å Æ Ø (фактичні букви Å Ä Ö). Unicode це виправляє.
Вегард Ларсен

Тож, про що я кажу в основному, це те, що ви, мабуть, повинні використовувати тип, що відповідає мові, якщо можете, але в більшості випадків це неможливо, тому перейдіть до загального сортування Unicode. На деяких мовах це все ще буде дивно, але правильніше, ніж ASCII.
Вегард Ларсен

3
@Manatax - при будь-якому зіставленнях utf8_ дані зберігаються як utf8. Порівняння полягає лише в тому, які символи вважаються рівними та як вони впорядковані.
фримайстер

2
@frymaster - неправда, згідно з: mathiasbynens.be/notes/mysql-utf8mb4 "utf8 MySQL дозволяє зберігати лише 5,88% усіх можливих кодів коду Unicode"
дані

120

Будьте дуже, дуже обізнані з цією проблемою, яка може виникнути при використанні utf8_general_ci.

MySQL не буде відрізняти деякі символи у вибраних операторах, якщо utf8_general_ciвикористовується посилання. Це може призвести до дуже неприємних помилок - особливо, наприклад, де задіяні імена користувачів. Залежно від реалізації, яка використовує таблиці баз даних, ця проблема може дозволити зловмисним користувачам створити ім’я користувача, що відповідає обліковому запису адміністратора.

Ця проблема виявляється щонайменше на ранніх версіях 5.x - я не впевнений, чи змінилася ця поведінка пізніше.

Я не DBA, але щоб уникнути цієї проблеми, я завжди йду з 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'. Вся суть порівняння полягає в тому, що він передбачає правила (серед іншого), коли два рядки вважаються рівними одна одній.
Hammerite

13
Саме цю проблему я намагаюся проілюструвати - порівняння робить дві речі рівними, а насправді вони взагалі не мають на меті бути рівними (і, таким чином, унікальне обмеження є прямо протилежним тому, що ви хочете досягти)
Guus

18
Але ти описуєш це як "проблему" і призводить до "помилок", коли поведінка - саме те, що має на меті збір. Ваш опис правильний, але лише настільки, наскільки це помилка з боку DBA у виборі невідповідного зіставлення.
Hammerite

32
Річ у тім, що коли ви вводите два імені користувача, які вважаються рівними порівнянням, це не буде дозволено, якщо ви встановите ім'я користувача колонки унікальним, що, звичайно, слід робити!
Студент Хогвартса

12
Я підтримав і цю відповідь, і коментар @ Hammerite, тому що обидва разом допомогли мені зрозуміти співставлення.
Нахт - Відновлення Моніки

86

Найкраще використовувати набір символів utf8mb4із зіставленням utf8mb4_unicode_ci.

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

MySQL уріже 4 байтові символи UTF-8, що призведе до пошкодження даних.

The 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)якщо 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

Список літератури:



Більше інформації про Вікіпедію: літаки Unicode
Jeremy Postlethwaite

6
utf8mb4_unicode_ci повинен бути абсолютно рекомендованим порівнянням для нових проектів у 2015 році.
Тревор Геман

7
Оновлення ... utf8mb4_unicode_520_ciкраще. В майбутньому буде utf8mb4_unicode_800_ci(або щось подібне), як MySQL наздоганяє стандарти Unicode.
Рік Джеймс

46

Збірники впливають на сортування даних та порівняння рядків між собою. Це означає, що ви повинні використовувати порівняння, яке очікує більшість користувачів.

Приклад з документації для unicode charset :

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
Якщо бінарне порівняння рядків є вашим бажаним порівнянням, то, звичайно, слід використовувати двійкове порівняння; але відхиляти альтернативне порівняння як "ризик помилок" або просто для зручності індексації говорить про те, що ви не повністю розумієте суть порівняння.
Hammerite

13

Для текстової інформації UTF-8 ви повинні використовувати, utf8_general_ciтому що ...

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

  • utf8_general_ci: порівнюйте рядки, використовуючи загальні мовні правила та використовуючи порівняння з урахуванням регістру

він також повинен зробити пошук та індексацію даних швидшими / ефективнішими / кориснішими.


12

Прийнята відповідь досить остаточно пропонує використовувати utf8_unicode_ci, і в той час як для нових проектів це чудово, я хотів розповісти про свій недавній протилежний досвід на випадок, якщо це заощадить когось деякий час.

Оскільки utf8_general_ci є порівнянням за умовчанням для Unicode в MySQL, якщо ви хочете використовувати utf8_unicode_ci, то в кінцевому підсумку вам доведеться вказати його у багатьох місцях.

Наприклад, усі клієнтські з'єднання мають не лише схему за замовчуванням (для мене це має сенс), але і зіставлення за замовчуванням (тобто порівняння завжди буде за замовчуванням utf8_general_ci для unicode).

Ймовірно, якщо ви використовуєте utf8_unicode_ci для своїх полів, ваші сценарії, які підключаються до бази даних, потрібно буде оновити, щоб чітко згадати про бажане зіставлення - інакше запити за допомогою текстових рядків можуть не працювати, коли для вашого з'єднання використовується порівняння за замовчуванням.

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


8

У випадку, виділеному Гуусом, я б настійно рекомендував використовувати або utf8_unicode_cs (з урахуванням регістру, чіткого узгодження, впорядкування здебільшого) замість utf8_bin (суворе узгодження, неправильне впорядкування).

Якщо поле призначене для пошуку, на відміну від відповідного для користувача, тоді використовуйте utf8_general_ci або utf8_unicode_ci. Обидва нечутливі до регістру, один з них втратить відповідність ("ß" дорівнює "s", а не "ss"). Існують також мовні версії, наприклад utf8_german_ci, де відповідність втрат більше підходить для вказаної мови.

[Редагувати - майже через 6 років]

Я більше не рекомендую набір символів "utf8" на MySQL, а натомість рекомендую набір символів "utf8mb4". Вони майже повністю відповідають, але дозволяють отримати трохи (багато) більше символів, що використовуються унікод.

Реально, MySQL повинен був оновити набір символів "utf8" та відповідні зіставлення, щоб відповідати специфікації "utf8", але натомість окремий набір символів та відповідні порівняння не впливають на позначення пам’яті для тих, хто вже використовує свій неповний набір символів "utf8". .


5
FYI: 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.
Jitesh Sojitra

5
Ця відповідь не має нічого спільного з питанням. Крім того, видача SET NAMESзапиту безпосередньо не дає клієнту знати про кодування і може дуже тонко порушити деякі функції, наприклад, підготовлені оператори.
Альваро Гонсалес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.