ロケール(国際化と地域化)

ロケール(国際化と地域化)

NTT オープンソースソフトウェアセンタ 板垣 貴裕

 

PostgreSQL でロケール (国際化と地域化) の設定を行うと、データベース内での文字列処理、日付や通貨の表示、メッセージの言語などを変更することができます。特に PostgreSQL 8.4 では、日本語のメッセージ・カタログが追加されたため、エラーメッセージを日本語化したい場合にはロケールを設定する必要があります。

ただし、C ロケール以外を設定すると、インデックスが使われないなどの性能への影響がある場合もあります。また、特に古いバージョンの PostgreSQL では、誤った設定によりサーバが正しく動作しなくなるケースもありました。

この記事では、ロケール設定の効果を結果例を交えて解説すると共に、トラブルへの対応方法を示します。PostgreSQL で日本語を扱う際に、ロケール設定を決める時の参考にして下さい。

PostgreSQL の利用者の間では「ロケールを C に設定する」ことを「ロケールを設定しない」と表現する場合がありますが、どちらも同じ意味です。initdb コマンドでも --locale=C と --no-locale は同じ意味ですね。

また、ロケール (locale) は、英語の発音はむしろ「ロカール」に近いようです。ただ、日本のコンピュータ業界ではロケールで定着しているようですので、この記事でも「ロケール」の表記で執筆しています。

ロケールの設定方法

ロケール設定の中で、文字種の扱いと文字の並び順の設定 (lc_ctype, lc_collate) は、データベース・クラスタの初期化 (initdb) または データベースの作成 (createdb, 8.4 のみ) 時に指定します。後からは変更できないので注意してください。

PostgreSQL 8.4 では、ロケールの扱いが改良され、データベースごとにロケールが指定できるようになりました。8.3 までは、ロケールはデータベースクラスタで1つだけで、エンコーディングだけがデータベースごとに指定できました。注意としては、initdb とは異なるロケールを指定した際に createdb に失敗した場合は、--template=template0 を指定する必要があることです。

$ createdb ja --locale=japanese
createdb: database creation failed:
  ERROR:  new collation (japanese) is incompatible with
          the collation of the template database (C)
  HINT:  Use the same collation as in the template database,
         or use template0 as template.

$ createdb ja --locale=japanese --template=template0
(成功)

ロケールは「[言語]_[地域].[エンコーディング]」の形で指定します。日本語の場合は [言語] = ja or japanese / [地域] = JP or Japan になります。日本語は日本でしか使われていないので、言語と地域の両方を指定するのは冗長に感じますが、例えば英語の場合には en_GB (イギリス) と en_US (アメリカ) を区別するために必要になります。

また、[エンコーディング] を含んでいることにも注意してください。 これは、ロケールが決まると、必ずそれに一致するエンコーディングを使わなければならないことを示しています。

$ initdb --encoding=UTF-8 --locale=ja_JP.UTF-8
                    ~~~~~                ~~~~~

~~~~~ の部分が同じ値になっていることを必ず確認してください。 さもないと、データベースの操作時にエラーが生じたり、データを破壊してしまう可能性があります。 例外は、encoding が SQL_ASCII の場合と、locale が C または --no-locale とした場合のみです。

古いバージョンの PostgreSQL では、エンコーディングの不一致が生じる設定をしてもエラーにならないという問題がありました。もし以下のような不具合やエラーメッセージに遭遇するようならば、ロケールとエンコーディングの不一致を疑ってみてください。

ロケールとエンコーディングの不一致で生じる不具合
  • 文字列のソート順序がおかしい。
  • 範囲検索で正しい結果が返らない。
  • 「ERROR: invalid memory alloc request size 2147483647」が発生する。エンコーディング不一致のため生じたエラーをチェックしないまま、PostgreSQL がメモリを確保しようとするためです。

日本語を扱う場合、サーバのエンコーディングは UTF8 または EUC_JP になると思いますが、日本語ロケールを使う場合には 表1 の組み合わせを使う必要があります。

表1 : エンコーディングと日本語ロケールの対応表
エンコーディング POSIX Windows
UTF8 ja_JP.utf8 Japanese_Japan.932 (※)
EUC_JP ja_JP.eucJP Japanese_Japan.20932

※ Windows で UTF8 エンコーディングを使う場合のみ、ロケールのエンコーディングにコードページ 932 (SJIS) を使うことができます。これは Windows の C ライブラリが UTF8 でのロケール処理をサポートしていないためで、PostgreSQL 側で専用の対応を行っています。

ロケール設定の種類

ロケール設定は複数あり、それぞれ設定できるタイミングや効果が違います。データベースの用途によっては、ロケール設定の一部だけを変更することが適している用途もあります。

設定の確認方法

PostgreSQL では lc_xxx の名前の設定パラメータでロケールを設定します。すでにサーバが動作しているのであれば、以下のSQLで設定を確認できます。

=# SELECT name, setting, context FROM pg_settings WHERE name LIKE 'lc%';
    name     | setting |  context
-------------+---------+-----------
 lc_collate  | C       | internal
 lc_ctype    | C       | internal
 lc_messages | C       | superuser
 lc_monetary | C       | user
 lc_numeric  | C       | user
 lc_time     | C       | user
(6 rows)

上の例では、ロケールは全て C になっています。また、context 列は設定が変更できるタイミングを表しますが、lc_collate と lc_ctype は internal (変更できない) になっています。これらは変更すると、インデックスのキーの順序など既存のデータに影響を及ぼすため、データベース初期化後は変更できません。その他のパラメータは、メッセージ / 日付 / 通貨といった画面表示に関わる設定なので、動作中でも変更することができます。

設定の意味

ただし、PostgreSQL ではロケール関連の処理を全てプラットフォームが用意する C ライブラリ (glibc, msvcrt 等) に任せているため、結果は環境依存で変化する場合があります。下記の動作例は Windows XP での PostgreSQL 8.4dev での結果です。他の環境やバージョンでは結果が異なるかもしれません。

(凡例)
c=#  : Cロケールでの結果
en=# : 英語ロケール (en, english) での結果
ja=# : 日本語ロケール (ja, japanese) での結果
lc_collate
文字列の並び順の設定です。Cロケールではエンコーディングに依存する文字のバイナリ値を元に並び順を決めますが、日本語ロケールでは辞書順(カタカナ→ひらがな, 清音→濁音→半濁音) の順にソートされています。
c=# SELECT substring(t, 1, 1)::bytea AS bin, t FROM tbl ORDER BY t;
     bin      |      t
--------------+--------------
 \343\201\257 | は(清音)
 \343\201\260 | ば(濁音)
 \343\201\261 | ぱ(半濁音)
 \343\203\217 | ハ(清音)
 \343\203\220 | バ(濁音)
 \343\203\221 | パ(半濁音)

ja=# SELECT substring(t, 1, 1)::bytea AS bin, t FROM tbl ORDER BY t;
     bin      |      t
--------------+--------------
 \343\203\217 | ハ(清音)
 \343\201\257 | は(清音)
 \343\203\220 | バ(濁音)
 \343\201\260 | ば(濁音)
 \343\203\221 | パ(半濁音)
 \343\201\261 | ぱ(半濁音)
lc_ctype
文字の種類の設定です。以下の例は、半角の 'a', 'A' と全角の 'a', 'A' の小文字化をしています。
c=# SELECT lower('aAaA');
 lower
--------
 aaaA    (半角アルファベットのみ変換)

ja=# SELECT lower('aAaA');
 lower
--------
 aaaa    (半角/全角共に変換)
lc_messages
メッセージの設定です。PostgreSQL 8.4 から日本語メッセージが追加されたため、この設定を 'ja' にするとエラーメッセージが日本語になります。ローカライズされたメッセージを表示させるためには、PostgreSQL を --enable-nls オプション付きでビルドする必要があります。既にインストール済みであれば、pg_config の実行結果の中の CONFIGURE で確認できます。
c=# foo;
ERROR:  syntax error at or near "foo"
LINE 1: foo;
        ^
ja=# foo;
ERROR:  "foo"またはその近辺で構文エラー
LINE 1: foo;
        ^
lc_monetary
通貨の書式です。money 型の出力方式や解釈に影響します。
en=# SELECT '123456'::money;
    money
-------------
 $123,456.00

ja=# SELECT '123456'::money;
  money
----------
 ¥123,456
lc_numeric
数値の書式です。小数点と桁区切り記号については、英語や日本語ではそれぞれ (. / ,) ですが、ヨーロッパ圏では (, / .) と逆になるようです。
=# CREATE OR REPLACE FUNCTION test(text, numeric) RETURNS text AS
   $$
     UPDATE pg_settings SET setting = $1 WHERE name = 'lc_numeric';
     SELECT to_char($2, '9G999G999D999');
   $$
   LANGUAGE sql STRICT;
=# SELECT locale, test(locale, 1234568.89) FROM (VALUES
     ('english'), ('japanese'), ('italian'), ('german') -- Windows
--   ('en_US'), ('ja_JP'), ('it_IT'), ('de_DE') -- Linux
   ) AS t(locale);
 
  locale  |      test
----------+----------------
 english  |  1,234,568.890
 japanese |  1,234,568.890
 italian  |  1.234.568,890
 german   |  1.234.568,890
(4 rows)
lc_time
日付の書式です。to_char() の書式で TMMonth や TMDay を指定した場合の月名や曜日名に影響します。
en=# SELECT to_char('2009-01-01'::timestamp, 'YYYY-TMMonth-DD (TMDay)');
          to_char
----------------------------
 2009-January-01 (Thursday)

ja=# SELECT to_char('2009-01-01'::timestamp, 'YYYY-TMMonth-DD (TMDay)');
       to_char
----------------------
 2009-1月-01 (木曜日)

lc_ctype, lc_collate について再整理

ここまでで既にロケールの効果は見てもらえたと思いますが、もう一度 lc_ctype, lc_collate の2つに注目して整理したいと思います。これらはデータベースの初期化後には変更できないため、トラブルの元になることがあります。特に、C ロケール以外の場合に疑問になりやすい点について、回避方法を示します。

一番の注意:「万能な」ロケールは無い

ロケールを設定する際に最も注意が必要なのは、世界中の言語や文化すべてを包括するロケールは存在しないことです。エンコーディングに関しては、ユニコード (UTF-8) を使うことで、多くの言語を概ね保持することができます。しかし、ユニコードであっても、例えばロケールを ja_JP.utf8 と設定してしまうと「日本語の文字以外も入れることはできるが、ロケール設定が適切に動作するのは日本語のみ」という状態になります。

特に PostgreSQL は、データベース・クラスタまたはデータベース単位でのロケール設定しかサポートしていないため、多国語を扱う上で柔軟性が犠牲になることに考慮が必要です。

利点と欠点

まず、ロケールを C 以外に設定した場合の利点 (◎) と欠点 (×) を一覧にします。

◎ 辞書順で文字列をソートできる
ひらがな、カタカナ、濁音などのソート順序が辞書順になります。ただ、もちろん漢字の読み方までは考慮してくれませんので、全てひらがなに統一した「読み仮名」列を用意して比較するほうが確実ではあります。
◎ 文字種の判別が適切になる
いわゆる全角文字の文字種が適切に扱えるようになります。
× 結果がプラットホーム依存
ソート順や文字種の扱いはプラットホーム依存です。異なるOSで (正確には異なるCライブラリで) サーバを動作させた場合、異なる結果になるかもしれません。
× 文字列の比較処理に時間がかかる
C ロケールでは単なるバイト列の比較で済むのに対し、C 以外のロケールでは追加の処理が必要になります。文字列の比較処理やソートの時間が多くかかります。
× LIKE 前方一致検索でインデックスが使われない
インデックスはロケールに基づいた比較を行うよう構成されるため、バイト列比較に基づいた比較が必要な LIKE ではインデックスが使われなくなります。(回避方法は後述します)

続いて、ロケールを C 以外に設定されているデータベースにおいて、C ロケールと同じ結果を得たい場合の対処方法を示します。

Cロケールと同じ比較やソートを行うには

演算子 < の代わりに ~<~ を使います。対応関係を表2に示します。

表2 : ロケール あり/なし での演算子の対応
ロケールあり ロケールなし
< ~<~
<= ~<=~
= = (同じ)
>= ~>=~
> ~>~

ソートの場合には、ORDER BY USING [演算子] 構文を使います。

ja=# SELECT t FROM tbl ORDER BY t USING ~<~;

LIKE でインデックスを使わせるには

普通にインデックスを作成してしまうと、LIKE 前方一致検索でインデックスが使われません。以下では、"SET enable_seqscan = off" 指定しているにも関わらず、他の選択肢が無いので Seq Scan を使ってしまっています。

SET enable_seqscan = off;
ja=# CREATE INDEX idx1 ON tbl (t); -- ロケールあり
ja=# SET enable_seqscan = off;
ja=# EXPLAIN SELECT * FROM tbl WHERE t LIKE 'は%';
                               QUERY PLAN
------------------------------------------------------------------------
 Seq Scan on tbl  (cost=10000000000.00..10000000001.08 rows=6 width=17)
   Filter: (t ~~ 'は%'::text)
(2 rows)

回避方法は、演算子クラス text_pattern_ops を指定することです。普通の使い方ではあまり目にしない構文ですが、デフォルト以外の大小比較ロジックに基づくインデックスを作成する場合に使います。新しく作成した idx2 であれば、LIKE で使われることがわかります。もちろん、idx1 と idx2 の2つのインデックスを保持/管理するコストがかかりますので、ディスクの容量や性能には注意しましょう。

ja=# CREATE INDEX idx2 ON tbl (t text_pattern_ops); -- ロケールなし
ja=# EXPLAIN SELECT * FROM tbl WHERE t LIKE 'は%';
                           QUERY PLAN
-----------------------------------------------------------------
 Index Scan using idx2 on tbl  (cost=0.00..8.27 rows=6 width=17)
   Index Cond: ((t ~>=~ 'は'::text) AND (t ~<~ 'ば'::text))
   Filter: (t ~~ 'は%'::text)
(3 rows)

まとめ

ロケールの設定方法とその効果について確認しました。必ずしも良い面ばかりではありませんので、データベースの用途にあわせて設定を切り替えて使うことになるかと思います。また、lc_ctype, lc_collate 以外のロケール設定はいつでも変更できるので、気軽に試せます。英語のエラーメッセージが苦手な方は、PostgreSQL 8.4 ならば日本語のメッセージに変更すれば安心できるかもしれません(?)

現在の PostgreSQL では、ロケールをデータベースやシステム一括で設定する必要があるため、柔軟性が乏しく使い勝手がよくありません。ただ、開発者の間では、列ごとにロケール設定を行うような改良も検討されているようですので、将来のバージョンに期待しましょう。

関連リンク


(2009年6月16日 公開)