PostgreSQL通过gettext实现对多语言消息的支持。消息的语言是英语还是中文可以通过postgresql.conf中的lc_messages控制。在启动PostgreSQL时,postmaster进程(你看到的进程名可能是postgres)会根据postgresql.conf中的lc_messages值设置进程locale(调用setlocale()),然后再fork出一堆其它进程。但在postmaster读取postgresql.conf之前的一个很短的启动阶段,进程的locale是由系统环境决定的。
贴一个图,可以看的更清楚一点。

PostgreSQL各个进程的启动过程与区域设置:
关于PostgreSQL的本地化消息-LMLPHP

1. 错误消息的语言


根据上面的图,当postgresql.conf中的loacle参数省略时,消息语言受环境loacle影响。下面验证一下。

当前的环境loacle是zh_CN.utf8,但postgresql.conf中没有设置loacle。
  1. -bash-4.1$ grep lc_ postgresql.conf
  2. #lc_messages = 'zh_CN.utf8'            # locale for system error message
  3. #lc_monetary = 'zh_CN.utf8'            # locale for monetary formatting
  4. #lc_numeric = 'zh_CN.utf8'            # locale for number formatting
  5. #lc_time = 'zh_CN.utf8'                # locale for time formatting

  6. -bash-4.1$ locale
  7. LANG=en_US.UTF-8
  8. LC_CTYPE="zh_CN.utf8"
  9. LC_NUMERIC="zh_CN.utf8"
  10. LC_TIME="zh_CN.utf8"
  11. LC_COLLATE="zh_CN.utf8"
  12. LC_MONETARY="zh_CN.utf8"
  13. LC_MESSAGES="zh_CN.utf8"
  14. LC_PAPER="zh_CN.utf8"
  15. LC_NAME="zh_CN.utf8"
  16. LC_ADDRESS="zh_CN.utf8"
  17. LC_TELEPHONE="zh_CN.utf8"
  18. LC_MEASUREMENT="zh_CN.utf8"
  19. LC_IDENTIFICATION="zh_CN.utf8"
  20. LC_ALL=zh_CN.utf8
启动postgres服务

  1. -bash-4.1$ pg_ctl -D `pwd` start
  2. 正在启动服务器进程
  3. -bash-4.1$ < 2014-09-19 20:18:11.582 CST >日志: redirecting log output to logging collector process
  4. < 2014-09-19 20:18:11.582 CST >提示: Future log output will appear in directory "pg_log".
控制台输出的消息和环境locale一致,是中文。
再看下日志文件,也是中文的。
  1. -bash-4.1$ tail -f pg_log/postgresql-Fri.log
  2. < 2014-09-19 20:18:11.584 CST >日志: 数据库上次关闭时间为 2014-09-19 20:17:42 CST
  3. < 2014-09-19 20:18:11.588 CST >日志: 数据库系统准备接受连接
  4. < 2014-09-19 20:18:11.588 CST >日志: 已启动autovacuum

用psql连接上去,发现lc_messages是空的,其他几个lc_*是C,并且获得的服务端消息语言也是继承了服务端环境loacle(即中文消息)。

  1. -bash-4.1$ psql
  2. psql (9.3.4)
  3. 输入 "help" 来获取帮助信息.

  4. postgres=# show lc_messages;
  5.  lc_messages
  6. -------------
  7.  
  8. (1 行记录)

  9. postgres=# show lc_monetary;
  10.  lc_monetary
  11. -------------
  12.  C
  13. (1 行记录)

  14. postgres=# show lc_numeric;
  15.  lc_numeric
  16. ------------
  17.  C
  18. (1 行记录)

  19. postgres=# show lc_time;
  20.  lc_time
  21. ---------
  22.  C
  23. (1 行记录)

  24. postgres=# ss;
  25. 错误: 语法错误 在 "ss" 或附近的
  26. 第1行ss;
  27.      ^

设置消息为C,服务端过来的消息变成了英文。

  1. postgres=# set lc_messages="C";
  2. SET
  3. postgres=# ss2;
  4. ERROR: syntax error at or near "ss2"
  5. 第1行ss2;
  6.      ^

日志中的消息也变成了英文(前一条中文消息是上次的)。

  1. < 2014-09-19 20:23:03.068 CST >错误: 语法错误 在 "ss" 或附近的 第 1 个字符处
  2. < 2014-09-19 20:23:03.068 CST >语句: ss;
  3. < 2014-09-19 20:33:30.811 CST >ERROR: syntax error at or near "ss2" at character 1
  4. < 2014-09-19 20:33:30.812 CST >STATEMENT: ss2

但是用set只影响这个会话。开个新的会话,还是中文的消息。

  1. -bash-4.1$ psql
  2. psql (9.3.4)
  3. 输入 "help" 来获取帮助信息.

  4. postgres=# ss3;
  5. 错误: 语法错误 在 "ss3" 或附近的
  6. 第1行ss3;
  7.      ^

如果想全局修改消息语言,可以修改postgresql.conf后用pg_ctl reload动态加载。

  1. -bash-4.1$ vi postgresql.conf
  2. lc_messages = 'C'
  3. -bash-4.1$ pg_ctl -D `pwd` reload
  4. server signaled

服务端日志已经切换到英文了

  1. < 2014-09-19 20:41:49.012 CST >日志: 接收到 SIGHUP, 重载配置文件
  2. < 2014-09-19 20:41:49.013 CST >LOG: parameter "lc_messages" changed to "C"

查看之前一直打开着的会话,也已经切换到英文了。

  1. postgres=# ss4;
  2. ERROR: syntax error at or near "ss4"
  3. 第1行ss4;
  4.      ^
*)postgresql.conf中的有些参数reload后只对以后创建的postgres进程生效,但lc_messages不是这样,可以立即生效。


2. 错误消息的编码


总的来说消息的语言决定于postgresql.conf中的lc_messages,postgresql.conf未指定时取决于启动postgres的环境loacle。那么消息的编码呢?
gettext取得的消息的编码取决于LC_CTYPE,而不是LC_MESSAGES。所以,在下面的设置中,本地化消息的编码是GBK而不是UTF8。

  1. [chenhj@hanode1 ~]$ export LC_ALL=
  2. [chenhj@hanode1 ~]$ export LC_CTYPE=zh_CN.gbk
  3. [chenhj@hanode1 ~]$ export LC_MESSAGES=zh_CN.utf8
  4. [chenhj@hanode1 ~]$ ls xx
  5. ls: ?·¨·??x: ???????倂?

为了便于区分,我的终端软件编码一直设置为UTF8,所以当你看到了乱码,就表示它不是UTF8编码,后面的测试也是一样
根据之前的图,postmaster和辅助进程的LC_TYPE应该取决于环境locale,postgres进程的LC_TYPE则取决于数据库的编码。下面也实测一下。

修改postgresql.conf中的lc_messages值为zh_CN.utf8,环境变量LC_CTYPE设为zh_CN.gb2312。

  1. -bash-4.1$ vi postgresql.conf
  2. lc_messages = 'zh_CN.utf8'
  3. -bash-4.1$ export LC_CTYPE="zh_CN.gb2312"
  4. -bash-4.1$ locale
  5. LANG=en_US.UTF-8
  6. LC_CTYPE=zh_CN.gb2312
  7. LC_NUMERIC="en_US.UTF-8"
  8. LC_TIME="en_US.UTF-8"
  9. LC_COLLATE="en_US.UTF-8"
  10. LC_MONETARY="en_US.UTF-8"
  11. LC_MESSAGES="en_US.UTF-8"
  12. LC_PAPER="en_US.UTF-8"
  13. LC_NAME="en_US.UTF-8"
  14. LC_ADDRESS="en_US.UTF-8"
  15. LC_TELEPHONE="en_US.UTF-8"
  16. LC_MEASUREMENT="en_US.UTF-8"
  17. LC_IDENTIFICATION="en_US.UTF-8"
  18. LC_ALL=

重启服务器,输出的消息中出现了个gb2312编码的字符(这部分消息是postmaster进程输出的)。

  1. -bash-4.1$ pg_ctl -D `pwd` restart
  2. waiting for server to shut down.... done
  3. server stopped
  4. server starting
  5. -bash-4.1$ < 2014-09-19 21:09:25.882 CST >??: redirecting log output to logging collector process
  6. < 2014-09-19 21:09:25.882 CST >??: Future log output will appear in directory "pg_log".

用psql连进来看下,消息是正常的(这部分消息来自postgres进程)。

  1. -bash-4.1$ locale
  2. LANG=en_US.UTF-8
  3. LC_CTYPE="zh_CN.utf8"
  4. LC_NUMERIC="zh_CN.utf8"
  5. LC_TIME="zh_CN.utf8"
  6. LC_COLLATE="zh_CN.utf8"
  7. LC_MONETARY="zh_CN.utf8"
  8. LC_MESSAGES="zh_CN.utf8"
  9. LC_PAPER="zh_CN.utf8"
  10. LC_NAME="zh_CN.utf8"
  11. LC_ADDRESS="zh_CN.utf8"
  12. LC_TELEPHONE="zh_CN.utf8"
  13. LC_MEASUREMENT="zh_CN.utf8"
  14. LC_IDENTIFICATION="zh_CN.utf8"
  15. LC_ALL=zh_CN.utf8

  16. -bash-4.1$ psql
  17. psql (9.3.4)
  18. 输入 "help" 来获取帮助信息.

  19. postgres=# ss5;
  20. 错误: 语法错误 在 "ss5" 或附近的
  21. 第1行ss5;
  22.      ^

日志中的消息也是UTF8。而且即使像这样修改客户端的编码为gb2312。

  1. -bash-4.1$ export LC_ALL=zh_CN.gb2312
  2. -bash-4.1$ psql
  3. psql (9.3.4)
  4. ?? "help" 4?衰?х?.

  5. postgres=# ss6;
  6. ??: ?·¨?? ? "ss6" ?载
  7. ???ss6;
  8.      ^
服务端日志中消息还是UTF8。(前面3条消息是postmaster进程报的消息,为gb2312编码)

  1. < 2014-09-19 21:09:25.884 CST >??: ?????ι??±?厪 2014-09-19 21:09:24 CST
  2. < 2014-09-19 21:09:25.890 CST >??: ??????±??????
  3. < 2014-09-19 21:09:25.891 CST >??: ????autovacuum
  4. < 2014-09-19 21:14:34.095 CST >错误: 语法错误 在 "ss5" 或附近的 第 1 个字符处
  5. < 2014-09-19 21:14:34.095 CST >语句: ss5;
  6. < 2014-09-19 21:26:52.511 CST >错误: 语法错误 在 "ss6" 或附近的 第 1 个字符处
  7. < 2014-09-19 21:26:52.511 CST >语句: ss6

创建一个gb2312编码的数据库,连上去。错误消息就是gb2312了。

  1. -bash-4.1$ export LC_ALL=zh_CN.utf8
  2. -bash-4.1$ createdb -T template0 -E EUC_CN --locale=C euccn
  3. -bash-4.1$ psql euccn
  4. psql (9.3.4)
  5. 输入 "help" 来获取帮助信息.

  6. euccn=# ss7;
  7. 错误: 语法错误 在 "ss7" 或附近的
  8. 第1行ss7;
  9.      ^
日志中的消息也是gb2312。

  1. < 2014-09-19 21:38:42.203 CST >语句: CREATE DATABASE euccn ENCODING 'EUC_CN' LC_COLLATE 'C' LC_CTYPE 'C';
  2.     
  3. < 2014-09-19 21:40:31.478 CST >??: ?·¨?? ? "ss7" ?载? 1 ?薷
  4. < 2014-09-19 21:40:31.478 CST >??? ss7

3. 结论

输出本地语言消息时服务端日志文件中可能会混合不同编码的消息。为了避免这种事发生,需要注意以下两点
1)同一数据库集群(cluster)中的多个数据库使用同一种编码
2)启动数据库时的环境locale(LC_CTYPE)也使用相同的编码



10-18 01:42