• PostgreSQL本地化


    本地化的概念

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

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

    locale

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

    initdb --locale=en_US
    
    • 1

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

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

    所有本地化子类:

    本地化子类规则
    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        | POSIX
     UTF8     | en_US.utf8   | en_US.utf8   | en_US.utf8
     EUC_CN   | zh_CN.gb2312 | zh_CN.gb2312 | zh_CN.gb2312
     UTF8     | zh_CN.utf8   | zh_CN.utf8   | zh_CN.utf8
     EUC_CN   | zh_SG.gb2312 | zh_SG.gb2312 | zh_SG.gb2312
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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
    
    • 1
    • 2
    • 3
    • 4
    • UTF8是最常见的字符集,我们最常见的语言环境是en_US和zh_CN
    • 可以通过CREATE COLLATION ...创建自定义的collation。不过LC_COLLATE和LC_CTYPE不同的情况非常少见

    LC_COLLATE

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

    expr COLLATE collation
    
    • 1

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

    不同的collation排序测试:

     select col1 from (values ('a'), ('A'), ('啊'), ('阿')) 
    ->  AS l(col1)
    -> order by col1 collate "C";
     col1 
    ------
     A
     a
     啊
     阿
     select col1 from (values ('a'), ('A'), ('啊'), ('阿')) 
    ->  AS l(col1)
    -> order by col1 collate "en_US.utf8";
     col1 
    ------
     a
     A
     啊
     阿
      select col1 from (values ('a'), ('A'), ('啊'), ('阿')) 
    ->  AS l(col1)
    -> order by col1 collate "zh_CN.utf8";
     col1 
    ------
     a
     A
     阿
     啊
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    这3个不同的collation有不同的lc_collate,排序方法应该是不一样的,从结果来看确实是不一样的,出现了3种排序结果。
    collation C为什么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阿Bbbb
    
    select initcap('啊aAAa阿aAAa' collate "en_US.utf8");
       initcap    
    --------------
     啊aaaa阿aaaa
    
    select initcap('啊aAAa阿aAAa' collate "zh_CN.utf8");
       initcap    
    --------------
     啊aaaa阿aaaa
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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
    
    • 1
    • 2
    • 3

    字符集转换测试
    需要注意客户端的字符集设置(如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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    同样报错〇不能从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
    
    • 1
    • 2
    • 3

    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.
    
    • 1
    • 2
    • 3

    另外,不能在创建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
    
    • 1
    • 2
    • 3
    • 4

    报错表示需要指定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.
    
    • 1
    • 2
    • 3

    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';
    
    • 1
    • createdb
      通过cli命令createdb来创建,createdb封装了create database,他俩是等价的:
     createdb -E EUC_CN -T template0 --lc-collate=zh_CN.gb2312 --lc-ctype=zh_CN.gb2312 db_GB2312
    
    • 1

    查看database字符集:

    1. \l

    2. pg_database

    select datname,pg_encoding_to_char(encoding),datcollate,datctype,datlocprovider,daticulocale from pg_database;
    
    • 1
    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";
    
    • 1
    • 2

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

    查看列的默认collation

    1. \d+ t1
    
    2. 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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    推荐方式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"
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
     --列的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_CN
     EUC_CN   | zh_CN.gb2312 | zh_CN.gb2312 | zh_CN.gb2312
     UTF8     | zh_CN.utf8   | zh_CN.utf8   | zh_CN.utf8
     UTF8     | 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           | C
     tlzl     | col4    |        12562 | en_US.utf8 | en_US.utf8  | en_US.utf8
     tlzl     | col3    |        13200 | zh_CN      | zh_CN.utf8  | zh_CN.utf8
    --此时才知道,col3 zh_CN的collate是zh_CN.utf8 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    修改列的collate重写测试:

    --给字段加索引,看看重写情况
    db_utf8_c=> create index idxcol4 on tlzl(col4);
    CREATE INDEX
    
    db_utf8_c=>  select pg_relation_filepath('tlzl') TableRelid, pg_relation_filepath('idxcol4') IndexRelid; 
        tablerelid    |    indexrelid    
    ------------------+------------------
     base/40996/41006 | base/40996/41015
    
    db_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
    --表没有重写,索引重写了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    索引的collation

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

    创建索引时显示使用collation:

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

    另外,索引还可以以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);
    
    • 1

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

    查看索引的collation:

    \d+ --\d+会展示指定过collate的索引,如果没有的话使用的是列默认索引
    db_utf8_c=> \d+ tlzl
                                                      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 |             |              | 
    Indexes:
        "idx_c" btree (col3 COLLATE "C")
        "idxcol4" btree (col4)
    Access method: heap
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    通过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_c
    
    db_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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    另外,不能通过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没有变
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    客户端的字符集

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

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

    Client字符集设置方法很多:

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

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

    查看client字符集:

    \encoding   --仅psql支持
    SHOW client_encoding;
    
    • 1
    • 2

    表达式collate

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

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

    expr COLLATE collation
    
    --例如
    select * from tab1 order by name COLLATE "C";
    
    • 1
    • 2
    • 3
    • 4

    排序和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'),('啊'),('阿'),('〇');
    
    • 1
    • 2
    • 3
    • 4

    不同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";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    顺序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");
    
    • 1
    • 2
    • 3
    • 4

    使用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[]))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    索引在指定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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    果然出现了另一个汉字!

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

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    CREATE INDEX idx_pattern ON tzlz (name varchar_pattern_ops);
    
    • 1

    来看看他的执行计划

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    他还是把大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);
    
    • 1
    • 2

    其他调整各级别的默认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

  • 相关阅读:
    KiSystemCall64 win10 21h2函数流程分析 3环到0环
    数位DP
    纳米软件分享:芯片电学测试及测试参数指标介绍
    统计学三大相关系数介绍及代码实现
    虚拟机安装
    02 C++ 变量和基本类型
    基于java web技术的班级同学录网站-计算机毕业设计
    央国企信创改造难在何处?先行建设国产身份域管可少走弯路
    二、T100应收管理之订金立帐
    ElementUI之首页导航与左侧菜单
  • 原文地址:https://blog.csdn.net/qq_40687433/article/details/132651899