更新时间:2024-07-01 GMT+08:00
分享

GDS导入/导出类问题

GDS导入/导出容易遇到字符集的问题,特别是不同类型的数据库或者不同编码类型的数据库进行迁移的过程中,往往会导致数据入不了库,严重阻塞数据迁移场景相关业务。

区域支持

区域支持指的是应用遵守文化偏好的问题,包括字母表、排序、数字格式等。区域是在使用initdb创建一个数据库时自动被初始化的。默认情况下,initdb将会按照它的执行环境的区域设置初始化数据库,即系统已经设置好的区域。如果想要使用其他的区域,可以使用手工指定(initdb –locale=xx)。

如果想要将几种区域的规则混合起来,可以使用以下区域子类来控制本地化规则的某些方面。这些类名转换成initdb的选项名来覆盖某个特定分类的区域选择。

表1 区域支持

字段

描述

LC_COLLATE

字符串排序顺序。

LC_CTYPE

字符分类(什么是一个字符?它的大写形式是否等效?)

LC_MESSAGES

消息使用的语言Language of messages。

LC_MONETARY

货币数量使用的格式。

LC_NUMERIC

数字的格式。

LC_TIME

日期和时间的格式。

如果想要系统表现得没有区域支持,可以使用区域C或者等效的POSIX。使用非C或非POSIX区域的缺点是性能影响。它降低了字符处理的速度并且阻止了在LIKE中对普通索引的使用。因此,只能在真正需要的时候才使用它。

一些区域分类的值必须在数据库被创建时的就被固定。不同的数据库可以使用不同的设置,但是一旦一个数据库被创建,就不能在数据库上修改这些区域分类的值。LC_COLLATE和LC_CTYPE就属于上述情形。它们影响索引的排序顺序,因此它们必须保持固定,否则在文本列上的索引将会崩溃。这些分类的默认值在initdb运行时被确定,并且这些值在新数据库被创建时使用,除非在CREATE DATABASE命令中特别指定。其它区域分类可以在任何时候被更改,更改的方式是设置与区域分类同名的服务器配置参数。被initdb选中的值实际上只是被写入到配置文件postgresql.conf中作为服务器启动时的默认值。如果你将这些赋值从postgresql.conf中除去,那么服务器将会从其执行环境中继承该设置。

区域设置特别影响下面的SQL特性:

  • 在文本数据上使用ORDER BY或标准比较操作符的查询中的排序顺序
  • 函数upper、lower和initcap
  • 模式匹配操作符(LIKE、SIMILAR TO和POSIX风格的正则表达式);区域影响大小写不敏感匹配和通过字符类正则表达式的字符分类
  • to_char函数家族

因此,在上述场景遇到查询结果集不一致的情况,就可以猜测可能是字符集问题。

排序规则支持

排序规则特性允许指定每一列甚至每一个操作的数据的排序顺序和字符分类行为。这放松了数据库的LC_COLLATE和LC_CTYPE设置自创建以后就不能更改这一限制。

一个表达式的排序规则可以是"默认"排序规则,它表示数据库的区域设置。一个表达式的排序规则也可能是不确定的。在这种情况下,排序操作和其他需要知道排序规则的操作会失败。

当数据库系统必须要执行一次排序或者字符分类时,它使用输入表达式的排序规则。这会在使用例如ORDER BY子句以及函数或操作符调用(如<)时发生。应用于ORDER BY子句的排序规则就是排序键的排序规则。应用于函数或操作符调用的排序规则从它们的参数得来,具体如下文所述。除比较操作符之外,在大小写字母之间转换的函数会考虑排序规则,例如lower、upper和initcap。模式匹配操作符和to_char及相关函数也会考虑排序规则。

对于一个函数或操作符调用,其排序规则通过检查在执行指定操作时参数的排序规则来获得。如果该函数或操作符调用的结果是一种可排序的数据类型,万一有外围表达式要求函数或操作符表达式的排序规则,在解析时结果的排序规则也会被用作函数或操作符表达式的排序规则。

一个表达式的排序规则派生可以是显式或隐式。该区别会影响多个不同的排序规则出现在同一个表达式中时如何组合它们。当使用一个COLLATE子句时,将发生显式排序规则派生。所有其他排序规则派生都是隐式的。当多个排序规则需要被组合时(例如在一个函数调用中),将使用下面的规则:

  1. 如果任何一个输入表达式具有一个显式排序规则派生,则在输入表达式之间的所有显式派生的排序规则必须相同,否则将产生一个错误。如果任何一个显式派生的排序规则存在,它就是排序规则组合的结果。
  2. 否则,所有输入表达式必须具有相同的隐式排序规则派生或默认排序规则。如果任何一个非默认排序规则存在,它就是排序规则组合的结果。否则,结果是默认排序规则
  3. 如果在输入表达式之间存在冲突的非默认隐式排序规则,则组合被认为是具有不确定排序规则。这并非一种错误情况,除非被调用的特定函数要求提供排序规则的知识。如果它确实这样做,运行时将发生一个错误。

字符集

PG里面的字符集支持各种字符集存储文本,包括单字节字符集,比如ISO 8859系列,以及多字节字符集,比如EUC(扩展Unix编码Extended Unix Code)、UTF-8和Mule内部编码。MPPDB中目前主要使用的字符集包括GBK、UTF-8和LATIN1。所有被支持的字符集都可以被客户端透明地使用,但少数只能在服务器上使用(即作为一种服务器端编码,GBK编码在PG中只是客户端编码,不是服务端编码,MPPDB将GBK引入到服务端编码,这是很多问题的根源)。默认的字符集是在使用initdb初始化PG数据库时选择的。在创建一个数据库实例时可以重载字符集,因此可能会有多个数据库实例并且每一个使用不同的字符集。一个重要的限制是每个数据库的字符集必须和数据库LC_CTYPE(字符分类)和LC_COLLATE (字符串排序顺序)设置兼容。对于C或POSIX,任何字符集都是允许的,但是对于其他区域只有一种字符集可以正确工作。不过,在Windows上UTF-8编码可以和任何区域配合使用。

SQL_ASCII设置与其他设置表现得相当不同。如果服务器字符集是SQL_ASCII,服务器把字节值0-127根据ASCII标准解释,而字节值128-255则当作无法解析的字符。如果设置为SQL_ASCII,就不会有编码转换。因此,这个设置基本不是用来声明所使用的指定编码,因为这个声明会忽略编码。在大多数情况下,如果使用了任何非ASCII数据,那么使用SQL_ASCII设置都是不明智的,因为PG将无法帮助你转换或者校验非ASCII字符。

数据库系统支持某种编码,主要涉及三个方面:数据库服务器支持,数据访问接口支持以及客户端工具支持。

  • 数据库服务器字符编码

    数据库服务器支持某种编码,是指数据库服务器能够从客户端接收、存储以及向客户端提供该种编码的字符(包括标识符、字符型字段值),并能将该种编码的字符转换到其它编码(如UTF-8编码转到GBK编码)。

    指定数据库服务器编码:创建数据库时指定:CREATE DATABASE … ENCODING … //可以取ASCII、UTF-8、EUC_CN、……;

    查看数据库编码:show server_encoding。

  • 数据库访问接口编码

    数据库访问接口支持某种编码,是指数据库访问接口要做到能对该种编码的字符进行正确读写,不应出现数据丢失、数据失真等情况。以JDBC接口为例:

    JDBC接口一般根据JVM的file.encoding设置client_encoding:set client_encoding to file_encoding;

    将String转换成client_encoding编码的字节流,传给服务器端:原型String.getBytes(client_encoding) ;

    收到服务器的字节流后,使用client_encoding构造String对象作为getString的返回值给应用程序:原型String(byte[], …, client_encoding)。

  • 客户端编码

    客户端工具支持某种编码,是指客户端工具能够显示从数据库读取该种编码的字符,也能通过本工具将该种编码的字符提交到服务器端。

    指定会话的客户端编码:SET CLIENT_ENCODING TO 'value';

    查看数据库编码:Show client_encoding。

GDS导入/导出遇到的字符集问题和解决办法

问题一:0x00字符无法入库:ERROR: invalid byte sequence for encoding "UTF8": 0x00

原因:PG本身不允许文本数据中出现0x00字符,基线问题,其他数据库不存在该问题。

解决方法:

  1. 替换0x00字符。
  2. Copy、GDS都有“compatible_illegal_chars”这个选项,把这个开关打开(COPY命令、GDS外表可Alter),会把单字节/多字节的非法字符替换成“(空格)”/“?”。这样可以顺利导入数据,但会更改原数据。
  3. 建立encoding为SQL_ASCII的库,然后client_encoding也设置为SQL_ASCII(COPY命令中可设置,GDS外表也可设置),这种情况下可以避免字符集的特殊处理和转换,所有库内相关的排序、比较以及处理全部按照单字节处理。

问题二: GBK字符无法导入UTF-8库

原因:缺少GBK到UTF-8的转换函数。

解决方法:目前在r8c10已经补充了缺少的转换函数,包含106个字符,会尽快同步到r7c10的发货版本。具体字符,参考如下。

问题三:GBK转义符\(0x5C)问题。

原因:gbk本身作为server端编码存在很多问题,违背了PG的设计原则,即ascii码不能作为多字节字符的一部分(多字节字符每个字节的首位必须为1),不遵循这个原则可能会出现误判的问题。例子如下所示,gbk多字节字符可能会使用'\'作为第二个字符。

/* Else, it's the traditional escaped style */
for (bc = 0, tp = inputText; *tp != '\0'; bc++)
{
if (tp[0] != '\\')
tp++;

上述问题属于PG基线的代码实现问题。当然可以通过枚举所有可能出现的非法情形来解决,但是实现难度比较大,更为关键的是会增加判断逻辑降低处理效率,这也是社区坚决不允许引入gbk、SJIS(日文)等字符集的原因所在。

解决方法:尽量不使用GBK作为server端字符编码,可使用utf-8替换。另外,尽量不使用SQL_ASCII作为server端编码,无论导入的数据是什么字符集,SQL_ASCII都会按单字节入库,内部逻辑也都会按单字节处理,同样会遇到上述问题。GBK中包含0x5C的两字节字符参考如下。

相关文档