Это сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

Внимание: MySQL считает, что буквы «ё» и «е» — одно и то же.

Увы, MySQL, если выбрать кодировку Unicode, считает, что «е» и «ё» — это одно и то же. Багом это не считается, в официальной таблице, где описано как MySQL 6.0 сравнивает символы, эти символы в одном ряду.

mysql> set names utf8 collate utf8_unicode_ci;
Query OK, 0 rows affected (0.00 sec)

mysql> select 'е'='ё', 'ё'='ѐ'\G
*************************** 1. row ***************************
'е'='ё': 1
'ё'='ѐ': 1
1 row in set (0.00 sec)

Самое печальное, это нелогичное поведение всплывает, если есть потребность перевести базу с CP1251 на UTF-8, так как в CP1251-то как раз всё хорошо:

mysql> set names cp1251;
Query OK, 0 rows affected (0.00 sec)

mysql> select 'е'='ё', 'ё'='ѐ'\G
*************************** 1. row ***************************
'е'='ё': 0
'ё'='ѐ': 0
1 row in set (0.00 sec)

Чем это может быть плохо? Например, у нас по какому-то текстовому полю имеется уникальный ключ. Для иллюстративности, пусть это будет словарь фамилий. Фамилии Рублев и Рублёв — разные, но MySQL так не считает (для простоты опускаю другие поля):

mysql> set names utf8 collate utf8_unicode_ci;
Query OK, 0 rows affected (0.00 sec)

mysql> create table surname(surname varchar(255) primary key);
Query OK, 0 rows affected (0.07 sec)

mysql> insert into surname values('Рублёв');
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> insert into surname values('Рублев');
ERROR 1062 (23000): Duplicate entry 'Рублев' for key 'PRIMARY'

Я придумал только один способ, исправляющий это поведение (но у него есть недостаток):

mysql> create table surname(surname varchar(255) primary key collate 'utf8_bin');
Query OK, 0 rows affected (0.02 sec)

mysql> insert into surname values('Рублёв');
Query OK, 1 row affected (0.00 sec)

mysql> insert into surname values('Рублев');
Query OK, 1 row affected (0.00 sec)

mysql> select * from surname where surname='рублев';
Empty set (0.00 sec)

Я выключил case insensitive у данного столбца, явным указанием «collate». Это полностью решает проблему с «ё», но лишает нас удовольствия искать, не заботясь о совпадении регистра.

18 комментариев
dinoel 2011

Интересно. А что вернёт
select * from surnames where LOWER(surname) = LOWER(’Рублёв’) ?

dinoel 2011

Кстати, заметил баг — если имя не вводить, то выдаст ошибку «введите имя или адрес блога» , но текст комментария пропадёт. Неприятно.

Евгений Степанищев (bolknote.ru) 2011

Комментарий для dinoel:

Интересно. А что вернёт
select * from surnames where LOWER(surname) = LOWER(’Рублёв’)?

В каких условиях?

Кстати, заметил баг — если имя не вводить, то выдаст ошибку «введите имя или адрес блога» , но текст комментария пропадёт. Неприятно.

Да, я знаю, в TODO записан, но никак не пойдут руки поправить :( Но я поправлю.

dinoel 2011

В каких условиях?

create table surname(surname varchar(255) primary key collate ’utf8_bin’);
вот в таких, т. е. при бинарном сравнении.
Хотя я догадываюсь :)

dinoel 2011

Проверил, работает как надо. Т. е. проблема с регистром решается просто

 select * from surname where LOWER(surname)=LOWER(’рублев’);
+-​-​-​-​-​-​-​-​-​-​-​-​-​-​+

| surname |

+-​-​-​-​-​-​-​-​-​-​-​-​-​-​+

| Рублев |

+-​-​-​-​-​-​-​-​-​-​-​-​-​-​+
1 row in set (0.00 sec)

mysql> select * from surname where LOWER(surname)=LOWER(’рублёв’);
+-​-​-​-​-​-​-​-​-​-​-​-​-​-​+

| surname |

+-​-​-​-​-​-​-​-​-​-​-​-​-​-​+

| Рублёв |

+-​-​-​-​-​-​-​-​-​-​-​-​-​-​+

dinoel 2011

Другое дело что с сортировкой будут проблемы..

Евгений Степанищев (bolknote.ru) 2011

Комментарий для dinoel:

Другое дело что с сортировкой будут проблемы..

С сортировкой и использованием индекса.

besisland (besisland.name) 2011

Я считаю, что подобное обращение с пользовательскими данными со стороны СУБД есть мерзость и грех великий. Кто ей разрешал брать на себя такую ответственность? Её дело — данные хранить.

dinoel 2011

Её дело — данные хранить.

А также сортировать, сравнивать и искать.

Евгений Степанищев (bolknote.ru) 2011

Комментарий для besisland.name:

Путать «е» и «ё» — точно великий грех. Это неудобно. В конце концов, кроме помощника кроссвордиста существует ещё множество других задач, которые хочется писать с использованием этой БД.

maxim-zotov (maxim-zotov.livejournal.com) 2011

mysql поступает очень правильно. У «ё» давно нет прав, и не mysql их у неё отобрал, потакать желаниям считанных пуристов — это усложнять жизнь миллионам обычных людей, которые ищут и заносят в базы данных «Рублев» и «Ежик в тумане».

besisland (besisland.name) 2011

Дело не в «е» и «ё», а в незапрашиваемых явно операциях над строками вообще.

besisland (besisland.name) 2011

Комментарий для dinoel:

До сих пор из того факта, что MySQL по умолчанию сравнивает строки регистронезависимо или же приравнивает «ü» и «u», я выносил для себя только широкий спектр проблем, которые приходилось так или иначе решать.

Artjom Kurapov (kurapov.name) 2011

BINARY() ?

Евгений Степанищев (bolknote.ru) 2011

Комментарий для kurapov.name:

Не понял.

Дмитрий (http://www.kains.ru) 2011

Хм... Полезное однако иследование... Хотя, если честно, то ё редко где можно увидеть, все давно уже используют е заместо ё... Но опять таки с точки зрения русского языка правильнее будет именно ё...

Евгений Степанищев (bolknote.ru) 2011

Комментарий для Дмитрий (http://www.kains.ru):

Например, в фамилиях и географических названиях нельзя без «ё».

NightFox 2016

Решил проблему по примеру описаному здесь: http://www.sql.ru/forum/766045-2/besit-zhenyok-kodirovki-cp1251-utf8-bukva-yo?mid=11663963#11663963
А именно в /usr/share/mysql/charsets/Index.xml изменил
<charset name=«utf8»>
  <family>Unicode</family>
  <description>UTF-8 Unicode</description>
  <alias>utf-8</alias>
  <collation name=«utf8_general_ci» id=«33»>
   <flag>primary</flag>
   <flag>compiled</flag>
  </collation>
  <collation name=«utf8_bin» id=«83»>
    <flag>binary</flag>
    <flag>compiled</flag>
  </collation>
  <collation name=«utf8_russian_ci» id=«250»>
    <rules>
      <reset>\u0415</reset><p>\u0401</p> <!-​-​ ЕЁ -​-​>
      <reset>\u0435</reset><p>\u0451</p> <!-​-​ её -​-​>
    </rules>
  </collation>
</charset>
и добавил:
<charset name=«utf8mb4»>
  <family>Unicode</family>
  <description>UTF-8 Unicode</description>
  <alias>utf-8</alias>
  <collation name=«utf8mb4_russian_ci» id=«251»>
    <rules>
      <reset>\u0415</reset><p>\u0401</p> <!-​-​ ЕЁ -​-​>
      <reset>\u0435</reset><p>\u0451</p> <!-​-​ её -​-​>
    </rules>
  </collation>
</charset>
после чего выполнил:
ALTER TABLE `таблица` CHANGE `столбец` `столбец` тип CHARACTER SET utf8mb4 COLLATE utf8mb4_russian_ci NOT NULL DEFAULT ’’;