华为云用户手册

  • 哈希分区 哈希分区(Hash Partition)基于对分区键使用哈希算法将数据映射到分区。使用的哈希算法为 GaussDB 内置哈希算法,在分区键取值范围不倾斜(no data skew)的场景下,哈希算法在分区之间均匀分布行,使分区大小大致相同。因此哈希分区是实现分区间均匀分布数据的理想方法。哈希分区也是范围分区的一种易于使用的替代方法,尤其是当要分区的数据不是历史数据或没有明显的分区键时,示例如下: CREATE TABLE bmsql_order_line ( ol_w_id INTEGER NOT NULL, ol_d_id INTEGER NOT NULL, ol_o_id INTEGER NOT NULL, ol_number INTEGER NOT NULL, ol_i_id INTEGER NOT NULL, ol_delivery_d TIMESTAMP, ol_amount DECIMAL(6,2), ol_supply_w_id INTEGER, ol_quantity INTEGER, ol_dist_info CHAR(24) ) --预先定义100个分区 PARTITION BY HASH(ol_d_id) ( PARTITION p0, PARTITION p1, PARTITION p2, … PARTITION p99 ); 上述例子中,bmsql_order_line表的ol_d_id进行了分区,ol_d_id列是一个identifier性质的属性列,本身并不带有时间或者某一个特定维度上的区分。使用哈希分区策略来对其进行分表处理则是一个较为理想的选择。相比其他分区类型,除了预先确保分区键没有过多数据倾斜(某一、某几个值重复度高),只需要指定分区键和分区数即可创建分区,同时还能够确保每个分区的数据均匀,提升了分区表的易用性。 父主题: 分区策略
  • 间隔分区 间隔分区(Interval Partition)可以看成是范围分区的一种增强和扩展方式,相比之下间隔分区定义分区时无需为新增的每个分区指定上限和下限值,只需要确定每个分区的长度,实际插入的过程中会自动进行分区的创建和扩展。间隔分区在创建初始时必须至少指定一个范围分区,范围分区键值确定范围分区的高值称为转换点,数据库为值超出该转换点的数据自动创建间隔分区。每个区间分区的下边界是先前范围或区间分区的非包容性上边界。示例如下: gaussdb=# CREATE TABLE interval_sales ( prod_id NUMBER(6), cust_id NUMBER, time_id DATE, channel_id CHAR(1), promo_id NUMBER(6), quantity_sold NUMBER(3), amount_sold NUMBER(10, 2) ) PARTITION BY RANGE (time_id) INTERVAL ('1 month') ( PARTITION date_2015 VALUES LESS THAN ('2016-01-01'), PARTITION date_2016 VALUES LESS THAN ('2017-01-01'), PARTITION date_2017 VALUES LESS THAN ('2018-01-01'), PARTITION date_2018 VALUES LESS THAN ('2019-01-01'), PARTITION date_2019 VALUES LESS THAN ('2020-01-01') ); gaussdb=# DROP TABLE interval_sales; 上述例子中,初始创建分区以2015年到2019年以年为单位创建分区,当数据插入到2020-01-01以后的数据时,由于超过的预先定义Range分区的上边界,会自动创建一个分区。 间隔分区仅支持数值类型和日期/时间类型,不支持字符类型和其他类型。支持类型白名单如下: INT1/UINT1、INT2/UINT2、INT4/UINT4、INT8/UINT8、FLOAT4、FLOAT8、NUMERIC、DATE、TIMESTAMP、TIMESTAMP WITH TIME ZONE。 父主题: 分区策略
  • 范围分区 范围分区(Range Partition)根据为每个分区建立分区键的值范围将数据映射到分区。范围分区是生产系统中最常见的分区类型,通常在以时间维度(Date、Time Stamp)描述数据场景中使用。范围分区有两种语法格式,示例如下: VALUES LESS THAN的语法格式 对于从句是VALUE LESS THAN的语法格式,范围分区策略的分区键最多支持16列。 单列分区键示例如下: gaussdb=# CREATE TABLE range_sales_single_key ( product_id INT4 NOT NULL, customer_id INT4 NOT NULL, time DATE, channel_id CHAR(1), type_id INT4, quantity_sold NUMERIC(3), amount_sold NUMERIC(10,2) ) PARTITION BY RANGE (time) ( PARTITION date_202001 VALUES LESS THAN ('2020-02-01'), PARTITION date_202002 VALUES LESS THAN ('2020-03-01'), PARTITION date_202003 VALUES LESS THAN ('2020-04-01'), PARTITION date_202004 VALUES LESS THAN ('2020-05-01') ); gaussdb=# DROP TABLE range_sales_single_key; 其中date_202002表示2020年2月的分区,将包含分区键值从2020年2月1日到2020年2月29日的数据。 每个分区都有一个VALUES LESS子句,用于指定分区的非包含上限。大于或等于该分区键的任何值都将添加到下一个分区。除第一个分区外,所有分区都具有由前一个分区的VALUES LESS子句指定的隐式下限。可以为最高分区定义MAXVALUE关键字,MAXVALUE表示一个虚拟无限值,其排序高于分区键的任何其他可能值,包括空值。 多列分区键示例如下: gaussdb=# CREATE TABLE range_sales ( c1 INT4 NOT NULL, c2 INT4 NOT NULL, c3 CHAR(1) ) PARTITION BY RANGE (c1,c2) ( PARTITION p1 VALUES LESS THAN (10,10), PARTITION p2 VALUES LESS THAN (10,20), PARTITION p3 VALUES LESS THAN (20,10) ); INSERT INTO range_sales VALUES(9,5,'a'); INSERT INTO range_sales VALUES(9,20,'a'); INSERT INTO range_sales VALUES(9,21,'a'); INSERT INTO range_sales VALUES(10,5,'a'); INSERT INTO range_sales VALUES(10,15,'a'); INSERT INTO range_sales VALUES(10,20,'a'); INSERT INTO range_sales VALUES(10,21,'a'); INSERT INTO range_sales VALUES(11,5,'a'); INSERT INTO range_sales VALUES(11,20,'a'); INSERT INTO range_sales VALUES(11,21,'a'); gaussdb=# SELECT * FROM range_sales PARTITION (p1); c1 | c2 | c3 ----+----+---- 9 | 5 | a 9 | 20 | a 9 | 21 | a 10 | 5 | a (4 rows) gaussdb=# SELECT * FROM range_sales PARTITION (p2); c1 | c2 | c3 ----+----+---- 10 | 15 | a (1 row) gaussdb=# SELECT * FROM range_sales PARTITION (p3); c1 | c2 | c3 ----+----+---- 10 | 20 | a 10 | 21 | a 11 | 5 | a 11 | 20 | a 11 | 21 | a (5 rows) gaussdb=# DROP TABLE range_sales; 多列分区的分区规则如下: 从第一列开始比较。 如果插入的当前列小于分区当前列边界值,则直接插入。 如果插入的当前列等于分区当前列的边界值,则比较插入值的下一列与分区下一列边界值的大小。 如果插入的当前列大于分区当前列的边界值,则换下一个分区进行比较。 START END语法格式 对于从句是START END语法格式,范围分区策略的分区键最多支持1列。 示例如下: gaussdb=# -- 创建表空间 CREATE TABLESPACE startend_tbs1 LOCATION '/home/omm/startend_tbs1'; CREATE TABLESPACE startend_tbs2 LOCATION '/home/omm/startend_tbs2'; CREATE TABLESPACE startend_tbs3 LOCATION '/home/omm/startend_tbs3'; CREATE TABLESPACE startend_tbs4 LOCATION '/home/omm/startend_tbs4'; -- 创建临时schema CREATE SCHEMA tpcds; SET CURRENT_SCHEMA TO tpcds; -- 创建分区表,分区键是integer类型 CREATE TABLE tpcds.startend_pt (c1 INT, c2 INT) TABLESPACE startend_tbs1 PARTITION BY RANGE (c2) ( PARTITION p1 START(1) END(1000) EVERY(200) TABLESPACE startend_tbs2, PARTITION p2 END(2000), PARTITION p3 START(2000) END(2500) TABLESPACE startend_tbs3, PARTITION p4 START(2500), PARTITION p5 START(3000) END(5000) EVERY(1000) TABLESPACE startend_tbs4 ) ENABLE ROW MOVEMENT; -- 查看分区表信息 gaussdb=# SELECT relname, boundaries, spcname FROM pg_partition p JOIN pg_tablespace t ON p.reltablespace=t.oid and p.parentid='tpcds.startend_pt'::regclass ORDER BY 1; relname | boundaries | spcname -------------+------------+--------------- p1_0 | {1} | startend_tbs2 p1_1 | {201} | startend_tbs2 p1_2 | {401} | startend_tbs2 p1_3 | {601} | startend_tbs2 p1_4 | {801} | startend_tbs2 p1_5 | {1000} | startend_tbs2 p2 | {2000} | startend_tbs1 p3 | {2500} | startend_tbs3 p4 | {3000} | startend_tbs1 p5_1 | {4000} | startend_tbs4 p5_2 | {5000} | startend_tbs4 startend_pt | | startend_tbs1 (12 rows) --清理示例 gaussdb=# DROP TABLE tpcds.startend_pt; DROP TABLE gaussdb=# DROP SCHEMA tpcds; DROP SCHEMA 父主题: 分区策略
  • 分区策略 分区策略在使用DDL语句建表语句时通过PARTITION BY语句的语法指定,分区策略描述了在分区表中数据和分区路由映射规则。常见的分区类型有基于条件的Range分区/Interval分区、基于哈希散列函数的Hash分区、基于数据枚举的List列表分区: CREATE TABLE table_name (…) PARTITION BY partition_strategy (partition_key) (…) 范围分区 间隔分区 哈希分区 列表分区 二级分区 分区表对导入操作的性能影响 父主题: 分区表介绍
  • 分区(分区子表、子分区) 分区表中实际保存数据的表,对应的entry通常保存在pg_partition中,各个子分区的parentid作为外键关联其分区母表在pg_class表中的OID列。 示例:t1_hash为一个一级分区表: gaussdb=# CREATE TABLE t1_hash (c1 INT, c2 INT, c3 INT) PARTITION BY HASH(c1) ( PARTITION p0, PARTITION p1, PARTITION p2, PARTITION p3, PARTITION p4, PARTITION p5, PARTITION p6, PARTITION p7, PARTITION p8, PARTITION p9 ); --查询t1_hash分区类型 gaussdb=# SELECT oid, relname, parttype FROM pg_class WHERE relname = 't1_hash'; oid | relname | parttype -------+---------+---------- 16685 | t1_hash | p (1 row) --查询t1_hash的分区信息 gaussdb=# SELECT oid, relname, parttype, parentid FROM pg_partition WHERE parentid = 16685; oid | relname | parttype | parentid -------+---------+----------+---------- 16688 | t1_hash | r | 16685 16689 | p0 | p | 16685 16690 | p1 | p | 16685 16691 | p2 | p | 16685 16692 | p3 | p | 16685 16693 | p4 | p | 16685 16694 | p5 | p | 16685 16695 | p6 | p | 16685 16696 | p7 | p | 16685 16697 | p8 | p | 16685 16698 | p9 | p | 16685 (11 rows) gaussdb=# DROP TABLE t1_hash; 父主题: 基本概念
  • 分区表(母表) 实际对用户体现的表,用户对该表进行常规DML语句的增、删、查、改操作。通常使用在建表DDL语句显式的使用PARTITION BY语句进行定义,创建成功以后在pg_class表中新增一个entry,并且parttype列内容为'p'(一级分区)或者's'(二级分区),表明该entry为分区表的母表。分区母表通常是一个逻辑形态,对应的表文件并不存放数据。 示例1:t1_hash为一个一级分区表,分区类型为hash: gaussdb=# CREATE TABLE t1_hash (c1 INT, c2 INT, c3 INT) PARTITION BY HASH(c1) ( PARTITION p0, PARTITION p1, PARTITION p2, PARTITION p3, PARTITION p4, PARTITION p5, PARTITION p6, PARTITION p7, PARTITION p8, PARTITION p9 ); gaussdb=# \d+ t1_hash Table "public.t1_hash" Column | Type | Modifiers | Storage | Stats target | Description --------+---------+-----------+---------+--------------+------------- c1 | integer | | plain | | c2 | integer | | plain | | c3 | integer | | plain | | Partition By HASH(c1) Number of partitions: 10 (View pg_partition to check each partition range.) Has OIDs: no Options: orientation=row, compression=no, storage_type=USTORE, segment=off --查询t1_hash分区类型 gaussdb=# SELECT relname, parttype FROM pg_class WHERE relname = 't1_hash'; relname | parttype ---------+---------- t1_hash | p (1 row) gaussdb=# DROP TABLE t1_hash; 示例2:t1_sub_rr为一个二级分区表,分区类型为range-list: gaussdb=# CREATE TABLE t1_sub_rr ( c1 INT, c2 INT, c3 INT ) PARTITION BY RANGE (c1) SUBPARTITION BY LIST (c2) ( PARTITION p_2021 VALUES LESS THAN (2022) ( SUBPARTITION p_2021_1 VALUES (1), SUBPARTITION p_2021_2 VALUES (2), SUBPARTITION p_2021_3 VALUES (3) ), PARTITION p_2022 VALUES LESS THAN (2023) ( SUBPARTITION p_2022_1 VALUES (1), SUBPARTITION p_2022_2 VALUES (2), SUBPARTITION p_2022_3 VALUES (3) ), PARTITION p_2023 VALUES LESS THAN (2024) ( SUBPARTITION p_2023_1 VALUES (1), SUBPARTITION p_2023_2 VALUES (2), SUBPARTITION p_2023_3 VALUES (3) ), PARTITION p_2024 VALUES LESS THAN (2025) ( SUBPARTITION p_2024_1 VALUES (1), SUBPARTITION p_2024_2 VALUES (2), SUBPARTITION p_2024_3 VALUES (3) ), PARTITION p_2025 VALUES LESS THAN (2026) ( SUBPARTITION p_2025_1 VALUES (1), SUBPARTITION p_2025_2 VALUES (2), SUBPARTITION p_2025_3 VALUES (3) ), PARTITION p_2026 VALUES LESS THAN (2027) ( SUBPARTITION p_2026_1 VALUES (1), SUBPARTITION p_2026_2 VALUES (2), SUBPARTITION p_2026_3 VALUES (3) ) ); gaussdb=# \d+ t1_sub_rr Table "public.t1_sub_rr" Column | Type | Modifiers | Storage | Stats target | Description --------+---------+-----------+---------+--------------+------------- c1 | integer | | plain | | c2 | integer | | plain | | c3 | integer | | plain | | Partition By RANGE(c1) Subpartition By LIST(c2) Number of partitions: 6 (View pg_partition to check each partition range.) Number of subpartitions: 18 (View pg_partition to check each subpartition range.) Has OIDs: no Options: orientation=row, compression=no, storage_type=USTORE, segment=off --查询t1_sub_rr分区类型 gaussdb=# SELECT relname, parttype FROM pg_class WHERE relname = 't1_sub_rr'; relname | parttype -----------+---------- t1_sub_rr | s (1 row) gaussdb=# DROP TABLE t1_sub_rr; 父主题: 基本概念
  • 分区表介绍 分区表(Partitioned Table)指在单节点内对表数据内容按照分区键以及围绕分区键的分区策略对表进行逻辑切分。从数据分区的角度来看是一种水平分区(horizontal partition)策略方式。分区表增强了数据库应用程序的性能、可管理性和可用性,并有助于降低存储大量数据的总体拥有成本。分区允许将表、索引和索引组织的表细分为更小的部分,使这些数据库对象能够在更精细的粒度级别上进行管理和访问。GaussDB提供了丰富的分区策略和扩展,以满足不同业务场景的需求。由于分区策略的实现完全由数据库内部实现,对用户是完全透明的,因此它几乎可以在实施分区表优化策略以后做平滑迁移,无需潜在耗费人力物力的应用程序更改。本章围绕GaussDB分区表的基本概念从以下几个方面展开介绍: 分区表基本概念:从表分区的基本概念出发,介绍分区表的catalog存储方式以及内部对应原理。 分区策略:从分区表所支持的基本类型出发,介绍各种分区模式下对应的特性以及能够达到的优化特点和效果。 基本概念 分区策略 分区基本使用 父主题: 分区表
  • 数据分区运维管理 分区表技术为数据生命周期管理(Data Life Cycle Management,DLM)提供了灵活性的支持,数据生命周期管理是一组用于在数据的整个使用寿命中管理数据的过程和策略。其中一个重要组成部分是确定在数据生命周期的任何时间点存储数据的最合适和最经济高效的介质:日常操作中使用的较新数据存储在最快、可用性最高的存储层上,而不经常访问的较旧数据可能存储在成本较低、效率较低的存储层。较旧的数据也可能更新的频率较低,因此将数据压缩并存储为只读是有意义的。 分区表为实施DLM解决方案提供了理想的环境,通过不同分区使用不同表空间,最大限度在确保易用性的同时,实现了有效的数据生命周期的成本优化。这部分的设置由数据库运维人员在服务端设置操作完成,实际用户并不感知这一层面的优化设置,对用户而言逻辑上仍然是对同一张表的查询操作。此外不同分区可以分别实施备份、恢复、索引重建等运维性质的操作,能够对单个数据集不同子类进行分治操作,满足用户业务场景的差异化需求。 父主题: 大容量数据库
  • 数据分区查找优化 分区表对数据查找方面的帮助主要体现在对分区键进行谓词查询场景,例如一张以月份Month作为分区键的表,如图1所示。 如果以普通表的方式设计表结构则需要访问表全量的数据(Full Table Scan),如果以日期为分区键重新设计该表,那么原有的全表扫描会被优化成为分区扫描。当表内的数据量很大同时具有很长的历史周期时,由于扫描数据缩减所带来的性能提升会有明显的效果,如图2所示。 图1 分区表示例图 图2 分区表剪枝示例图 父主题: 大容量数据库
  • 表分区技术 表分区技术(Table-Partitioning)通过将非常大的表或者索引从逻辑上切分为更小、更易管理的逻辑单元(分区),能够让用户对表查询、变更等语句操作具备更小的影响范围,能够让用户通过分区键(Partition Key)快速定位到数据所在的分区,从而避免在数据库中对大表的全量扫描,能够在不同的分区上并发进行DDL、DML操作。从用户使用的角度来看,表分区技术主要有以下三个方面能力: 提升大容量数据场景查询效率:由于表内数据按照分区键进行逻辑分区,查询结果可以通过访问分区的子集而不是整个表来实现。这种分区剪枝技术可以提供数量级的性能增益。 降低运维与查询的并发操作影响:降低DML语句、DDL语句并发场景的相互影响,在对一些大数据量以时间维度进行分区的场景下会明显受益。例如,新数据分区进行入库、实时点查操作,老数据分区进行数据清洗、分区合并等运维性质操作。 提供大容量场景下灵活的数据运维管理方式:由于分区表从物理上对不同分区的数据做了表文件层面的隔离,每个分区可以具有单独的物理属性,如启用或禁用压缩、物理存储设置和表空间。同时它支持数据管理操作,如数据加载、索引创建和重建,以及分区级别的备份和恢复,而不是对整个表进行操作,从而减少了操作时间。 父主题: 大容量数据库
  • 大容量数据库背景介绍 随着处理数据量的日益增长和使用场景的多样化,数据库越来越多地面对容量大、数据多样化的场景。在过去数据库业界发展的20多年时间里,数据量从最初的MB、GB级数据量逐渐发展到现在的TB级数据量,在如此数据大规模、数据多样化的客观背景下,数据库管理系统(DBMS)在数据查询、数据管理方面提出了更高的要求,客观上要求数据库能够支持多种优化查找策略和管理运维方式。 在计算机科学经典的算法中,人们通常使用分治法(Divide and Conquer)解决场景和规模较大的问题。其基本思想就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题直到最后子问题可以简单的直接求解,原问题的解可看成子问题的解的合并。对于大容量数据场景,数据库提供对数据进行“分治处理”的方式即分区,将逻辑数据库或其组成元素划分为不同的独立部分,每一个分区维护逻辑上存在相类似属性的数据,这样就把庞大的数据整体进行了切分,有利于数据的管理、查找和维护。 父主题: 大容量数据库
  • 接口设计 新增控制参数 新增逻辑解码控制参数,用于控制DDL的反解析流程以及输出形式。可通过JDBC接口或者pg_logical_slot_peek_changes开启。 enable-ddl-decoding:默认false,不开启DDL语句的逻辑解码;值为true时,开启DDL语句的逻辑解码。 enable-ddl-json-format:默认false,传送TEXT格式的DDL反解析结果;值为true时,传送JSON格式的DDL反解析结果。 新增GUC参数 enable_logical_replication_ddl:默认为ON,ON状态下,逻辑复制可支持DDL,否则,不支持DDL。只有当ON状态下,才会对DDL执行结果进行反解析,并生成DDL的WAL日志。否则,不反解析也不生成WAL日志。 enable_logical_replication_ddl的开关日志,以证明是否是用户修改了该参数导致逻辑解码不支持DDL。 新增日志 新增DDL日志xl_logical_ddl_message,其类型为RM_ LOG ICALDDLMSG_ID。其定义如下: 名称 类型 意义 db_id OID 数据库ID rel_id OID 表ID csn CommitSeqNo CS N快照 cid CommandId Command ID tag_type NodeTag DDL类型 message_size Size 日志内容长度 filter_message_size Size 日志中白名单过滤信息长度 message char * DDL内容
  • 解码格式 JSON格式 对于输入的DDL语句,SQL引擎解析器会通过语法、词法分析将其分解为解析树,解析树节点中包含了DDL的全部信息,并且执行器会根据解析树内容,执行系统元信息的修改。在执行器执行完成之后,便可以获取到DDL操作数据对象的search_path。本特性在执行器执行成功之后,对解析树信息以及执行器结果进行反解析,以还原出DDL原语句的全部信息。反解析的方式可以分解整个DDL语句,以方便输出JSON格式的DDL,用以适配异构数据库场景。 CREATE TABLE语句在经过词法、语法分析之后,得到对应的CreateStmt解析树节点,节点中包含了表信息、列信息、分布式信息(DistributeBy结构体)、分区信息(PartitionState结构)等。通过反解析后,可输出的JSON格式如下: {"JDDL":{"fmt":"CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D %{table_elements}s %{with_clause}s %{compression}s","identity":{"object_name":"test_create_table_a","schema_name":"public"},"compression":"NOCOMPRESS","persistence":"","with_clause":{"fmt":"WITH (%{with:, }s)","with":[{"fmt":"%{label}s = %{value}L","label":{"fmt":"%{label}I","label":"orientation"},"value":"row"},{"fmt":"%{label}s = %{value}L","label":{"fmt":"%{label}I","label":"compression"},"value":"no"}]},"if_not_exists":"","table_elements":{"fmt":"(%{elements:, }s)","elements":[{"fmt":"%{name}I %{column_type}T","name":"a","column_type":{"typmod":"","typarray":false,"type_name":"int4","schema_name":"pg_catalog"}}]}}} 可以看到,JSON格式中包含对象的search_path,其中的identity键标识Schema为public,表名为test_create_table_a,其中%{persistence}s对应的字段如下,此SQL语句不含此字段所以为空。 [ [ GLOBAL | LOCAL ] [ TEMPORARY | TEMP ] | UNLOGGED ] %{if_not_exists}s对应SQL语句中的字段,不含此字段所以为空: [ IF NOT EXISTS ] %{identity}D对应SQL语句中的字段: table_name %{table_elements}s对应SQL语句中的字段: (column_name data_type) %{with_clause}s对应SQL语句中的字段: [ WITH ( {storage_parameter = value} [, ... ] ) ] %{compression}s对应SQL语句中的字段: [ COMPRESS | NOCOMPRESS ] decode-style指定格式 输出的格式由decode-style参数控制,如当decode-style='j'时,输出格式如下: {"TDDL":"CREATE TABLE public.test_create_table_a (a pg_catalog.int4) WITH (orientation = 'row', compression = 'no') NOCOMPRESS"} 其中语句中也包含Schema名称。
  • 功能描述 数据库在执行DML的时候,存储引擎会生成对应的DML日志,用于进行恢复,对这些DML日志进行解码,即可还原对应的DML语句,生成逻辑日志。而对于DDL语句,数据库并不记录DDL原语句的日志,而是记录DDL语句涉及的系统表的DML日志。DDL种类多样、语法复杂,逻辑复制要支持DDL语句,通过这些系统表的DML日志来解码原DDL语句是非常困难的。新增DDL日志记录原DDL信息,并在解码时通过DDL日志可以得到DDL原语句。 在DDL语句执行过程中,SQL引擎解析器会对原语句进行语法、词法解析,并生成解析树(不同的DDL语法会生成不同类型的解析树,解析树中包含DDL语句的全部信息)。随后,执行器通过这些信息执行对应操作,生成、修改对应元信息。 本节通过新增DDL日志的方式,来支持逻辑解码DDL,其内容由解析器结果(解析树)以及执行器结果生成,并在执行器执行完成后生成该日志。 从语法树反解析出DDL,DDL反解析能够将DDL命令转换为JSON格式的语句,并提供必要的信息在目标位置重建DDL命令。与原始DDL命令字符串相比,使用DDL反解析的好处包括: 解析出来的每个数据库对象都带有Schema,因此如果使用不同的search_path,也不会有歧义。 结构化的JSON和格式化的输出能支持异构数据库。如果用户使用的是不同的数据库版本,并且存在某些DDL语法差异,需要在应用之前解决这些差异。 反解析输出的结果是规范化后的形式,结果与用户输入等价,不保证完全相同,例如: 示例1:在函数体中没有单引号'时,函数体的分隔符$$会被解析为单引号'。 原始SQL语句: CREATE FUNCTION func(a INT) RETURNS INT AS $$ BEGIN a:= a+1; CREATE TABLE test(col1 INT); INSERT INTO test VALUES(1); DROP TABLE test; RETURN a; END; $$ LANGUAGE plpgsql; 反解析结果: CREATE FUNCTION public.func ( IN a pg_catalog.int4 ) RETURNS pg_catalog.int4 LANGUAGE plpgsql VOLATILE CALLED ON NULL INPUT SECURITY INVOKER COST 100 AS ' BEGIN a:= a+1; CREATE TABLE test(col1 INT); INSERT INTO test VALUES(1); DROP TABLE test; RETURN a; END; '; 示例2:“CREATE MATERIALIZED VIEW v46_4 AS SELECT a, b FROM t46 ORDER BY a OFFSET 10 ROWS FETCH NEXT 3 ROWS ONLY”会被反解析为“CREATE MATERIALIZED VIEW public.v46_4 AS SELECT a, b FROM public.t46 ORDER BY a OFFSET 10 LIMIT 3”;。 示例3:“ALTER INDEX "Alter_Index_Index" REBUILD PARTITION "CA_ADDRESS_SK_index2"”会被反解析为“REINDEX INDEX public."Alter_Index_Index" PARTITION "CA_ADDRESS_SK_index2"”。 示例4:创建/修改范围分区表,START END语法格式均解码转化为LESS THAN语句: gaussdb=# CREATE TABLE test_create_table_partition2 (c1 INT, c2 INT) PARTITION BY RANGE (c2) ( PARTITION p1 START(1) END(1000) EVERY(200) , PARTITION p2 END(2000), PARTITION p3 START(2000) END(2500), PARTITION p4 START(2500), PARTITION p5 START(3000) END(5000) EVERY(1000) );
  • 并行解码 以下配置选项仅限流式解码设置。 decode-style: 当enable-ddl-json-format参数值为true时,DDL的格式由enable-ddl-json-format控制,decode-style仅指定DML语句的解码格式;当enable-ddl-json-format参数值为false时,decode-style指定DML和DDL语句的解码格式。 取值范围:char型的字符'j'、't'或'b',分别代表JSON格式、TEXT格式及二进制格式。 默认值: 没有指定decode-style: 针对复制槽插件类型为mppdb_decoding、sql_decoding,decode-style默认值为'b'即二进制格式解码。针对复制槽插件类型为parallel_binary_decoding、parallel_json_decoding、parallel_text_decoding,decode-style默认值分别为'b'、'j'、't',解码格式分别为二进制格式、JSON格式、TEXT格式。 指定decode-style: 按照指定的decode-style进行解码。 对于JSON格式和TEXT格式解码,开启批量发送选项时的解码结果中,每条解码语句的前4字节组成的uint32代表该条语句总字节数(不包含该uint32类型占用的4字节,0代表本批次解码结束),8字节uint64代表相应lsn(begin对应first_lsn,commit对应end_lsn,其他场景对应该条语句的lsn)。 例如:以mppdb_decoding插件为例,当decode-style为b类型时,以二进制格式解码,结果如下: current_lsn: 0/CFE5C80 BEGIN CSN: 2357 first_lsn: 0/CFE5C80 current_lsn: 0/CFE5D40 INSERT INTO public.test1 new_tuple: {a[typid = 23]: "1", b[typid = 23]: "2"} current_lsn: 0/CFE5E68 COMMIT xid: 78108 当decode-style为j类型时,以JSON格式解码,结果如下: BEGIN CSN: 2358 first_lsn: 0/CFE6220 {"table_name":"public.test1","op_type":"INSERT","columns_name":["a","b"],"columns_type":["integer","integer"],"columns_val":["3","3"],"old_keys_name":[],"old_keys_type":[],"old_keys_val":[]} COMMIT XID: 78109 当decode-style为t类型时,以TEXT格式解码,结果如下: BEGIN CSN: 2359 first_lsn: 0/CFE64D0 table public test1 INSERT: a[integer]:3 b[integer]:4 COMMIT XID: 78110 二进制格式编码规则如下所示: 前4字节代表接下来到语句级别分隔符字母P(不含)或者该批次结束符F(不含)的解码结果的总字节数,该值如果为0代表本批次解码结束。 接下来8字节uint64代表相应lsn(begin对应first_lsn,commit对应end_lsn,其他场景对应该条语句的lsn)。 接下来1字节的字母有5种B/C/I/U/D,分别代表begin/commit/insert/update/delete。 第3步字母为B时: 接下来的8字节uint64代表CSN。 接下来的8字节uint64代表first_lsn。 【该部分为可选项】接下来的1字节字母如果为T,则代表后面4字节uint32表示该事务commit时间戳长度,再后面等同于该长度的字符为时间戳字符串。 【该部分为可选项】接下来的1字节字母如果为N,则代表后面4字节uint32表示该事务用户名的长度,再后面等同于该长度的字符为事务的用户名字。 因为之后仍可能有解码语句,接下来会有1字节字母P或F作为语句间的分隔符,P代表本批次仍有解码的语句,F代表本批次解码完成。 第3步字母为C时: 【该部分为可选项】接下来1字节字母如果为X,则代表后面的8字节uint64表示xid。 【该部分为可选项】接下来的1字节字母如果为T,则代表后面4字节uint32表示时间戳长度,再后面等同于该长度的字符为时间戳字符串。 因为批量发送日志时,一个COMMIT日志解码之后可能仍有其他事务的解码结果,接下来的1字节字母如果为P则表示该批次仍需解码,如果为F则表示该批次解码结束。 第3步字母为I/U/D时: 接下来的2字节uint16代表Schema名的长度。 按照上述长度读取Schema名。 接下来的2字节uint16代表table名的长度。 按照上述长度读取table名。 【该部分为可选项】接下来1字节字母如果为N代表为新元组,如果为O代表为旧元组,这里先发送新元组。 接下来的2字节uint16代表该元组需要解码的列数,记为attrnum。 以下流程重复attrnum次。 接下来2字节uint16代表列名的长度。 按照上述长度读取列名。 接下来4字节uint32代表当前列类型的OID。 接下来4字节uint32代表当前列值(以字符串格式存储)的长度,如果为0xFFFFFFFF则表示NULL,如果为0则表示长度为0的字符串。 按照上述长度读取列值。 因为之后仍可能有解码语句,接下来的1字节字母如果为P则表示该批次仍需解码,如果为F则表示该批次解码结束。 sending-batch: 指定是否批量发送。 取值范围:0或1的int型,默认值为0。 0:设为0时,表示逐条发送解码结果。 1:设为1时,表示解码结果累积到达1MB则批量发送解码结果。 开启批量发送的场景中,当解码格式为'j'或't'时,在原来的每条解码语句之前会附加一个uint32类型,表示本条解码结果长度(长度不包含当前的uint32类型),以及一个uint64类型,表示当前解码结果对应的lsn。 在CSN序解码(即output-order设置为1)场景下,批量发送仅限于单个事务内(即如果一个事务有多条较小的语句会采用批量发送),即不会使用批量发送功能在同一批次里发送多个事务,且BEGIN和COMMIT语句不会批量发送。 parallel-queue-size: 指定并行逻辑解码线程间进行交互的队列长度。 取值范围:2~1024的int型,且必须为2的整数幂,默认值为128。 队列长度和解码过程的内存使用量正相关。 logical-reader-bind-cpu: reader线程绑定cpu核号的参数。 取值范围:-1~65535,不使用该参数则为不绑核。 默认-1,为不绑核。-1不可手动设置,核号应确保在机器总逻辑核数以内,不然会返回报错。多个线程绑定同一个核会导致该核负担加重,从而导致性能下降。 logical-decoder-bind-cpu-index: 逻辑解码线程绑定cpu核号的参数。 取值范围:-1~65535,不使用该参数则为不绑核。 默认-1,不绑核。-1不可手动设置,核号应确保在机器总逻辑核数以内且小于[cpu核数 - 并行逻辑解码数],不然会返回报错。 从给定的核号参数开始,新拉起的线程会依次递增加一。 多个线程绑定同一个核会导致该核负担加重,从而导致性能下降。 GaussDB在进行逻辑解码和日志回放时,会占用大量的CPU资源,相关线程如Walwriter、WalSender、WALreceiver、pageredo就处于性能瓶颈,如果能将这些线程绑定在固定的CPU上运行,可以减少因操作系统调度线程频繁切换CPU,导致缓存未命中带来的性能开销,从而提高流程处理速度,如用户场景有性能需求,可根据以下的绑核样例进行配置优化。 参数样例: walwriter_cpu_bind=1 walwriteraux_bind_cpu=2 wal_receiver_bind_cpu=4 wal_rec_writer_bind_cpu=5 wal_sender_bind_cpu_attr='cpuorderbind:7-14' redo_bind_cpu_attr='cpuorderbind:16-19' logical-reader-bind-cpu=20 logical-decoder-bind-cpu-index=21 样例中1.2.3.4.5.6通过GUC工具设置,使用指令如: gs_guc set -Z datanode -N all -I all -c “walwriter_cpu_bind=1” 样例中7.8通过JDBC客户端发起解码请求时添加。 样例中如walwriter_cpu_bind=1是限定该线程在1号CPU上运行。 cpuorderbind:7-14意为拉起的每个线程依次绑定7号到14号CPU,如果范围内的CPU用完,则新拉起的线程不参与绑核。 logical-decoder-bind-cpu-index意为拉起的线程从21号CPU依次开始绑定,后续拉起的线程分别绑定21、22、23,依次类推。 绑核的原则是一个线程占用一个CPU。 不恰当的绑核,例如将多个线程绑定在一个CPU上很有可能带来性能劣化。 可以通过lscpu指令查看“CPU(s):”得知自己环境的CPU逻辑核心数。 CPU逻辑核心数低于36则不建议使用此套绑核策略,此时建议使用默认配置(不进行参数设置)。 big-transaction-limit: 指定并行逻辑解码大事务的内存限额。 取值范围:1~200的int型,默认值为10,单位MB。 用于内存资源管控时判定大事务,用于优先落盘。
  • 串行解码 force-binary: 是否以二进制格式输出解码结果,针对不同场景呈现不同行为。 针对系统函数pg_logical_slot_get_binary_changes和pg_logical_slot_peek_binary_changes: 取值范围:boolean型,默认值为false。此值无实际意义,均以二进制格式输出解码结果。 针对系统函数pg_logical_slot_get_changes、pg_logical_slot_peek_changes和pg_logical_get_area_changes: 取值范围:仅取false值的boolean型。以文本格式输出解码结果。 针对流式解码: 取值范围:boolean型,默认值为false。此值无实际意义,均以文本格式输出解码结果。
  • 通用选项 串行解码和并行解码均可配置,但可能无效,请参考相关选项详细说明。 include-xids: 解码出的data列是否包含xid信息。 取值范围:boolean型,默认值为true。 false:设为false时,解码出的data列不包含xid信息。 true:设为true时,解码出的data列包含xid信息。 skip-empty-xacts: 解码时是否忽略空事务信息。 取值范围:boolean型,默认值为false。 false:设为false时,解码时不忽略空事务信息。 true:设为true时,解码时会忽略空事务信息。 include-timestamp: 解码信息是否包含commit时间戳。 取值范围:boolean型,针对并行解码场景默认值为false,针对SQL函数解码和串行解码场景默认值为true。 false:设为false时,解码信息不包含commit时间戳。 true:设为true时,解码信息包含commit时间戳。 only-local: 是否仅解码本地日志。 取值范围:boolean型,默认值为true。 false:设为false时,解码非本地日志和本地日志。 true:设为true时,仅解码本地日志。 white-table-list: 白名单参数,包含需要进行解码的Schema和表名。 取值范围:包含白名单中表名的字符串,不同的表以','为分隔符进行隔离;使用'*'来模糊匹配所有情况;Schema名和表名间以'.'分隔,不允许存在任意空白符。例如: select * from pg_logical_slot_peek_changes('slot1', NULL, 4096, 'white-table-list', 'public.t1,public.t2,*.t3,my_schema.*'); max-txn-in-memory: 内存管控参数,单位为MB,单个事务占用内存大于该值即进行落盘,并行解码中该参数已废弃使用,不生效。 串行解码-取值范围:0~100的整型,默认值为0,即不开启此种管控。 并行解码-取值范围:0~max_process_memory总量的25%,默认值为max_process_memory/4/1024,其中1024为kB到MB的单位转换,0表示不开启此条内存管控项。 max-reorderbuffer-in-memory 内存管控参数,单位为GB,拼接-发送线程中正在拼接的事务总内存(包含缓存)大于该值则对当前解码事务进行落盘。 串行解码-取值范围:0~100的整型,默认值为0,即不开启此种管控。 并行解码-取值范围:1~max_process_memory总量的50%,默认值为1与max_process_memory/1048576*10%较大值,其中1048576为KB到GB的单位转换。 函数解码属于串行解码,流式解码配置解码参数parallel-decode-num等于1是串行解码,大于1是并行解码。 desc-memory-limit 内存管控参数,单位为MB,逻辑解码任务维护的表元信息总内存大于该值时,触发淘汰机制清理部分表元信息。 取值范围:10~1024的整型,默认值为100。 include-user: 事务的BEGIN逻辑日志是否输出事务的用户名。事务的用户名特指授权用户(执行事务对应会话的登录用户),它在事务的整个执行过程中不会发生变化。 取值范围:boolean型,默认值为false。 false:设为false时,事务的BEGIN逻辑日志不输出事务的用户名。 true:设为true时,事务的BEGIN逻辑日志输出事务的用户名。 exclude-userids: 黑名单用户的OID参数。 取值范围:字符串类型,指定黑名单用户的OID,多个OID通过','分隔,不校验用户OID是否存在。 exclude-users: 黑名单用户的名称列表。 取值范围:字符串类型,指定黑名单用户名,通过','分隔,不校验用户名是否存在。 dynamic-resolution: 是否动态解析黑名单用户名。如果解码某条Xlog,且Xlog写入时,用户未创建,则认为用户不存在。 取值范围:boolean型,默认值为true。 false:设为false时,当解码观测到黑名单exclude-users中用户不存在时将会报错并退出逻辑解码;当用户存在,黑名单功能正常过滤用户的操作。 true:设为true时,当解码观测到黑名单exclude-users中用户不存在时不报错,并正常解码;当用户存在,黑名单功能正常过滤用户的操作。 standby-connection: 仅流式解码设置,是否仅限制备机解码。 取值范围:boolean型,默认值为false。 true:设为true时,仅允许连接备机解码,连接主机解码时会报错退出。 false:设为false时,不做限制,允许连接主机或备机解码。 如果主机资源使用率较大,且业务对增量数据同步的实时性不敏感,建议进行备机解码;如果业务对增量数据同步的实时性要求高,并且主机业务压力较小,建议使用主机解码。 sender-timeout: 仅流式解码设置,GaussDB与客户端的心跳超时阈值。如果该时间段内没有收到客户端任何消息,逻辑解码将主动停止,并断开和客户端的连接。单位为毫秒(ms)。 取值范围:0~2147483647的int型,默认值取决于GUC参数logical_sender_timeout的配置值。配置为0,表示逻辑解码不会主动断开和客户端的连接,如果设置过小,例如1ms,则可能存在解码任务中断风险。 change-log-max-len: 逻辑日志缓存长度上限参数,单位为字节,仅并行解码有效,串行解码及SQL函数解码无效。如果单条解码结果长度超过上限,则会销毁重新分配大小为1024字节的内存并缓存。过长会增加内存占用,过短会频繁触发内存申请和释放的操作,不建议设置成小于1024的值。 取值范围:1~65535,默认值为4096。 max-decode-to-sender-cache-num: 并行解码日志的缓存条数阈值,仅并行解码有效,串行解码及SQL函数解码无效。 取值范围:1~65535,默认值为4096。 enable-heartbeat: 仅流式解码设置,代表是否输出心跳日志。 取值范围:boolean型,默认值为false。 true:设为true时,输出心跳日志。 false:设为false时,不输出心跳日志。 若开启心跳日志选项,此处说明并行解码场景心跳日志如何解析:二进制格式首先是字符'h'表示消息是心跳日志,之后是心跳日志内容,分别是8字节uint64代表LSN,表示发送心跳逻辑日志时读取的WAL日志结束位置;8字节uint64代表LSN,表示发送心跳逻辑日志时刻已经落盘的WAL日志的位置;8字节int64代表时间戳(从1970年1月1日开始),表示最新解码到的事务日志或检查点日志的产生时间戳。关于消息结束符:如果是二进制格式则为字符'F',如果格式为TEXT或者JSON且为批量发送则结束符为0,否则没有结束符。心跳日志消息返回给接收端的ReceiveLSN为0/0值,不影响复制槽推进。具体解析见下图: parallel-decode-num: 仅流式解码设置有效,并行解码的Decoder线程数量;系统函数调用场景下此选项无效,仅校验取值范围。 取值范围:1~20的int型,取1表示按照原有的串行逻辑进行解码,取其余值即为开启并行解码,默认值为1。 当parallel-decode-num不配置(即为默认值1)或显式配置为1时,下述“并行解码”中的选项不可配置。 output-order: 仅流式解码设置有效,代表是否使用CSN顺序输出解码结果;系统函数调用场景下此选项无效,仅校验取值范围。 取值范围:0或1的int型,默认值为0。 0:设为0时,解码结果按照事务的COMMIT LSN排序,当且仅当解码复制槽的confirmed_csn列值为0(即不显示)时可使用该方式,否则报错。 1:设为1时,解码结果按照事务的CSN排序,当且仅当解码复制槽的confirmed_csn列值为非零时可使用该方式,否则报错。 auto-advance: 仅流式解码设置有效,代表是否允许自主推进逻辑复制槽。 取值范围:boolean型,默认值为false。 true:设为true时,在已发送日志都被确认推进且没有待发送事务时,推进逻辑复制槽到当前解码位置。 false:设为false时,完全交由复制业务调用日志确认接口推进逻辑复制槽。 enable-ddl-decoding: 逻辑解码控制参数,用于控制是否开启DDL语句的逻辑解码。 取值范围:boolean型,默认值为false。 true:值为true时,开启DDL语句的逻辑解码。 false:值为false时,不开启DDL语句的逻辑解码。 enable-ddl-json-format: 逻辑解码控制参数,用于控制DDL的反解析流程以及输出形式。 取值范围:boolean型,默认值为false。 true:值为true时,传送JSON格式的DDL反解析结果。 false:设为false时,传送decode-style指定格式的DDL反解析结果。 skip-generated-columns: 逻辑解码控制参数,用于跳过存储生成列的输出。对UPDATE和DELETE的旧元组无效,相应元组始终会输出存储生成列。 取值范围:boolean型,默认值为false/off。 true/on:值为true/on时,不输出存储生成列的解码结果。 false/off:设为false/off时,输出存储生成列的解码结果。 虚拟生成列不受此参数控制,DML的解码结果始终不会输出虚拟生成列。 restart-lsn: 逻辑解码控制参数,用于指定解码开始点,逻辑解码会从restart-lsn该点往后找到一个一致性点(consistency lsn),然后从consistency lsn点开始解码并输出数据。 取值范围:字符串类型,格式类型为"XXXXXXXX/XXXXXXXX",其中"0/0"为无效值。 1、推荐设置复制选项restart-lsn时,不设置复制参数startposition。 2、当复制选项restart-lsn和复制参数startposition同时使用时,restart-lsn必须小于startposition,且根据restart-lsn查找到的一致性点的confirm_flush也需要小于等于startposition,防止startposition点之后的事务漏发。 3、当设置复制选项restart-lsn,不设置复制参数startposition时,根据restart-lsn查找到的一致性点进行解码并输出数据,若设置复制参数startposition时,根据restart-lsn查找到的一致性点进行解码,以startposition位置向客户端发送数据。 4、仅多版本数据字典类型的复制槽才支持该选项。
  • SQL函数解码性能 在Benchmarksql-5.0的100warehouse场景下,采用pg_logical_slot_get_changes时: 单次解码数据量4K行(对应约5MB~10MB日志),解码性能0.3MB/s~0.5MB/s。 单次解码数据量32K行(对应约40MB~80MB日志),解码性能3MB/s~5MB/s。 单次解码数据量256K行(对应约320MB~640MB日志),解码性能3MB/s~5MB/s。 单次解码数据量再增大,解码性能无明显提升。 如果采用pg_logical_slot_peek_changes + pg_replication_slot_advance方式,解码性能相比采用pg_logical_slot_get_changes时要下降30%~50%。 在Benchmarksql-5.0的100warehouse场景下,采用pg_logical_get_area_changes时: 单次解码数据量4K行(对应约5MB~10MB日志),解码性能0.3MB/s~0.5MB/s。 单次解码数据量32K行(对应约40MB~80MB日志),解码性能3MB/s~5MB/s。 单次解码数据量256K行(对应约320MB~640MB日志),解码性能3MB/s~5MB/s。 单次解码数据量再增大,解码性能无明显提升。 在Benchmarksql-5.0的100warehouse场景下,采用pg_logical_slot_get_binary_changes时: 单次解码数据量4K行(对应约5MB~10MB日志),解码性能0.3MB/s~0.5MB/s。 单次解码数据量32K行(对应约40MB~80MB日志),解码性能2MB/s~3MB/s。 单次解码数据量256K行(对应约320MB~640MB日志),解码性能2MB/s~3MB/s。 单次解码数据量再增大,解码性能无明显提升。 如果采用pg_logical_slot_peek_binary_changes + pg_replication_slot_advance方式,解码性能相比采用pg_logical_slot_get_binary_changes时要下降30%~50%。
  • 逻辑复制 逻辑复制分为逻辑解码与数据复制两部分。逻辑解码提取事务级逻辑日志,由业务或数据库中间件解析后完成数据复制。 GaussDB数据库 支持通过数据迁移工具定期向异构数据库同步数据,不具备实时数据复制能力,不足以支撑与异构数据库间并网运行实时数据同步的诉求。因此,GaussDB数据库提供逻辑解码功能,通过解析Xlog生成逻辑日志,供目标数据库实时解析实现数据复制。降低对目标数据库的形态限制,支持异构数据库、同构异形数据库对数据的同步;支持目标数据库进行数据同步期间的数据可读写,实现数据同步低时延。实现逻辑如图1所示,本章节仅介绍逻辑解码。 图1 逻辑复制 逻辑解码
  • 背景信息 当在异常情况或表被损坏时,需要使用ledger_gchain_repair(text, text)接口对全局区块表进行修复,或使用ledger_hist_repair(text, text)接口对用户历史表进行修复,修复后调用全局区块表或用户历史表校验接口结果为true。 修复用户历史表的接口为pg_catalog.ledger_hist_repair,操作为: SELECT pg_catalog.ledger_hist_repair(schema_name text,table_name text); 如果修复成功,函数返回修复过程中用户历史表hash的增量。 注:对用户表执行闪回DROP时,可使用该函数恢复用户表和用户历史表名称,请参见恢复用户表和用户历史表名称。 修复全局区块表的接口为pg_catalog.ledger_gchain_repair,操作为: SELECT pg_catalog.ledger_gchain_repair(schema_name text,table_name text); 如果修复成功,函数返回修复过程中全局区块表中指定表的hash总和。
  • 恢复用户表和用户历史表名称 已通过enable_recyclebin参数和recyclebin_retention_time参数开启闪回DROP功能,恢复用户表和用户历史表名称。示例如下: DROP用户表,对用户表执行闪回DROP。使用ledger_hist_repair对用户表、用户历史表进行表名恢复。 -- 对用户表执行闪回drop,使用ledger_hist_repair对用户历史表进行表名恢复。 gaussdb=# CREATE TABLE ledgernsp.tab2(a int, b text); CREATE TABLE gaussdb=# DROP TABLE ledgernsp.tab2; DROP TABLE gaussdb=# SELECT rcyrelid, rcyname, rcyoriginname FROM gs_recyclebin; rcyrelid | rcyname | rcyoriginname ----------+------------------------------+--------------------- 16717 | BIN$38242338414D$42EB978==$0 | tab2 16725 | BIN$382423384155$42EC678==$0 | gs_hist_tab2_index 16722 | BIN$382423384152$42ECC30==$0 | ledgernsp_tab2_hist 16720 | BIN$382423384150$42ED3E0==$0 | pg_toast_16717 (4 rows) -- 对用户表执行闪回drop。 gaussdb=# TIMECAPSULE TABLE ledgernsp.tab2 TO BEFORE DROP; TimeCapsule Table -- 使用ledger_hist_repair恢复用户历史表表名。 gaussdb=# SELECT ledger_hist_repair('ledgernsp', 'tab2'); ledger_hist_repair -------------------- 0000000000000000 (1 row) gaussdb=# TIMECAPSULE TABLE ledgernsp.tab2 TO BEFORE DROP; TimeCapsule Table gaussdb=# SELECT ledger_hist_repair('ledgernsp', 'tab2'); ledger_hist_repair -------------------- 0000000000000000 (1 row) gaussdb=# \d+ ledgernsp.tab2; Table "ledgernsp.tab2" Column | Type | Modifiers | Storage | Stats target | Description -------------+---------+-----------+----------+--------------+------------- a | integer | | plain | | b | text | | extended | | hash_1d2d14 | hash16 | | plain | | Has OIDs: no Options: orientation=row, compression=no, storage_type=USTORE, segment=off, toast.storage_type=USTORE, toast.toast_storage_type=enhanced_toast History table name: ledgernsp_tab2_hist -- 对用户表执行闪回drop,使用ledger_hist_repair对用户表进行表名恢复。 gaussdb=# CREATE TABLE ledgernsp.tab3(a int, b text); CREATE TABLE gaussdb=# DROP TABLE ledgernsp.tab3; DROP TABLE gaussdb=# SELECT rcyrelid, rcyname, rcyoriginname FROM gs_recyclebin; rcyrelid | rcyname | rcyoriginname ----------+------------------------------+--------------------- 17574 | BIN$44A4233844A6$B18E7A0==$0 | tab3 17582 | BIN$44A4233844AE$B18F488==$0 | gs_hist_tab3_index 17579 | BIN$44A4233844AB$B18FA40==$0 | ledgernsp_tab3_hist 17577 | BIN$44A4233844A9$B190208==$0 | pg_toast_17574 (4 rows) -- 对用户历史表执行闪回drop。 gaussdb=# TIMECAPSULE TABLE blockchain.ledgernsp_tab3_hist TO BEFORE DROP; TimeCapsule Table -- 拿到回收站中用户表对应的rcyname,使用ledger_hist_repair恢复用户表表名。 gaussdb=# SELECT ledger_hist_repair('ledgernsp', 'BIN$44A4233844A6$B18E7A0==$0'); ledger_hist_repair -------------------- 0000000000000000 (1 row) gaussdb=# \d+ ledgernsp.tab3; Table "ledgernsp.tab3" Column | Type | Modifiers | Storage | Stats target | Description -------------+---------+-----------+----------+--------------+------------- a | integer | | plain | | b | text | | extended | | hash_7a0c87 | hash16 | | plain | | Has OIDs: no Options: orientation=row, compression=no, storage_type=USTORE, segment=off, toast.storage_type=USTORE, toast.toast_storage_type=enhanced_toast History table name: ledgernsp_tab3_hist -- 删除表。 gaussdb=# DROP TABLE ledgernsp.tab2 PURGE; DROP TABLE gaussdb=# DROP TABLE ledgernsp.tab3 PURGE; DROP TABLE
  • 恢复用户表数据和全局区块表数据 执行历史表修复操作。 1 gaussdb=# SELECT pg_catalog.ledger_hist_repair('ledgernsp', 'usertable'); 查询结果如下: ledger_hist_repair -------------------- 84e8bfc3b974e9cf (1 row) 该结果表明当前节点用户历史表修复成功,修复造成的用户历史表hash增量为84e8bfc3b974e9cf。 执行全局区块表修复操作。 1 gaussdb=# SELECT pg_catalog.ledger_gchain_repair('ledgernsp', 'usertable'); 查询结果如下: ledger_gchain_repair ---------------------- a41714001181a294 (1 row) 该结果表明,全局区块表修复成功,且插入一条修复数据,其hash值为a41714001181a294。
  • 背景信息 账本数据库归档功能目前提供两种校验接口,分别为:ledger_hist_archive(text, text)和ledger_gchain_archive(text, text)。账本数据库接口仅审计管理员可以调用。 归档用户历史表的接口为pg_catalog.ledger_hist_archive,操作为: SELECT pg_catalog.ledger_hist_archive(schema_name text,table_name text); 如果归档成功,函数返回t,反之则提示失败原因并返回f。 归档全局区块表的接口为pg_catalog.ledger_gchain_archive,操作为: SELECT pg_catalog.ledger_gchain_archive(); 如果归档成功,函数返回t,反之则提示失败原因并返回f。
  • 操作步骤 对指定用户历史表进行归档操作。 1 gaussdb=# SELECT pg_catalog.ledger_hist_archive('ledgernsp', 'usertable'); 执行结果如下: ledger_hist_archive --------------------- t (1 row) 用户历史表将归档为一条数据: gaussdb=# SELECT * FROM blockchain.ledgernsp_usertable_hist; rec_num | hash_ins | hash_del | pre_hash ---------+------------------+------------------+---------------------------------- 3 | e78e75b00d396899 | 8fcd74a8a6a4b484 | fd61cb772033da297d10c4e658e898d7 (1 row) 该结果表明当前节点用户历史表导出成功。 执行全局区块表导出操作。 1 gaussdb=# SELECT pg_catalog.ledger_gchain_archive(); 执行结果如下: ledger_gchain_archive ----------------------- t (1 row) 全局历史表将以用户表为单位归档为N(用户表数量)条数据: gaussdb=# SELECT * FROM gs_global_chain; blocknum | dbname | username | starttime | relid | relnsp | relname | relhash | globalhash | txcommand ----------+----------+----------+-------------------------------+-------+-----------+-----------+------------------+----------------------------------+----------- 1 | testdb | libc | 2021-05-10 19:59:38.619472+08 | 16388 | ledgernsp | usertable | 57c101076694b415 | be82f98ee68b2bc4e375f69209345406 | Archived. (1 row) 该结果表明,当前节点全局区块表导出成功。
  • 操作步骤 校验防篡改用户表ledgernsp.usertable与其对应的历史表是否一致。 1 gaussdb=# SELECT pg_catalog.ledger_hist_check('ledgernsp', 'usertable'); 查询结果如下: ledger_hist_check ------------------- t (1 row) 该结果表明防篡改用户表和用户历史表中记录的结果能够一一对应,保持一致。 查询防篡改用户表ledgernsp.usertable与其对应的历史表以及全局区块表中关于该表的记录是否一致。 1 gaussdb=# SELECT pg_catalog.ledger_gchain_check('ledgernsp', 'usertable'); 查询结果如下: ledger_gchain_check --------------------- t (1 row) 查询结果显示,上述三表中关于ledgernsp.usertable的记录保持一致,未发生篡改行为。
  • 背景信息 账本数据库校验功能目前提供两种校验接口,分别为:ledger_hist_check(text, text)和ledger_gchain_check(text, text)。普通用户调用校验接口,仅能校验自己有权限访问的表。 校验防篡改用户表和用户历史表的接口为pg_catalog.ledger_hist_check,操作为: SELECT pg_catalog.ledger_hist_check(schema_name text,table_name text); 如果校验通过,函数返回t,反之则提示失败原因并返回f。 校验防篡改用户表、用户历史表和全局区块表三者是否一致的接口为pg_catalog.ledger_gchain_check,操作为: SELECT pg_catalog.ledger_gchain_check(schema_name text, table_name text); 如果校验通过,函数返回t,反之则提示失败原因并返回f。
  • 操作步骤 创建防篡改模式。 例如,创建防篡改模式ledgernsp。 1 gaussdb=# CREATE SCHEMA ledgernsp WITH BLOCKCHAIN; 如果需要创建防篡改模式或更改普通模式为防篡改模式,则需设置enable_ledger参数为on。enable_ledger默认参数为off。 在防篡改模式下创建防篡改用户表。 例如,创建防篡改用户表ledgernsp.usertable。 gaussdb=# CREATE TABLE ledgernsp.usertable(id int, name text); 查看防篡改用户表结构及其对应的用户历史表结构。 gaussdb=# \d+ ledgernsp.usertable; gaussdb=# \d+ blockchain.ledgernsp_usertable_hist; 执行结果如下: gaussdb=# \d+ ledgernsp.usertable; Table "ledgernsp.usertable" Column | Type | Modifiers | Storage | Stats target | Description --------+---------+-----------+----------+--------------+------------- id | integer | | plain | | name | text | | extended | | hash_69dd43 | hash16 | | plain | | Has OIDs: no Options: orientation=row, compression=no History table name: ledgernsp_usertable_hist gaussdb=# \d+ blockchain.ledgernsp_usertable_hist; Table "blockchain.ledgernsp_usertable_hist" Column | Type | Modifiers | Storage | Stats target | Description ----------+--------+-----------+---------+--------------+------------- rec_num | bigint | | plain | | hash_ins | hash16 | | plain | | hash_del | hash16 | | plain | | pre_hash | hash32 | | plain | | Indexes: "gs_hist_69dd43_index" PRIMARY KEY, btree (rec_num int4_ops) TABLESPACE pg_default Has OIDs: no Options: internal_mask=263 防篡改表在创建时会自动增加一个名为hash的系统列,所以防篡改表单表最大列数为1599。 修改防篡改用户表数据。 例如,对防篡改用户表执行INSERT、UPDATE、DELETE操作。 gaussdb=# INSERT INTO ledgernsp.usertable VALUES(1, 'alex'), (2, 'bob'), (3, 'peter'); INSERT 0 3 gaussdb=# SELECT *, hash_69dd43 FROM ledgernsp.usertable ORDER BY id; id | name | hash_69dd43 ----+-------+------------------ 1 | alex | 1f2e543c580cb8c5 2 | bob | 8fcd74a8a6a4b484 3 | peter | f51b4b1b12d0354b (3 rows) gaussdb=# UPDATE ledgernsp.usertable SET name = 'bob2' WHERE id = 2; UPDATE 1 gaussdb=# SELECT *, hash_69dd43 FROM ledgernsp.usertable ORDER BY id; id | name | hash_69dd43 ----+-------+------------------ 1 | alex | 1f2e543c580cb8c5 2 | bob2 | 437761affbb7c605 3 | peter | f51b4b1b12d0354b (3 rows) gaussdb=# DELETE FROM ledgernsp.usertable WHERE id = 3; DELETE 1 gaussdb=# SELECT *, hash_69dd43 FROM ledgernsp.usertable ORDER BY id; id | name | hash_69dd43 ----+------+------------------ 1 | alex | 1f2e543c580cb8c5 2 | bob2 | 437761affbb7c605 (2 rows) 删除表和模式。 若要执行其他账本数据库章节的示例,请在执行完之后再执行当前步骤,否则请直接执行当前步骤。 gaussdb=# DROP TABLE ledgernsp.usertable; DROP TABLE gaussdb=# DROP SCHEMA ledgernsp; DROP SCHEMA
  • 外部密钥服务的身份验证 当数据库驱动访问华为云密钥管理服务时,为避免攻击者伪装为密钥服务,在数据库驱动与密钥服务建立https连接的过程中,可通过CA证书验证密钥服务器的合法性。为此,需提前配置CA证书,如果未配置,将不会验证密钥服务的身份。本节介绍如何下载与配置CA证书。 配置方法 在key_info参数的中,增加证书相关参数即可。 使用gsql时 gaussdb=# \key_info keyType=huawei_kms,iamUrl=https://iam.example.com/v3/auth/tokens,iamUser={ IAM 用户名},iamPassword={IAM用户密码},iamDomain={账号名},kmsProject={项目},iamCaCert=/路径/IAM的CA证书文件,kmsCaCert=/路径/KMS的CA证书文件 gaussdb=# \key_info keyType=huawei_kms,kmsProjectId={项目ID},ak={AK},sk={SK},kmsCaCert=/路径/KMS的CA证书文件 使用JDBC时 conn.setProperty("key_info", "keyType=huawei_kms," + "iamUrl=https://iam.example.com/v3/auth/tokens," + "iamUser={IAM用户名}," + "iamPassword={IAM用户密码}," + "iamDomain={账号名}," + "kmsProject={项目}," + "iamCaCert=/路径/IAM的CA证书文件," + "kmsCaCert=/路径/KMS的CA证书文件"); conn.setProperty("key_info", "keyType=huawei_kms, kmsProjectId={项目ID}, ak={AK}, sk={SK}, kmsCaCert=/路径/KMS的CA证书文件"); 获取证书 大部分浏览器均会自动下载网站对应的CA证书,并提供证书导出功能。虽然很多网站也提供自动下载CA证书的功能,但可能因本地环境中存在代理或网关,导致CA证书无法正常使用。所以,建议借助浏览器下载CA证书。下载方式如下: 由于使用restful接口访问密钥服务,当在浏览器输入接口对应的url时,可忽略下述2中的失败页面,因为即使在失败的情况下,浏览器也早已提前自动下载CA证书。 输入 域名 :打开浏览器,在华为云场景中,分别输入IAM服务器地址和KMS服务器地址,地址获取方式参见:生成主密钥阶段。 查找证书:在每次输入域名后,找到SSL连接相关信息,单击后会发现证书,继续单击可查看证书内容。 导出证书:在证书查看页面,可能会看到证书分为很多级,仅需要域名的上一级证书即可,选择该证书并单击导出,便可直接生成证书文件,即需要的证书文件。 上传证书:将导出的证书上传至应用端,并配置到上述参数中即可。
  • 前向兼容 在上文中,支持通过key_info设置访问外部密钥管理的参数: 使用gsql时,通过元命令\key_info xxx设置。 使用JDBC时,通过连接参数conn.setProperty(“key_info”, “xxx”)设置 为保持前向兼容,还支持通过环境变量等方式设置访问主密钥的参数。 第一次配置使用密态数据库时,可忽略下述方法。如果以前使用下述方法配置密态数据库,建议改用’key_info’配置 使用系统级环境变量配置的方式如下: export HUAWEI_KMS_INFO='iamUrl=https://iam.{项目}.myhuaweicloud.com/v3/auth/tokens,iamUser={IAM用户名},iamPassword={IAM用户密码},iamDomain={账号名},kmsProject={项目}' # 该方法中操作系统日志可能会记录环境变量中的敏感信息,使用过程中注意及时清理。 还可通过标准库接口设置进程级环境变量,不同语言设置方法如下: C/C++ setenv("HIS_KMS_INFO", "xxx"); GO os.Setenv("HIS_KMS_INFO", "xxx");
  • 执行加密表的预编译SQL语句 // 调用DB实例的Prepare方法创建预编译对象 delete_stmt, err := db.Prepare("delete from creditcard_info where name = $1;") defer delete_stmt.Close() // 调用预编译对象的Exec方法绑定参数并执行SQL语句 _, err = delete_stmt.Exec("mike")
共100000条
提示

您即将访问非华为云网站,请注意账号财产安全