Какое влияние оказывает 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, который страдает от этой проблемы.


Как я могу заставить его работать на современных Mac. Вы прошли через что-нибудь, чтобы заставить это работать в вашем Mac?
— Динеш Кумар
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.