Какое влияние оказывает LC_CTYPE на базу данных PostgreSQL?

25

Итак, у меня есть несколько серверов Debian с PostgreSQL на нем. Исторически эти серверы и PostgreSQL были локализованы с помощью набора символов Latin 9, и тогда это было нормально. Теперь мы должны разобраться с такими вещами, как польский, греческий или китайский, поэтому изменение становится растущей проблемой.

Когда я попытался создать базу данных UTF8, я получил сообщение:

ОШИБКА: кодировка UTF8 не соответствует локали fr_FR. Подробно: выбранная настройка LC_CTYPE требует кодировки LATIN9.

Пару раз я проводил некоторые исследования по этому вопросу со своим старым приятелем Google, и все, что я мог найти, было несколько слишком сложных процедур, таких как обновление Debian LANG, перекомпиляция PostgreSQL с помощью правильной кодировки, редактирование всех LC_системных переменных и других непонятных решений. Поэтому пока мы оставляем этот вопрос в стороне.

Недавно он снова вернулся, греки хотят вещи, а латынь 9 не хотят. И пока я снова изучал этот вопрос, один коллега подошел ко мне и сказал: «Нет, это просто, смотри».

Он ничего не редактировал, не делал фокусов, он просто делал SQL-запрос:

CREATE DATABASE my_utf8_db
  WITH ENCODING='UTF8'
       OWNER=admin
       TEMPLATE=template0
       LC_COLLATE='C'
       LC_CTYPE='C'
       CONNECTION LIMIT=-1
       TABLESPACE=pg_default;

И это работало нормально.

Я на самом деле не знал об этом, LC_CTYPE='C'и я был удивлен, что его использование не было в первых решениях в Google и даже в переполнении стека. Я оглянулся и нашел только упоминание в документации PostgreSQL.

Когда LC_CTYPE - C или POSIX, любой набор символов разрешен, но для других настроек LC_CTYPE есть только один набор символов, который будет работать правильно. Поскольку настройка LC_CTYPE замораживается initdb, очевидная гибкость использования разных кодировок в разных базах данных кластера является более теоретической, чем реальной, за исключением случаев, когда вы выбираете язык C или POSIX (таким образом отключая любую реальную осведомленность о языке).

Это заставило меня задуматься, это слишком просто, слишком идеально, каковы недостатки? И мне пока трудно найти ответ. Итак, я прихожу сюда:

tl; dr: Каковы недостатки использования LC_CTYPE='C'определенной локализации? Разве это плохо? Что я должен ожидать, чтобы сломаться?

Грегуар Д.
источник

Ответы:

26

Каковы недостатки использования LC_CTYPE = 'C' для конкретной локализации

В документации упоминается связь между локалями и функциями SQL в поддержке локалей :

Настройки локали влияют на следующие функции SQL:

  • Порядок сортировки в запросах с использованием ORDER BY или стандартных операторов сравнения текстовых данных.

  • Верхняя, нижняя и initcap функции

  • Операторы сопоставления с образцом (регулярные выражения в стиле LIKE, SIMILAR TO и POSIX); локали влияют как на совпадение без учета регистра, так и на классификацию символов по регулярным выражениям класса символов

  • Семейство функций to_char

  • Возможность использовать индексы с предложениями LIKE

Первый пункт (порядок сортировки) о, LC_COLLATEа остальные, кажется, все о LC_CTYPE.

LC_COLLATE

LC_COLLATEвлияет на сравнение между строками. На практике наиболее заметным эффектом является порядок сортировки. LC_COLLATE='C'(или POSIXкоторый является синонимом) означает, что это порядок байтов, который управляет сравнениями, тогда как локаль в language_REGIONформе означает, что культурные правила будут управлять сравнениями.

Пример с французскими именами, выполненный из базы данных UTF-8:

select firstname from (values ('bernard'), ('bérénice'), ('béatrice'), ('boris'))
 AS l(firstname)
order by firstname collate "fr_FR";

Результат:

 имя 
-----------
 Беатрис
 Беренис
 Бернард
 борис

béatriceприходит раньше boris, потому что акцентированный E сравнивается с O, как если бы он был без акцента. Это культурное правило.

Это отличается от того, что происходит с Cлокалью:

select firstname from (values ('bernard'), ('bérénice'), ('béatrice'), ('boris')) 
 AS l(firstname)
order by firstname collate "C";

Результат:

 имя 
-----------
 Бернард
 борис
 Беатрис
 Беренис

Теперь имена с E акцентированы на E в конце списка. Представление байта éв UTF-8 является шестнадцатеричным C3 A9и для oнего 6f. c3больше, чем 6fтак под Cязыком 'béatrice' > 'boris'.

Это не просто акценты. Там более сложные правила с переносами, пунктуацией и такими странными символами, как œ. Странные культурные правила следует ожидать в каждом регионе.

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

В этом случае Cэто рациональный выбор, и он имеет преимущество в том, что он быстрее, потому что ничто не сравнится с чистыми байтовыми сравнениями.

LC_CTYPE

Имея LC_CTYPEнабор для «C» означает , что C функция , как isupper(c)и tolower(c)дать ожидаемые результаты только для символов в диапазоне US-ASCII (то есть, до 0x7F в элементе кода Unicode).

Поскольку функции SQL нравится upper(), lower()или initcap реализуются в Postgres на вершине этих LibC функций, они страдают от этого , как только есть символы не US-ASCII в строках.

Пример:

test=> show lc_ctype;
  lc_ctype   
-------------
 fr_FR.UTF-8
(1 row)

-- Good result
test=> select initcap('élysée');
 initcap 
---------
 Élysée
(1 row)

-- Wrong result
-- collate "C" is the same as if the db has been created with lc_ctype='C'
test=> select initcap('élysée' collate "C");
 initcap 
---------
 éLyséE
(1 row)

Для Cлокали éрассматривается как некатегоризированный символ.

Аналогичные неправильные результаты получаются и с регулярными выражениями:

test=> select 'élysée' ~ '^\w+$';
 ?column? 
----------
 t
(1 row)

test=> select 'élysée' COLLATE "C" ~ '^\w+$';
 ?column? 
----------
 f
(1 row)
Даниэль Верите
источник
Так что, если я правильно понял, у нас возникнет проблема с заказом, даже если вы сделали сервер UTF-8? Я предполагаю, что установка системы LC_CTYPE на UTF-8 или компиляция PostgreSQL в UTF-8 приведет к той же проблеме сравнения, на которую вы указали.
Грегуар Д.
Чтобы расширить это, можно ли будет принудительно сопоставлять запросы, чтобы сравнение было локально правильным?
Грегуар Д.
Да, сравнения отдельных строк могут включать свои собственные правила сортировки, как я делаю в этом ответе collate "C"после order by. Вам решать, нужно ли и где это необходимо вашему приложению. Большинству приложений там все равно.
Даниэль Верите
1
Также обратите внимание, что отдельные столбцы могут иметь COLLATEспецификатор, который отличается от базы данных.
Даниэль Верите
2
Этот ответ действительно для LC_COLLATE, а не LC_CTYPE. LC_CTYPE используется для определения, является ли символ цифрой, буквой, пробелом, пунктуацией и т. Д.
jjanes
10

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

http://www.postgresql.org/message-id/4B4E845F.80906@postnewspapers.com.au

Это не специфическая проблема PostgreSQL, а скорее проблема с настройками по умолчанию для Mac для параметров сортировки. Моя текущая система работает на PostgreSQL 9.3 в OS X El Capitan Version 10.11 и страдает от этой проблемы. Моя система возвращает одинаковые результаты запроса независимо от того, использую ли я сопоставление «fr_FR» или «en_US». Например:

Используя сопоставление «fr_FR»:

select firstname from (values ('bernard'), ('bérénice'), ('béatrice'), ('boris'))
AS l(firstname)
order by firstname collate "fr_FR";

results:
==============
bernard
boris
béatrice
bérénice

Используя сопоставление «en_US»:

select firstname from (values ('bernard'), ('bérénice'), ('béatrice'), ('boris'))
AS l(firstname)
order by firstname collate "en_US";

results:
==============
bernard
boris
béatrice
bérénice

В моей системе параметры сортировки (на уровне операционной системы) такие же для «fr_FR» и «en_US», как показано в оболочке при запуске diff:

cd /usr/share/locale
diff fr_FR.UTF-8/LC_COLLATE en_US.UTF-8/LC_COLLATE

Надеемся, что эта дополнительная информация будет полезна всем, кто читает эту статью и использует PostgreSQL на Mac, который страдает от этой проблемы.

cafecoder905
источник
Как я могу заставить его работать на современных Mac. Вы прошли через что-нибудь, чтобы заставить это работать в вашем Mac?
Динеш Кумар