PostgreSQL本地化

本地化的概念

本地化的目的是支持不同国家、地区的语言特性、规则。比如拥有本地化支持后,可以使用支持汉语、法语、日语等等的字符集。除了字符集以外,还有字符排序规则和其他语言相关规则的支持,例如我们知道(‘a’,‘b’)该如何排序,那么(‘a’,‘A’)和(‘啊’,‘阿’)又该如何排序?
如果通过google去搜本地化、字符集、collation相关信息,可能会得到一些复杂又遥远的知识。最好的老师还是在这里插入图片描述

本地化知识点总共分为3个部分:locale本地化支持、collation校验、字符集。

locale

pg的本地化由由操作系统提供,需要检查操作系统是否支持locale -a。在初始化数据库时可指定locale

initdb --locale=en_US

也可以单独设置本地化子类:字符串排序、字符归类方法、数值格式、日期格式、时间格式、货币格式等

initdb --locale=zh_CN --lc-monetary=en_US

所有本地化子类:

本地化子类规则
LC_COLLATEString sort order
LC_CTYPECharacter classification (What is a letter? Its upper-case equivalent?)
LC_MESSAGESLanguage of messages
LC_MONETARYFormatting of currency amounts
LC_NUMERICFormatting of numbers
LC_TIMEFormatting of dates and times

这些子类又可以分为两部分,其中lc_messages,lc_monetary,lc_numeric,lc_time在初始化后,可以通过参数进行调整。LC_COLLATE,LC_CTYPE属于collation,详见collation的调整。
locale设置会影响如下行为:

  • Sort order in queries using ORDER BY or the standard comparison operators on textual data
  • The upper, lower, and initcap functions
  • Pattern matching operators (LIKE, SIMILAR TO, and POSIX-style regular expressions); locales affect both case insensitive matching and the classification of characters by character-class regular expressions
  • The to_char family of functions
  • The ability to use indexes with LIKE clauses

COLLATION

collation是字符排序的顺序和字符分类行为。一些数据库操作符依赖collation,比如order by、lower, upper、initcap 、to_char等等。
使用如下SQL查询系统表pg_collation,来获取字符集支持的LC_COLLATE和LC_CTYPE信息

 select pg_encoding_to_char(collencoding) as encoding,collname,collcollate,collctype from pg_collation where collname in ('default','C','POSIX','en_US.utf8','zh_CN.utf8','zh_CN.gb2312','zh_SG.gb2312') ;encoding |   collname   | collcollate  |  collctype   
----------+--------------+--------------+--------------| default      |              | | C            | C            | C| POSIX        | POSIX        | POSIXUTF8     | en_US.utf8   | en_US.utf8   | en_US.utf8EUC_CN   | zh_CN.gb2312 | zh_CN.gb2312 | zh_CN.gb2312UTF8     | zh_CN.utf8   | zh_CN.utf8   | zh_CN.utf8EUC_CN   | zh_SG.gb2312 | zh_SG.gb2312 | zh_SG.gb2312

encoding是字符集,collname为collation的名字

  • encoding 为空时,表示这个 collation 支持所有的字符集
  • default, C, POSIX是所有平台都支持的collation,由libc提供,其他collation取决于操作系统是否支持(locale -a)
  • default表示使用建库时的collation,可通过\l查看
  • C语义上等价于POSIX,但是PG仍然认为他们是不同的collation。他们的字符都以ASCII码对比,严格按照字节序比对大小。
=> SELECT 'a' COLLATE "C" < 'b'  COLLATE "POSIX" ;
ERROR:  42P21: collation mismatch between explicit collations "C" and "POSIX"
LINE 1: SELECT 'a' COLLATE "C" < 'b'  COLLATE "POSIX" ;
LOCATION:  merge_collation_state, parse_collate.c:834
  • UTF8是最常见的字符集,我们最常见的语言环境是en_US和zh_CN
  • 可以通过CREATE COLLATION ...创建自定义的collation。不过LC_COLLATE和LC_CTYPE不同的情况非常少见

LC_COLLATE

LC_COLLATE影响字符比对(排序、字符操作等等)
collate子句可以转化表达式的collation:

expr COLLATE collation

注意这里指定的是collation,不是lc_collate。如果没有显示指定collation,数据库默认使用字段的collation,如果字段没有指定collation,使用database的默认collation。

不同的collation排序测试:

 select col1 from (values ('a'), ('A'), ('啊'), ('阿')) 
->  AS l(col1)
-> order by col1 collate "C";col1 
------Aa啊阿select col1 from (values ('a'), ('A'), ('啊'), ('阿')) 
->  AS l(col1)
-> order by col1 collate "en_US.utf8";col1 
------aA啊阿select col1 from (values ('a'), ('A'), ('啊'), ('阿')) 
->  AS l(col1)
-> order by col1 collate "zh_CN.utf8";col1 
------aA阿啊

这3个不同的collation有不同的lc_collate,排序方法应该是不一样的,从结果来看确实是不一样的,出现了3种排序结果。
collation C为什么A<a?
collation C使用的ASCII的编码顺序,ASCII码中大写在小写前面。而en_US.utf8和zh_CN.utf8的英文字母明显不是这个顺序
中文的顺序
同样是utf8字符集,中文环境和英文环境的中文顺序不一样。不同的lc_collate对于不同本地化语言,应该都可以对应到不同的alphabets。其中,lc_collate=C的排序一定是按字节序排的,虽然ASCII没有中文,但是C也可以排序中文,(基本)每个中文都可以对应UTF8的一个编码,而C以其字节序排序。

LC_CTYPE

LC_CTYPE影响字符操作(如upper、initcap等)
如果字符串都是英文,比如是’abcD’,initcap在3种collation下都会转换为’Abcd’,这里不多展示了。
但是加入中文,结果就不一样了:

select initcap('啊aAAa阿bBBb' collate "C");initcap    
--------------啊Aaaa阿Bbbbselect initcap('啊aAAa阿aAAa' collate "en_US.utf8");initcap    
--------------啊aaaa阿aaaaselect initcap('啊aAAa阿aAAa' collate "zh_CN.utf8");initcap    
--------------啊aaaa阿aaaa

LC_CTYPE=C时,initcap把每个非连续英文字符串的首字母大写,而en_US.utf8和zh_CN.utf8只会将首个字符大写(中文就不会变),其他英文字符小写。
initcap中文也许处于需求不明的状况,但是我们可以得出结论:不同的LC_CTYPE会导致initcap等字符敏感函数结果不一样
另外,中文对于大小写不敏感,一些其他本地化语言同样有大小写,不同的LC_CTYPE导致的结果会更复杂。

字符集

字符集基础

PostgreSQL支持不同的字符集character sets(也叫encodings)。字符集于collation是两个概念,但是字符集必须跟LC_CTYPE,LC_COLLATE兼容。就像在pg_collation中看到的那样,C/POSIX支持所有字符集,而其他collation只支持一种字符集(linux系统中)。

PostgreSQL中文相关可用的字符集:
(*collation C由libc库提供,部分collation可以由ICU库提供,需提前编译)

NameDescriptionLanguageServer端是否支持?ICU是否支持?Bytes/CharAliases
BIG5Big Five繁体中文NoNo1–2WIN950, Windows950
EUC_CNExtended UNIX Code-CN简体中文YesYes1–3GB2312
GB18030National Standard中文NoNo1–4
GBKExtended National Standard简体中文NoNo1–2WIN936, Windows936
UTF8Unicode, 8-bitallYesYes1–4Unicode

繁体中文
BIG5是最常见的繁体中文字符集标准。之前是业界标准,后来被录入为国家标准。
简体中文
GB是国标的意思,GB2312、GB18030、GBK都是我国的国家字符集标准。由于生僻字等问题,并经过多年发展产生了一些历史版本,所以标准看上去有多个。
其中EUC_CN全称为 Extended UNIX Code-CN ,其实就是GB2312,但它也不能处理所有罕见字。类似命名的还有EUC_KR,EUC_JP,EUC_TW等等。
国际标准
上面的字符集都是国家标准,他们除了支持英、中外不支持其他语言。而国际标准支持世界上所有语言,这就是unicode国际编码标准(甚至emoji也包含其中 👍)。(还有个著名的国际标准组织ISO也在维护字符集,他俩有交集,这里先忽略ISO)。
由于Unicode编码方案的不同又有UTF-8、UTF-16、UTF-32三种编码方式。

UTF-8编码格式:

字节格式实际编码位码点范围
1字节0xxxxxxx70 ~ 127
2字节110xxxxx 10xxxxxx11128 ~ 2047
3字节1110xxxx 10xxxxxx 10xxxxxx162048 ~ 65535
4字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx2165536 ~ 2097151

UTF8编码是变长的。
0x00-0x7F之间的字符(1字节),UTF-8编码与ASCII(American Standard Code for Information Interchange 美国标准信息交换代码)编码完全相同,所以UTF-8是完全兼容ASCII的。
由于同源、同义、相似性,在unicode中中日韩越使用了同一编码,称为中日韩统一表意文字(或称中日韩越统一表意文字)。
中日韩统一表意文字编码范围为:3400-4DBF/4E00-9FFF/20000-3FFFF

字符集转换

server_encoding与client_encoding不一致,可发生自动转换Server查出来的字符集。设置服务端字和客户端字符集参考设置字符集一节。
中文相关字符集Server/Client可转化表:

Server Character SetAvailable Client Character Sets
BIG5not supported as a server encoding
EUC_CN(GB2312)EUC_CN(GB2312), MULE_INTERNAL, UTF8
GB18030not supported as a server encoding
GBKnot supported as a server encoding
UTF8all supported encodings
GB18030,GBK服务端都不支持,所以其实只有EUC_CN(GB2312)、UTF8能在Server/Client转换。

以上是可以转换的字符集,仍需要CONVERSATION的支持。PG内置了一些转换函数,可通过pg_conversion查看:

Conversion NameSource EncodingDestination Encoding
big5_to_utf8BIG5UTF8
euc_cn_to_utf8EUC_CNUTF8
gb18030_to_utf8GB18030UTF8
gbk_to_utf8GBKUTF8
utf8_to_big5UTF8BIG5
utf8_to_euc_cnUTF8EUC_CN
utf8_to_gb18030UTF8GB18030
utf8_to_gbkUTF8GBK

可通过create conversation语句创建自定义的转换,需指定转换的function。
有些字符集间看上去可以转换,但是server端根本不支持存储这些字符集(如big5、gb18030、gbk),所以也没啥用。我们这里仅需要知道euc_cn和utf8能相互转换就可以了。
没有CONVERSATION是不能发生转换的:

--EUC_CN的database
=> \encoding EUC_KR
EUC_KR: invalid encoding name or conversion procedure not found

字符集转换测试
需要注意客户端的字符集设置(如CRT的 “session”-“Appearance”-“Character encoding”)
至少有3个端有字符集的概念:数据库server、数据库client、UI客户端。CONVERSATION也只能控制:数据库server -> 数据库client
1.server为UTF8的转换测试:

create table zh(col1 varchar(20));
insert into zh values('>'),('阿'),('〇');  --〇 ling是一个中文
--CRT不设置为UTF8中文全是乱码,只有CRT设置UTF8来插入=> show server_encoding;server_encoding 
-----------------UTF8
=> show client_encoding;client_encoding 
-----------------UTF8
--完全没有转换的情况下,UTF8正常展示。此时3端字符集为:UTF8 - UTF8 - UTF8
=> select * from  zh;col1 
------>阿〇--切换数据库client字符集,此时3端字符集为:UTF8 - EUC_CN - UTF8
=> \encoding EUC_CN;  --设置客户端字符集=> select * from zh where col1 in ('阿');
ERROR:  22021: invalid byte sequence for encoding "EUC_CN": 0xe9 0x98
LOCATION:  report_invalid_encoding, mbutils.c:1597
Time: 0.112 ms
=>  select * from zh where col1 in ('〇');
ERROR:  22021: invalid byte sequence for encoding "EUC_CN": 0xe3 0x80
ERROR:  22021: invalid byte sequence for encoding "EUC_CN": 0xe3 0x80
--“阿”和“〇”看上去不能转换为EUC_CN,但不是这样的=>  select * from zh limit 2;col1 
------><B0><A2>
(2 rows)
--第二行即是"阿",数据库server/client看上去转换了字符集,从UTF8转换为了EUC_CN
--但是可能是因为UI客户端问题没有正确显示(此时UI客户端CRT为UTF8)--然而把CRT改成GB2312还是不会正确展示
select * from zh limit 2;col1 
------><B0><A2>
(2 rows)--当查询〇时,数据库直接抛出报错,说明〇不能从UTF8转换为EUC_CN
select * from zh ;
ERROR:  22P05: character with byte sequence 0xe3 0x80 0x87 in encoding "UTF8" has no equivalent in encoding "EUC_CN"
LOCATION:  report_untranslatable_char, mbutils.c:1631

2.server为EUC_CN的转换测试:

=> show server_encoding; --database为EUC_CN字符集
server_encoding 
-----------------
EUC_CN--在EUC_CN库下同样创建一个zh表,此时尝试插入就有问题了 
=> insert into zh values('〇');
ERROR:  22P05: character with byte sequence 0xe3 0x80 0x87 in encoding "UTF8" has no equivalent in encoding "EUC_CN"
LOCATION:  report_untranslatable_char, mbutils.c:1631

同样报错〇不能从UTF8转换为EUC_CN。EUC_CN(GB2312)中文编码不完全与UTF8相同,EUC_CN(GB2312)不是所有中文都包含的,特别是罕见字。

设置locale、collation和字符集

上面已经了解过本地化和字符集设置了,这里做一个汇总

database cluster的locale、collation、字符集

初始化时可设置database cluster的locale和字符集,参考:

initdb -D $DATADIR -E UTF8 --locale=en_US.UTF8 
initdb -D $DATADIR -E UTF8 --locale=en_US.UTF8 --lc_collate=C --lc_ctype=C
initdb -D $DATADIR -E UTF8 --locale=en_US.UTF8 --lc_collate=C --lc_ctype=C --lc-messages=en_US.UTF8 --lc-monetary=en_US.UTF8 --lc-numeric=en_US.UTF8 --lc-time=en_US.UTF8

initdb会创建postgres,template1,和template0三个库。create database语句时默认使用template1创建库。

  • encoding设置字符集;locale设置LC_COLLATE,LC_CTYPE,LC_MESSAGES,LC_MONETARY,LC_NUMERIC,LC_TIME,除非特别指定(如–lc_collate)

  • LC_COLLATE,LC_CTYPE称为collation,还可在database、列、索引上设置。LC_MESSAGES,LC_MONETARY,LC_NUMERIC,LC_TIME为实例参数,可随时修改。

  • encoding只能在初始化、创建database时设置,一旦设置不可修改。

database的collation、字符集

创建database时可设置database的字符集、lc_collate、lc_ctype。

create database ,createdb都可以在创建database时指定字符集,一旦创建就不能修改database的字符集。两个命令都是使用template库来创建database

template又有template0和template1,官方文档有这样一句话:

Another common reason for copying template0 instead of template1 is that new encoding and locale settings can be specified when copying template0, whereas a copy of template1 must use the same settings it does. This is because template1 might contain encoding-specific or locale-specific data, while template0 is known not to.

template1是可写数据的模板库,可能包含本地化过的数据,而template0不能写数据,所以要创建不同的本地化库应使用template0。

而且要显示使用template0,因为不指定的话默认是template1。所以在创建database时没有指定template1且指定其他字符集会报错:

=> create database db_GB2312 ENCODING 'EUC_CN'  LC_COLLATE 'zh_CN.gb2312' LC_CTYPE 'zh_CN.gb2312';
ERROR:  22023: new encoding (EUC_CN) is incompatible with the encoding of the template database (UTF8)
HINT:  Use the same encoding as in the template database, or use template0 as template.

另外,不能在创建database时通过指定locale来设置字符集

=> create database db_GB2312 locale 'zh_CN.gb2312' template 'template0';
ERROR:  22023: encoding "UTF8" does not match locale "zh_CN.gb2312"
DETAIL:  The chosen LC_CTYPE setting requires encoding "EUC_CN".
LOCATION:  check_encoding_locale_matches, dbcommands.c:773

报错表示需要指定LC_CYTPE子选项,把collation相关子选项全部加上仍然报错:

=> create database db_GB2312 LOCALE 'EUC_CN'  LC_COLLATE 'zh_CN.gb2312' LC_CTYPE 'zh_CN.gb2312';
ERROR:  42601: conflicting or redundant options
DETAIL:  LOCALE cannot be specified together with LC_COLLATE or LC_CTYPE.

LOCALE又不能跟LC_CTYPE等子选项一起使用
然后把locale去掉,通过设置字符集、LC_COLLATE、LC_CTYPE 来设置,可以成功。

创建指定字符集的database的正确姿势

  • create database
create database db_GB2312 ENCODING 'EUC_CN'  LC_COLLATE 'zh_CN.gb2312' LC_CTYPE 'zh_CN.gb2312' template 'template0';
  • createdb
    通过cli命令createdb来创建,createdb封装了create database,他俩是等价的:
 createdb -E EUC_CN -T template0 --lc-collate=zh_CN.gb2312 --lc-ctype=zh_CN.gb2312 db_GB2312

查看database字符集:

  1. \l

  2. pg_database

select datname,pg_encoding_to_char(encoding),datcollate,datctype,datlocprovider,daticulocale from pg_database;
  1. show参数

SERVER_ENCODING,LC_COLLATE,LC_CTYPE三个参数都是不可更改的,分别展示当前database的server端字符集、LC_COLLATE、LC_CTYPE

列的collation

collation只跟字符排序、字符函数相关,跟编码不相关。在没有索引的情况下,修改列的collation相当于只是在调整这个列的default排序输出,有索引的情况下会重建索引。不指定列的collation默认与database一致。

建表时指定collation:(注意有些字段类型是un-collatable的,比如int)

create table t1(col1 varchar(10) collate "en_US.utf8");
alter table t1 alter column col1 type varchar(10) collate "C";

注意:alter table不修改长度是不会重建表的,但是一定会重建索引

查看列的默认collation

1. \d+ t12. information_schema.columns
select table_catalog,table_schema,table_name,column_name,collation_name from  information_schema.columns where table_name='t1';3. pg_attribute
select a.attrelid::regclass,a.attname,a.attcollation,c.collname,c.collcollate,c.collctype from pg_attribute a left join pg_collation c on a.attcollation=c.oid  where a.attrelid::regclass='tlzl'::regclass and a.attcollation<>0;

推荐方式3查看。\d+ ,information_schema.columns虽然能看到collname,但是collname不是唯一的。只有方式3可以看到collate,ctype

指定collate和查看pg_attribute的测试:

create table tlzl(col1 varchar(10) ,col2 varchar(10) collate "C",col3 varchar(10) collate "zh_CN",col4 varchar(10) collate "en_US.utf8"
);
 --列的collation相当于给列打上默认排序的标记,也看不到具体是哪个collate和ctype
db_utf8_c=>                                          Table "public.tlzl"Column |         Type          | Collation  | Nullable | Default | Storage  | Compression | Stats target | Description 
--------+-----------------------+------------+----------+---------+----------+-------------+--------------+-------------col1   | character varying(10) |            |          |         | extended |             |              | col2   | character varying(10) | C          |          |         | extended |             |              | col3   | character varying(10) | zh_CN      |          |         | extended |             |              | col4   | character varying(10) | en_US.utf8 |          |         | extended |             |              | 
--collname和collate,ctype不是一一对应的,上面的col3 zh_CN看不出来collate是哪个
db_utf8_c=> select pg_encoding_to_char(collencoding) as encoding,collname,collcollate,collctype from pg_collation where collname like 'zh_CN%';encoding |   collname   | collcollate  |  collctype   
----------+--------------+--------------+--------------EUC_CN   | zh_CN        | zh_CN        | zh_CNEUC_CN   | zh_CN.gb2312 | zh_CN.gb2312 | zh_CN.gb2312UTF8     | zh_CN.utf8   | zh_CN.utf8   | zh_CN.utf8UTF8     | zh_CN        | zh_CN.utf8   | zh_CN.utf8--pg_attribute展示比\d+准确
db_utf8_c=> select a.attrelid::regclass,a.attname,a.attcollation,c.collname,c.collcollate,c.collctype from pg_attribute a left join pg_collation c on a.attcollation=c.oid  where a.attrelid::regclass='tlzl'::regclass and a.attcollation<>0;attrelid | attname | attcollation |  collname  | collcollate | collctype  
----------+---------+--------------+------------+-------------+------------tlzl     | col1    |          100 | default    |             | tlzl     | col2    |          950 | C          | C           | Ctlzl     | col4    |        12562 | en_US.utf8 | en_US.utf8  | en_US.utf8tlzl     | col3    |        13200 | zh_CN      | zh_CN.utf8  | zh_CN.utf8
--此时才知道,col3 zh_CN的collate是zh_CN.utf8 

修改列的collate重写测试:

--给字段加索引,看看重写情况
db_utf8_c=> create index idxcol4 on tlzl(col4);
CREATE INDEXdb_utf8_c=>  select pg_relation_filepath('tlzl') TableRelid, pg_relation_filepath('idxcol4') IndexRelid; tablerelid    |    indexrelid    
------------------+------------------base/40996/41006 | base/40996/41015db_utf8_c=> alter table tlzl alter column col4 type varchar(10) collate "C";
ALTER TABLE
db_utf8_c=> select pg_relation_filepath('tlzl') TableRelid, pg_relation_filepath('idxcol4') IndexRelid; tablerelid    |    indexrelid    
------------------+------------------base/40996/41006 | base/40996/41016
--表没有重写,索引重写了

列的collation只是标记,修改列的collation不会重写表,但是如果其上有索引,那么会重写这个索引(有时候不会见下面一节)。

索引的collation

在创建索引时,如不显示指定索引的collation,那么索引会使用列上声明的collation。

创建索引时显示使用collation:

create index idx_C on tlzl(col3 collate "C");  

另外,索引还可以以text_pattern_ops,varchar_pattern_ops,bpchar_pattern_ops来创建,此时的索引不依赖collation的规则,而是一个字符一个字符的对比

The difference from the default operator classes is that the values are compared strictly character by character rather than according to the locale-specific collation rules.

CREATE INDEX test_index ON test_table (col varchar_pattern_ops);

其实这种索引跟collation不是完全无关,索引一定有一个排序规则,这种索引的排序规则看上去跟C一致。参考like不走索引一节

查看索引的collation:

\d+ --\d+会展示指定过collate的索引,如果没有的话使用的是列默认索引
db_utf8_c=> \d+ tlzlTable "public.tlzl"Column |         Type          | Collation  | Nullable | Default | Storage  | Compression | Stats target | Description 
--------+-----------------------+------------+----------+---------+----------+-------------+--------------+-------------col1   | character varying(10) |            |          |         | extended |             |              | col2   | character varying(10) | C          |          |         | extended |             |              | col3   | character varying(10) | zh_CN      |          |         | extended |             |              | col4   | character varying(10) | en_US.utf8 |          |         | extended |             |              | 
Indexes:"idx_c" btree (col3 COLLATE "C")"idxcol4" btree (col4)
Access method: heap

通过pg_index查看更为清晰:(pg_index的indcollation类型是oidvector的,不能直接转化为oid,查起来麻烦点)

db_utf8_c=>  select indcollation,indexrelid::regclass from pg_index where indexrelid::regclass ='idx_C'::regclass;indcollation | indexrelid 
--------------+------------950          | idx_cdb_utf8_c=> select oid,pg_encoding_to_char(collencoding) as encoding,collname,collcollate,collctype from pg_collation where oid=950;oid | encoding | collname | collcollate | collctype 
-----+----------+----------+-------------+-----------950 |          | C        | C           | C

另外,不能通过alter index改变索引的collation,只能删除重建

测试:指定过索引collate后,修改列的collate是否会重写索引?

db_utf8_c=> select pg_relation_filepath('tlzl') TableRelid, pg_relation_filepath('idxcol4') IndexRelid4,pg_relation_filepath('idx_c') IndexRelidC; tablerelid    |   indexrelid4    |   indexrelidc    
------------------+------------------+------------------base/40996/41020 | base/40996/41023 | base/40996/41024
(1 row)db_utf8_c=> alter table tlzl alter column col3 type varchar(10) collate "en_US.utf8";
ALTER TABLE
db_utf8_c=> select pg_relation_filepath('tlzl') TableRelid, pg_relation_filepath('idxcol4') IndexRelid4,pg_relation_filepath('idx_c') IndexRelidC; tablerelid    |   indexrelid4    |   indexrelidc    
------------------+------------------+------------------base/40996/41020 | base/40996/41023 | base/40996/41024   --idx_c的relfileid没有变

如果指定过索引的collate,修改其字段默认collate,不会重新索引。

客户端的字符集

客户端设置与database不同的字符集,会发生字符集转换,也可能转换不成功,具体参考字符集转换一节。

服务端的字符集在创建database后无法改变,client的字符集可随时调整。

Client字符集设置方法很多:

  • 直接在客户端设置
\encoding UTF8  --仅psql支持
SET CLIENT_ENCODING TO UTF8;  --session级修改参数
SET NAMES UTF8;  --sql标准
  • 设置环境变量PGCLIENTENCODING
  • 设置client_encoding服务端配置参数

优先级:客户端设置>环境变量PGCLIENTENCODING>client_encoding服务端配置参数

查看client字符集:

\encoding   --仅psql支持
SHOW client_encoding;

表达式collate

表达式加collate会覆盖表达式原本的collation,相当于指定了排序collation。

需在表达式的最后加collate关键字:

expr COLLATE collation--例如
select * from tab1 order by name COLLATE "C";

排序和collate索引选择详见排序结果问题一节。

MORE

概念整理

PostgreSQL本地化有三个重要概念:字符集、locale、collation,需要弄清他们的关系。

字符集在服务端的设置非常重要,只能在初始化和建db时指定,建库后不可修改。字符集选择直接影响编码方式,collation并不是,但是他俩之间有依赖关系。locale同样可以在初始化时指定,其中collation可在建库时指定,也可以单独指定列的collation,注意他们只是默认值。只有在建索引时指定collation,会影响其真正的存储顺序。不同的collation是无法使用索引的,即使他们同源。

client字符集和LC_MASSAGES等4个参数都比较简单,可直接修改参数,与数据存储无关。

在这里插入图片描述

排序结果问题

因为utf8是最常见的字符集,我们测试utf相关的collation排序

create database db_UTF8 ENCODING 'UTF8'  template 'template0';  --建一个UTF8的库,collation无所谓
use db_UTF8;
create table tzlz(name varchar(10));
insert into tzlz values('a'),('aa'),('A'),('AA'),('啊'),('阿'),('〇');

不同collation的order by结果:

select name from tzlz where name in ('a','aa','A','AA','啊','阿','〇') order by name;
select name from tzlz where name in ('a','aa','A','AA','啊','阿','〇') order by name collate "C";
select name from tzlz where name in ('a','aa','A','AA','啊','阿','〇') order by name collate "en_US";
select name from tzlz where name in ('a','aa','A','AA','啊','阿','〇') order by name collate "en_US.utf8";
select name from tzlz where name in ('a','aa','A','AA','啊','阿','〇') order by name collate "zh_CN";
select name from tzlz where name in ('a','aa','A','AA','啊','阿','〇') order by name collate "zh_CN.utf8";
顺序defaultCen_USen_US.utf8zh_CNzh_CN.utf8
1Aaa
2aAAaaAA
3AaAAaaaa
4aaaaaaaaAAAA
5AAAAAA
6
7

这里的default是en_US.utf8(字段collation(default)->database collation(en_US.utf8))

🌟 C、en_US.uft8、zh_CN.uft8排序结果都不同

collate和索引扫描测试:

insert into tzlz values(generate_series(1,10000));
create index idxzlz_default on tzlz(name);
create index idxzlz_C on tzlz(name collate "C");
create index idxzlz_enUS_utf8 on tzlz(name collate "en_US.utf8");

使用collate在索引上的优化:

--不加任何collate关键字,简单的索引扫描,不会额外排序
db_utf8_c=> explain select name from tzlz where name in ('a','aa','A','AA','啊','阿','〇') order by name;QUERY PLAN                                    
---------------------------------------------------------------------------------Index Only Scan using idxzlz_default on tzlz  (cost=0.29..30.13 rows=8 width=4)Index Cond: (name = ANY ('{a,aa,A,AA,啊,阿,〇}'::text[]))--谓词加collate转换,可以走到正确的索引
db_utf8=> explain select name  from tzlz  where name collate "C" in ('a','aa','A','AA','啊','阿','〇');QUERY PLAN                                 
---------------------------------------------------------------------------Index Only Scan using idxzlz_c on tzlz  (cost=0.29..30.12 rows=7 width=4)Index Cond: (name = ANY ('{a,aa,A,AA,啊,阿,〇}'::text[]))db_utf8=>  explain select name  from tzlz  where name collate "en_US.utf8" in ('a','aa','A','AA','啊','阿','〇');QUERY PLAN                                     
-----------------------------------------------------------------------------------Index Only Scan using idxzlz_enus_utf8 on tzlz  (cost=0.29..30.12 rows=7 width=4)Index Cond: (name = ANY ('{a,aa,A,AA,啊,阿,〇}'::text[]))--但是collation的名字必须一致
db_utf8=> explain select name  from tzlz  where name collate "en_US" in ('a','aa','A','AA','啊','阿','〇');QUERY PLAN                            
-----------------------------------------------------------------Seq Scan on tzlz  (cost=0.00..232.63 rows=7 width=4)Filter: ((name)::text = ANY ('{a,aa,A,AA,啊,阿,〇}'::text[]))--同时order by也需要加collate转换表达式
--此时使用了正确的索引,但是order by的时候判断为不同的collation(哪怕他们是一样的)
db_utf8=>  explain select name  from tzlz  where name collate "en_US.utf8" in ('a','aa','A','AA','啊','阿','〇') order by name;QUERY PLAN                                        
-----------------------------------------------------------------------------------------Sort  (cost=30.22..30.23 rows=7 width=4)Sort Key: name->  Index Only Scan using idxzlz_enus_utf8 on tzlz  (cost=0.29..30.12 rows=7 width=4)Index Cond: (name = ANY ('{a,aa,A,AA,啊,阿,〇}'::text[]))--where和order by都加上collate转换,可以小选择正确的索引,且不会在发生排序
db_utf8=> explain select name  from tzlz  where name collate "en_US.utf8" in ('a','aa','A','AA','啊','阿','〇') order by name  collate "en_US.utf8";QUERY PLAN                                     
------------------------------------------------------------------------------------Index Only Scan using idxzlz_enus_utf8 on tzlz  (cost=0.29..30.12 rows=7 width=42)Index Cond: (name = ANY ('{a,aa,A,AA,啊,阿,〇}'::text[]))

索引在指定collation后,sql需要显示使用collate关键字转换表达式,即便default与当前collation一致,pg也不会使用到索引。

like不走索引

The drawback of using locales other than C or POSIX in PostgreSQL is its performance impact. It slows character handling and prevents ordinary indexes from being used by LIKE

PostgreSQL原话:使用非C or POSIX会阻止使用普通索引!

db_utf8=>  explain select name  from tzlz  where name  like 'a%';QUERY PLAN                                
--------------------------------------------------------------------------Index Only Scan using idxzlz_c on tzlz  (cost=0.29..4.31 rows=1 width=4)Index Cond: ((name >= 'a'::text) AND (name < 'b'::text))Filter: ((name)::text ~~ 'a%'::text)
(3 rows)db_utf8=>  explain select name  from tzlz  where name collate "en_US.utf8" like 'a%';QUERY PLAN                                
--------------------------------------------------------------------------Index Only Scan using idxzlz_c on tzlz  (cost=0.29..4.31 rows=1 width=4)Index Cond: ((name >= 'a'::text) AND (name < 'b'::text))Filter: ((name)::text ~~ 'a%'::text)

PostgreSQL在索引扫描时把like转化为了>=和<,<还加了一个比输入的值大一号的值,这里就有问题了,collation跟排序强相关,ASCII码中a+1是b,但是汉字又如何?

db_utf8=> explain select name  from tzlz  where name collate "en_US.utf8" like '阿%';QUERY PLAN                                
--------------------------------------------------------------------------Index Only Scan using idxzlz_c on tzlz  (cost=0.29..6.49 rows=1 width=4)Index Cond: ((name >= '阿'::text) AND (name < '陿'::text))Filter: ((name)::text ~~ '阿%'::text)

果然出现了另一个汉字!

如果是全表扫描,不会出现>= <的情况

db_utf8=> drop index idxzlz_c;
DROP INDEX
db_utf8=>  explain select name  from tzlz  where name collate "en_US.utf8" like '阿%';QUERY PLAN                      
------------------------------------------------------Seq Scan on tzlz  (cost=0.00..170.09 rows=1 width=4)Filter: ((name)::text ~~ '阿%'::text)

可以创建一个与collation规则无关的索引(pg官方声称无关)

CREATE INDEX idx_pattern ON tzlz (name varchar_pattern_ops);

来看看他的执行计划

db_utf8=> explain select name  from tzlz  where name like '阿%';QUERY PLAN                                  
-----------------------------------------------------------------------------Index Only Scan using idx_pattern on tzlz  (cost=0.29..6.49 rows=1 width=4)Index Cond: ((name ~>=~ '阿'::text) AND (name ~<~ '陿'::text))Filter: ((name)::text ~~ '阿%'::text)

他还是把大1号的字符串自己生成了,这跟collation一定是有关系的···,看上去就是C

所以可以得出结论:

pg在like使用普通索引时,需要把其转换为>= <,此时必须出现一个比当前字符串大1的值。而collation又与大小强相关,此时只能使用同一collation索引才能保证数据正确。pg选择了非本地化的collation C。

临时解决这个问题最快的办法是新建一个collation C或pattern索引:

create index idxzlz_C on tzlz(name collate "C");
CREATE INDEX idx_pattern ON tzlz (name varchar_pattern_ops);

其他调整各级别的默认collation参考上面的章节。

开发习惯在建索引时不会指定collation,如果不是C或pattern,都走不了like,在加上要选择国际字符集utf8,这样在数据库运维时选择的本地化方式就非常少了。字符集为utf8,collation为C

参考

https://dbafix.com/what-is-the-impact-of-lc_ctype-on-a-postgresql-database/#:~:text=Having%20LC_CTYPE%20set%20to%20%E2%80%98C%E2%80%99%20implies%20that%20C,Postgres%20on%20top%20of%20these%20libc%20functions%2C%20they%E2%80%99re
https://www.postgresql.org/docs/current/charset.html
https://www.bookstack.cn/read/rds-best-pratice/bfc0037fe00d87dc.md
https://help.aliyun.com/zh/rds/apsaradb-rds-for-postgresql/configure-the-collation-of-a-database-on-an-apsaradb-rds-for-postgresql-instance
https://baike.baidu.com/item/%E7%BB%9F%E4%B8%80%E7%A0%81/2985798?fromModule=lemma_inlink&fromtitle=Unicode&fromid=750500
https://baike.baidu.com/item/%E4%B8%AD%E6%97%A5%E9%9F%A9%E8%B6%8A%E7%BB%9F%E4%B8%80%E8%A1%A8%E6%84%8F%E6%96%87%E5%AD%97/1301611?fromModule=lemma_inlink

https://blog.csdn.net/songyundong1993/article/details/128739919

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/70580.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

移动安全测试框架-MobSF WINDOWS 环境搭建

安装python python-3.11.5-amd64.exe 安装Win64OpenSSL-3_1_2.exe 安装VisualStudioSetup.exe github下载安装包 https://github.com/MobSF/Mobile-Security-Framework-MobSF/archive/refs/heads/master.zip GitHub - MobSF/Mobile-Security-Framework-MobSF: Mobile Secur…

亲测可用的1688商品详情API和关键字搜索API,需要自取!

引言 1688是中国最大的B2B电商平台之一&#xff0c;拥有众多的供应商和商品。为了方便用户查找需要的商品&#xff0c;1688提供了关键词搜索商品的API接口。通过该接口开发者可以在自己的系统中调用平台上的数据和功能&#xff0c;例如获取商品信息、下单等操作1。 1688平台提…

mysql 数据库面试题整理

Mysql 中 MyISAM 和 InnoDB 的区别 1、InnoDB 支持事务MyISAM 不支持 2、InnoDB 支持外键MyISAM 不支持 3、InnoDB 是聚集索引&#xff0c;MyISAM 是非聚集索引 4、InnoDB 不保存表的具体行数 5、InnoDB 最小的锁粒度是行锁&#xff0c;MyISAM是表锁 mysql中有就更新&#xf…

gismo程序示例:边长为 8 16 32 的长方体 受均布载荷

文章目录 前言一、一、8*32面 受均布载荷 二、最小的面&#xff08;8*16&#xff09;受均布载荷三、最大的面受均布载荷 前言 只是为方便学习&#xff0c;不做其他用途&#xff0c; 一、 一、8*32面 受均布载荷 /// This is an example of using the linear elasticity solver…

【进阶篇】Redis缓存击穿, 穿透, 雪崩, 污染详解

【进阶篇】Redis缓存穿击, 穿透, 雪崩, 污染详解 文章目录 【进阶篇】Redis缓存穿击, 穿透, 雪崩, 污染详解0. 前言大纲缓存穿击缓存穿透缓存雪崩缓存污染 1. 什么是缓存穿透&#xff1f;1.1. 发生原因1.2. 导致问题1.3. 解决方案 2. 什么是缓存击穿3.1. 发生原因3.1. 解决方案…

Spring-Cloud-Openfeign如何传递用户信息?

用户信息传递 微服务系统中&#xff0c;前端会携带登录生成的token访问后端接口&#xff0c;请求会首先到达网关&#xff0c;网关一般会做token解析&#xff0c;然后把解析出来的用户ID放到http的请求头中继续传递给后端的微服务&#xff0c;微服务中会有拦截器来做用户信息的…

PY32F003F18端口复用功能映射

PY32F003F18端口复用功能映射&#xff0c;GPIO引脚可配置为"输入&#xff0c;输出,模拟或复用功能。 一、端口A复用功能映射 端口A复用功能映射表里&#xff0c;每个引脚都有AF0~AF15&#xff0c;修改AF0~AF15的值&#xff0c;就可以将对应复用用能引脚映射到CPU引脚上。…

leetcode 925. 长按键入

2023.9.7 我的基本思路是两数组字符逐一对比&#xff0c;遇到不同的字符&#xff0c;判断一下typed与上一字符是否相同&#xff0c;不相同返回false&#xff0c;相同则继续对比。 最后要分别判断name和typed分别先遍历完时的情况。直接看代码&#xff1a; class Solution { p…

【业务功能篇99】微服务-springcloud-springboot-电商订单模块-生成订单服务-锁定库存

八、生成订单 一个是需要生成订单信息一个是需要生成订单项信息。具体的核心代码为 /*** 创建订单的方法* param vo* return*/private OrderCreateTO createOrder(OrderSubmitVO vo) {OrderCreateTO createTO new OrderCreateTO();// 创建订单OrderEntity orderEntity build…

[HNCTF 2022] web 刷题记录

文章目录 [HNCTF 2022 Week1]easy_html[HNCTF 2022 Week1]easy_upload[HNCTF 2022 Week1]Interesting_http[HNCTF 2022 WEEK2]ez_SSTI[HNCTF 2022 WEEK2]ez_ssrf[HNCTF 2022 WEEK2]Canyource [HNCTF 2022 Week1]easy_html 打开题目提示cookie有线索 访问一下url 发现要求我们…

C语言中计算函数执行时间

要计算C语言程序中某个函数从开始到结束执行的时间&#xff0c;可以使用C标准库中的clock()函数。clock()函数返回程序执行开始到当前时刻的时钟数&#xff0c;除以某个常数&#xff08;CLOCKS_PER_SEC&#xff09;&#xff0c;就可以得到程序执行的秒数。 下面是一个示例代码…

mybatisplus多租户原理略解

概述 当前mybatisPlus版本 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.2</version> </dependency>jdk版本&#xff1a;17 springboot版本&#xff1a;…

深入探讨Kubernetes(K8s)在云原生架构中的关键作用和应用

文章目录 1. 容器化的应用程序管理2. 自动化扩展和负载均衡3. 容器编排和调度4. 存储管理5. 自动化滚动更新6. 多云和混合云部署7. 监控和日志8. 安全9. 社区支持和生态系统10. 未来展望案例 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1…

2023年高教社杯全国大学生数学建模竞赛参赛事项注意

MathClub数模资源&#xff0c;含专属思路 资源链接&#xff1a;点击这里获取众多数模资料、思路精讲、论文模板latex和word、学习书籍等 2023高教社杯数学建模国赛–赛前准备 一年一度的数学建模国赛要来啦&#xff01;&#xff01;&#xff01;小编仔细阅读了比赛官方网站上…

java 实现外观模式

外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它提供了一个简化的接口&#xff0c;用于访问一组相关的接口或子系统。外观模式的主要目的是隐藏复杂的系统结构&#xff0c;提供一个更简单的接口供客户端使用。以下是一个简单的Java示例&…

【请求报错:javax.net.ssl.SSLHandshakeException: No appropriate protocol】

1、问题描述 在请求服务时报错说SSL握手异常协议禁用啥的&#xff0c;而且我的连接数据库的url也加了useSSLfalse javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)2、解决方法 在网上查找了方法…

LeetCode 1004.最大连续1的个数

题目链接 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目解析 硬往题目介绍上边去想的话其实非常困难&#xff0c;如果换种方式思考就会简单许多。 若我们将思想转化为&#xff0c;找出最长的子串(里面含有的0的数量最大为k)&#xff0c;然后返…

QT实现任意阶贝塞尔曲线绘制

bezier曲线在编程中的难点在于求取曲线的系数&#xff0c;如果系数确定了那么就可以用微小的直线段画出曲线。bezier曲线的系数也就是bernstein系数&#xff0c;此系数的性质可以自行百度&#xff0c;我们在这里是利用bernstein系数的递推性质求取&#xff1a; 简单举例 两个…

运维Shell脚本小试牛刀(七):在函数文脚本件中调用另外一个脚本文件中函数|函数递归调用|函数后台执行

运维Shell脚本小试牛刀(一) 运维Shell脚本小试牛刀(二) 运维Shell脚本小试牛刀(三)::$(cd $(dirname $0)&#xff1b; pwd)命令详解 运维Shell脚本小试牛刀(四): 多层嵌套if...elif...elif....else fi_蜗牛杨哥的博客-CSDN博客 Cenos7安装小火车程序动画 运维Shell脚本小试…

华纳云:Linux的底层体系结构是怎样的

Linux操作系统的底层体系结构是一个开源的Unix-like操作系统内核&#xff0c;通常称为Linux内核(Linux Kernel)。下面是Linux底层体系结构的主要组成部分和工作原理&#xff1a; 内核&#xff08;Kernel&#xff09;&#xff1a; Linux的核心部分是内核&#xff0c;它是操作系统…