华为云用户手册

  • 什么是数据库? 数据库是“按照数据结构来组织、存储和管理数据的仓库”。 广义上的数据库,在20世纪60年代已经在计算机中应用了。但这个阶段的数据库结构主要是层次或网状的,且数据和程序之间具备非常强的依赖性,应用较为有限。 现在通常所说的数据库指的是关系型数据库。关系数据库是指采用了关系模型来组织数据的数据库,其以行和列的形式存储数据,具有结构化程度高、独立性强、冗余度低等优点。1970年关系型数据库的诞生,真正彻底把软件中的数据和程序分开来,成为主流计算机系统不可或缺的组成部分。关系型数据库已经成为目前数据库产品中最重要的一员,几乎所有的数据库厂商新出的数据库产品都支持关系型数据库,即使一些非关系数据库产品也几乎都有支持关系数据库的接口。 关系型数据库的主要用于联机事务处理OLTP(On-Line Transaction Processing)主要进行基本的、日常的事务处理,例如银行交易等场景。
  • 什么是 数据湖 ? 在企业内部,数据是一类重要资产已经成为了共识。随着企业的持续发展,数据不断堆积,企业希望把生产经营中的所有相关数据都完整保存下来,进行有效管理与集中治理,挖掘和探索数据价值。 数据湖就是在这种背景下产生的。数据湖是一个集中存储各类结构化和非结构化数据的大型 数据仓库 ,它可以存储来自多个数据源、多种数据类型的原始数据,数据无需经过结构化处理,就可以进行存取、处理、分析和传输。数据湖能帮助企业快速完成异构数据源的联邦分析、挖掘和探索数据价值。 数据湖的本质,是由“数据存储架构+数据处理工具”组成的解决方案。 数据存储架构:要有足够的扩展性和可靠性,可以存储海量的任意类型的数据,包括结构化、半结构化和非结构化数据。 数据处理工具,则分为两大类: 第一类工具,聚焦如何把数据“搬到”湖里。包括定义数据源、制定数据同步策略、移动数据、编制数据目录等。 第二类工具,关注如何对湖中的数据进行分析、挖掘、利用。数据湖需要具备完善的数据管理能力、多样化的数据分析能力、全面的数据生命周期管理能力、安全的数据获取和数据发布能力。如果没有这些 数据治理 工具,元数据缺失,湖里的数据质量就没法保障,最终会由数据湖变质为数据沼泽。 随着大数据和AI的发展,数据湖中数据的价值逐渐水涨船高,价值被重新定义。数据湖能给企业带来多种能力,例如实现数据的集中式管理,帮助企业构建更多优化后的运营模型,也能为企业提供其他能力,如预测分析、推荐模型等,这些模型能刺激企业能力的后续增长。 对于数据仓库与数据湖的不同之处,可以类比为仓库和湖泊的区别:仓库存储着来自特定来源的货物;而湖泊的水来自河流、溪流和其他来源,并且是原始数据。 表2 数据湖与数据仓库的对比 维度 数据湖 数据仓库 应用场景 可以探索性分析所有类型的数据,包括机器学习、数据发现、特征分析、预测等。 通过历史的结构化数据进行数据分析。 使用成本 起步成本低,后期成本较高。 起步成本高,后期成本较低。 数据质量 包含大量原始数据,使用前需要清洗和标准化处理。 质量高,可作为事实依据。 适用对象 数据科学家、数据开发人员为主。 业务分析师为主。
  • DWS中单表查询性能与哪些因素有关? DWS采用Shared-nothing架构,数据是被分布式存储,因此分布键设计、单表存储数据量、分区数量都会影响单表的整体查询性能。 分布键设计 DWS默认会选择主键的第一列作为分布键。如果同时设置主键和分布键,则主键必须包含分布键。分布键决定了数据在各个分区之间的分布情况,如果分布键很好地分布在各个分区中,则可以使查询性能变得更好。 分布列选择不当,在数据导入后有可能出现数据分布倾斜,进而导致某些磁盘的使用明显高于其他磁盘,极端情况下会导致集群只读。合理的选择分布键,对表查询的性能至关重要。此外,合适的分布键还可以使数据的索引更快地创建和维护。 单表存储数据量 单表存储的数据量越大,查询性能就越差。当表中的数据量很大时,则需要考虑将数据进行分区存储。普通表若要转成分区表,需要新建分区表,然后把普通表中的数据导入到新建的分区表中。因此在初始设计表时,请根据业务提前规划是否使用分区表。 对表进行分区,一般需要遵循以下原则: 使用具有明显区间性的字段进行分区,比如日期、区域等字段。 分区名称应当体现分区的数据特征。比如关键字+区间特征。 将分区上边界的分区值定义为MAXVALUE,以防止可能出现的数据溢出。 分区数量 利用分区,可以将表和索引划分为一些更小、更易管理的单元。大幅减少搜索空间,从而提升访问性能。 使用分区数量会影响查询的性能。如果分区数量太小,则可能会使查询性能下降。 DWS支持范围分区(Range Partitioning)和列表分区(List Partitioning)功能,即根据表的一列或者多列,将要插入表的记录分为若干个范围(这些范围在不同的分区里没有重叠),然后为每个范围创建一个分区,用来存储相应的数据。其中,列表分区(List Partitioning)仅8.1.3及以上集群版本支持。 因此,在设计数据仓库时,需要考虑这些因素并进行实验来确定最佳设计方案。 父主题: 数据库性能
  • 场景二:执行计划中的or条件对普通用户执行语句逐一判断耗时 执行计划中的or条件里有权限相关的判断,此场景多发生在使用系统视图时。例如以下sql: 1 2 3 4 5 6 7 8 SELECT distinct(dtp.table_name), ta.table_catalog, ta.table_schema, ta.table_name, ta.table_type from information_schema.tables ta left outer join DBA_TAB_PARTITIONS dtp on (dtp.schema = ta.table_schema and dtp.table_name = ta.table_name) where ta.table_schema = 'public'; 一部分执行计划如下: 可以看到系统视图中的权限判断中多用or条件判断: 1 pg_has_role(c.relowner, 'USAGE'::text) OR has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFEREN CES , TRIGGER'::text) OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES'::text) 由于dbadmin用户pg_has_role总能返回true,因此or之后的条件无需继续判断; 而普通用户的or条件需要逐一判断,如果数据库中表个数比较多,最终会导致普通用户比dbadmin需要更长的执行时间。 这种场景如果输出结果集很少,可以考虑尝试设置set enable_hashjoin = off; set enable_seqscan = off; 走index + nestloop的计划。
  • 专属限额 专属限额:限定资源池中数据库用户在执行作业时可使用的最大CPU核数占总核数的百分比。 专属限额有两层含义: 专属:CPU是某个控制组专属的,其他控制组不能使用空闲的CPU资源。 限额:只能使用限额配置的CPU资源,其他控制组空闲的CPU资源,也不能抢占。 专属限额基于cpuset.cpu实现,通过合理的限额设置可以实现控制组之间CPU资源的绝对隔离,各控制组间任务互不影响。但因为CPU的绝对隔离,因此在控制组空闲时就会导致CPU资源的极大浪费,因此限额设置不能太大。从作业性能来看并不是限额越大越好。 例如:10个作业运行在10个CPU上,CPU平均使用率5%左右;10个作业运行在5个CPU上,CPU平均使用率10%左右。通过上面共享配额的分析可知:虽然10个作业运行在5个CPU上CPU使用率很低,看似空闲,但是相对10个作业运行在10个CPU上还是存在某种程度的CPU资源争抢,因此10个作业运行在10个CPU上性能要好于运行在5个CPU上。但也不是越多越好,10个作业运行在20个CPU上,在任意一个时刻,总会至少10个CPU是空闲的,因此理论上10个作业运行在20个CPU上并不会比运行在10个CPU上性能更好。对于并发为N的控制组,分配cpus小于N的情况下,CPU越多作业性能越好;但是当分配CPUS大于N的情况下,性能就不会有任何提升了。
  • CPU资源管理应用场景 CPU共享配额和专属限额的管控方式各有优劣,共享配额能够实现CPU资源的充分利用,但是各控制组之间资源隔离不彻底,可能影响查询性能;专属限额的管控方式可以实现CPU资源的绝对隔离,但是在CPU资源空闲时会造成CPU资源的浪费。相对专属限额来说,共享配额拥有更高的CPU使用率和更高的整体作业吞吐量;相对共享配额来说,专属限额CPU隔离彻底,更满足性能敏感用户的使用诉求。 数据库系统中运行多种类型作业出现CPU争抢时,可根据不同场景,选择不同的CPU资源管控方式: 场景一:实现CPU资源的充分利用,不关注单一类型作业的性能,主要关注CPU整体吞吐量。 应用建议:不建议进行用户之间的CPU隔离管控,无论哪一种CPU管控都会对CPU整体使用率产生影响。 场景二:允许一定程度的CPU资源争抢和性能损耗,在CPU空闲情况下实现CPU资源充分利用,在CPU满负载情况下需要各业务类型按比例使用CPU。 应用建议:可以采用基于cpu.shares的共享配额管控方式,在实现满负载CPU隔离管控前提下,尽量提高CPU整体使用率。 场景三:部分作业对性能敏感,允许CPU资源的浪费。 应用建议:可以采用基于cpuset.cpu的专属限额管控方式,实现不同类型作业之间的CPU绝对隔离。
  • CPU资源管控概述 在不同的业务场景中,对数据库的系统资源(CPU资源、内存资源、IO资源和存储资源)进行合理的分配,保证执行查询时有充足的系统资源,确保查询性能,可以维持业务稳定性。 DWS的资源管理功能支持用户根据自身业务将资源按需划分成不同的资源池,不同资源池之间资源互相隔离。再通过关联数据库用户将其关联至不同的资源池,用户SQL查询时将根据“用户-资源池”的关联关系将查询转至资源池中执行。通过指定资源池上可并行运行的查询数、单查询内存上限以及资源池可使用的内存和CPU资源,从而实现对不同业务之间的资源限制和隔离,满足数据库混合负载需求。 DWS主要利用cgroup(control group,控制组)进行CPU资源管控,涉及CPU、cpuacct、cpuset子系统。CPU共享配额管控基于CPU子系统的cpu.shares实现,该配置方法的好处是:OS CPU没有占满的情况下,不触发CPU管控;CPU专属限额管控基于cpuset实现;cpuacct子系统主要用于CPU资源使用的监控。 在DWS管理控制台使用资源管理配置功能创建资源池时,根据业务需要对CPU资源管理的“共享配额”和“专属配额”进行配置。
  • 共享配额 共享配额:关联在当前资源池的用户在执行作业时可以使用的CPU时间比例。 共享配额有两层含义: 共享:CPU是所有控制组共享的,其他控制组能够使用空闲的CPU资源。 配额:业务繁忙、CPU满负载情况下,控制组之间按照配额比例进行CPU抢占。 共享配额基于cpu.shares实现,只有在CPU满负载情况下生效,因此在CPU空闲情况下并不能保证控制组能够抢占到配额比例的CPU资源。CPU空闲并不能理解为没有CPU资源争抢,控制组内任务可以任意使用CPU。虽然CPU平均使用率可能不高,但是某个特定时刻还是可能存在CPU资源争抢的。 例如:10个CPU上运行10个作业,每个CPU上运行1个作业,这种情况下各作业在任意时刻请求CPU都可以瞬间得到响应,作业之间没有任何CPU资源的争抢;但假如10个CPU上运行20个作业,因为作业不会一直占用CPU,在某些时间可能等待IO、网络等,因此CPU使用率可能并不高,此时CPU资源看似空闲,但是在某个时刻可能出现2~N作业同时请求一个CPU的情况出现,即会导致CPU资源争抢,影响作业性能。
  • 动态行转列 8.1.2及以上集群版本可使用GROUP_CONCAT生成列存语句。 1 2 3 4 5 6 7 8 9 10 SELECT group_concat(concat('sum(IF(subject = ''', subject, ''', score, 0)) AS "', name, '"'))FROM students_info; group_concat ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------ sum(IF(subject = 'literature', score, 0)) AS "jack",sum(IF(subject = 'literature', score, 0)) AS "lily",sum(IF(subject = 'literature', score, 0)) AS "matu",sum(IF(subject = 'math', score, 0)) AS "jack",sum(IF (subject = 'math', score, 0)) AS "lily",sum(IF(subject = 'math', score, 0)) AS "matu",sum(IF(subject = 'physics', score, 0)) AS "jack",sum(IF(subject = 'physics', score, 0)) AS "lily",sum(IF(subject = 'physics ', score, 0)) AS "matu" (1 row) 8.1.1及更低版本中可用LISTAGG生成列存语句。 1 2 3 4 5 6 7 8 SELECT listagg(concat('sum(case when subject = ''', subject, ''' then score else 0 end) AS "', subject, '"'),',') within GROUP(ORDER BY 1)FROM (select distinct subject from students_info); listagg ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- sum(case when subject = 'literature' then score else 0 end) AS "literature",sum(case when subject = 'physics' then score else 0 end) AS "physics",sum(case when subject = 'math' then score else 0 end) AS "math " (1 row) 再通过视图动态重建: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 CREATE OR REPLACE FUNCTION build_view() RETURNS VOID LANGUAGE plpgsql AS $$ DECLARE sql text; rec record; BEGIN sql := 'select LISTAGG( CONCAT( ''sum(case when subject = '''''', subject, '''''' then score else 0 end) AS "'', subject, ''"'' ) ,'','' ) within group(order by 1) from (select distinct subject from students_info);'; EXECUTE sql INTO rec; sql := 'drop view if exists get_score'; EXECUTE sql; sql := 'create view get_score as select name, ' || rec.LISTAGG || ' from students_info group by name'; EXECUTE sql; END$$; 执行重建: 1 CALL build_view(); 查询视图: 1 2 3 4 5 6 7 SELECT * FROM get_score; name | literature | physics | math ------+------------+---------+------ matu | 85 | 90 | 75 lily | 92 | 80 | 95 jack | 95 | 95 | 90 (3 rows)
  • 列转行 使用union all,将各科目(math、physics和literature)整合为一列,示例如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 SELECT * FROM ( SELECT name, 'math' AS subject, math AS score FROM students_info1 union all SELECT name, 'physics' AS subject, physics AS score FROM students_info1 union all SELECT name, 'literature' AS subject, literature AS score FROM students_info1 ) order by name; name | subject | score ------+------------+------- jack | math | 90 jack | physics | 95 jack | literature | 95 lily | math | 95 lily | physics | 80 lily | literature | 92 matu | math | 75 matu | physics | 90 matu | literature | 85 (9 rows)
  • 静态行转列 静态行转列需要手动指定每一列的列名,如果存在则取其对应值,否则将赋其默认值0。 1 2 3 4 5 6 7 8 9 10 SELECT name, sum(case when subject='math' then score else 0 end) as math, sum(case when subject='physics' then score else 0 end) as physics, sum(case when subject='literature' then score else 0 end) as literature FROM students_info GROUP BY name; name | math | physics | literature ------+------+---------+------------ matu | 75 | 90 | 85 lily | 95 | 80 | 92 jack | 90 | 95 | 95 (3 rows)
  • 示例表 创建行存表students_info并插入数据。 1 2 3 4 5 6 7 8 9 10 CREATE TABLE students_info(name varchar(20),subject varchar(100),score bigint) distribute by hash(name); INSERT INTO students_info VALUES('lily','math',95); INSERT INTO students_info VALUES('lily','physics',80); INSERT INTO students_info VALUES('lily','literature',92); INSERT INTO students_info VALUES('matu','math',75); INSERT INTO students_info VALUES('matu','physics',90); INSERT INTO students_info VALUES('matu','literature',85); INSERT INTO students_info VALUES('jack','math',90); INSERT INTO students_info VALUES('jack','physics',95); INSERT INTO students_info VALUES('jack','literature',95); 查看表students_info信息。 1 2 3 4 5 6 7 8 9 10 11 12 SELECT * FROM students_info; name | subject | score ------+------------+------- matu | math | 75 matu | physics | 90 matu | literature | 85 lily | math | 95 lily | physics | 80 lily | literature | 92 jack | math | 90 jack | physics | 95 jack | literature | 95 创建列存表students_info1并插入数据。 1 2 3 4 CREATE TABLE students_info1(name varchar(20), math bigint, physics bigint, literature bigint) with (orientation = column) distribute by hash(name); INSERT INTO students_info1 VALUES('lily',95,80,92); INSERT INTO students_info1 VALUES('matu',75,90,85); INSERT INTO students_info1 VALUES('jack',90,95,95); 查看表students_info1信息。 1 2 3 4 5 6 7 SELECT * FROM students_info1; name | math | physics | literature ------+------+---------+------------ matu | 75 | 90 | 85 lily | 95 | 80 | 92 jack | 90 | 95 | 95 (3 rows)
  • 场景介绍 以学生成绩为例: 老师会按照学科录入成绩,每科老师都会单独录入每个学生对应学科的成绩,而每位学生只关注自己各科的成绩。如果把老师录入数据作为原始表,那么学生查看自己的成绩就要用到行转列;如果让学生自己填写各科的成绩并汇总,然后老师去查自己学科所有学生的成绩,那就是列转行。 行转列与列转行的示意图如下: 图1 示意图 行转列 将多行数据转换成一行显示,或将一列数据转换成多列显示。 列转行 将一行数据转换成多行显示,或将多列数据转换成一列显示。
  • 场景一:事务开启后没有提交,语句处于idle in transaction 手动BEGIN/START TRANSACTION开启事务,执行某语句后,不执行COMMIT/ROLLBACK,此时执行如下命令查看视图PGXC_STAT_ACTIVITY: 1 SELECT state, query, query_id FROM pgxc_stat_activity; 查看结果显示:该语句状态为idle in transaction。 解决方法:这种场景下需要手动对开启的事务执行COMMIT/ROLLBACK即可。
  • 场景三:大量SAVEPOINT/RELEASE语句处于idle in transaction(8.1.0之前集群版本) 执行如下命令查看PGXC_STAT_ACTIVITY视图: 1 SELECT coorname,pid,query_id,state,query,usename FROM pgxc_stat_activity WHERE usename='jack'; 结果显示SAVEPOINT/RELEASE语句处于idle in transaction。
  • 场景二:存储过程中有DDL语句,该存储过程结束前,其他节点上DDL语句执行完后的状态是idle in transaction 先创建存储过程: 1 2 3 4 5 6 7 8 9 10 11 CREATE OR REPLACE FUNCTION public.test_sleep() RETURNS void LANGUAGE plpgsql AS $$ BEGIN truncate t1; truncate t2; EXECUTE IMMEDIATE 'select pg_sleep(6)'; RETURN; END$$; 再执行如下命令查看PGXC_STAT_ACTIVITY视图: 1 SELECT coorname,pid,query_id,state,query,usename FROM pgxc_stat_activity WHERE usename='jack'; 查看结果显示:truncate t2处于idle in transaction状态,coorname为coordinator2。说明cn2上该语句已经执行完成,该存储过程在执行下一条语句。 解决方法:此类场景是由于存储过程执行慢导致,等存储过程执行完成即可,也可考虑优化存储过程中执行时间较长的语句。
  • 8.1.3及以上版本 8.1.3及以上版本的集群,AUTO VACUUM默认是打开的(由GUC参数autovacuum控制),用户通过设置对应的GUC参数后,系统会自动触发所有系统表和用户的行存表进行VACUUM FULL,用户不需要手动执行vacuum。 autovacuum_max_workers = 0,系统表和普通表都不会触发。 autovacuum = off,普通表不会触发,但系统表会触发。 以上仅针对行存表的AUTO VACCUM触发,如果需要针对列存表做自动触发VACUUM,还需要用户在管理控制台上配置智能调度任务。具体参见运维计划。
  • 使用string_agg 场景:使用string_agg查询表employee,出现查询结果不一致。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 SELECT * FROM employee; empno | ename | job | mgr | hiredate | sal | comm | deptno -------+--------+---------+------+---------------------+-------+------+-------- 7654 | MARTIN | SALEMAN | 7698 | 2022-11-08 00:00:00 | 12000 | 1400 | 30 7566 | JONES | MANAGER | 7839 | 2022-11-08 00:00:00 | 32000 | 0 | 20 7499 | ALLEN | SALEMAN | 7698 | 2022-11-08 00:00:00 | 16000 | 300 | 30 (3 rows) SELECT count(*) FROM (select deptno, string_agg(ename, ',') from employee group by deptno) t1, (select deptno, string_agg(ename, ',') from employee group by deptno) t2 where t1.string_agg = t2.string_agg; count ------- 2 (1 row) SELECT count(*) FROM (select deptno, string_agg(ename, ',') from employee group by deptno) t1, (select deptno, string_agg(ename, ',') from employee group by deptno) t2 where t1.string_agg = t2.string_agg; count ------- 1 (1 row) 原因分析: String_agg函数的作用是将组内的数据合并成一行,但是如果某用户的用法是string_agg(ename, ',') ,结果集就是不稳定的,因为没有指定组合的顺序。例如,上述语句中,对于select deptno, string_agg(ename, ',') from employee group by deptno; 输出结果既可以是: 1 30 | ALLEN,MARTIN 也可能是: 1 30 |MARTIN,ALLEN 两个结果都是合理的,因此上述关联场景下,有可能出现t1这个subquery中的结果和t2这个subquery中的结果对于deptno=30时的输出结果不一致。 解决方法: String_agg中增加order by排序,保证按顺序拼接。 1 SELECT count(*) FROM (select deptno, string_agg(ename, ',' order by ename desc) from employee group by deptno) t1 ,(select deptno, string_agg(ename, ',' order by ename desc) from employee group by deptno) t2 where t1.string_agg = t2.string_agg;
  • 数据库兼容模式 场景:在数据库中查询空串结果不一致。 database1(TD兼容模式): 1 2 3 4 5 td=# select '' is null; isnull -------- f (1 row) database2(ORA兼容模式): 1 2 3 4 5 ora=# select '' is null; isnull -------- t (1 row) 原因分析: 查询空串结果不同是由于不同数据库兼容模式下空串与null语法有差异导致。 目前,DWS支持三种数据库兼容模式:Oracle、TD和MySql,不同兼容模式下语法和行为存在差异,兼容性差异说明可参考Oracle、Teradata和MySQL语法兼容性差异。 不同兼容模式下的database表现出不同的兼容性行为属于正常现象。可以通过查看select datname, datcompatibility from pg_database;确认数据库兼容性设置是否相同。 解决方法: 这种场景下只能将两个database的兼容性模式设置为一致的才能解决。Database的DBCOMPATIBILITY属性不支持ALTER,只能通过新建数据库的方法,在创建数据库时指定相同的DBCOMPATIBILITY属性解决。
  • 数据库兼容性行为配置项behavior_compat_options设置不同 场景:add_months函数计算结果不一致。 database1: 1 2 3 4 5 SELECT add_months('2018-02-28',3) from dual; add_months --------------------- 2018-05-28 00:00:00 (1 row) database2: 1 2 3 4 5 SELECT add_months('2018-02-28',3) from dual; add_months --------------------- 2018-05-31 00:00:00 (1 row) 原因分析: 数据库兼容性配置项behavior_compat_options不同会导致部分行为不同,该参数选项可参考behavior_compat_options中的相关选项描述。 此场景中behavior_compat_options配置项中的end_month_calculate参数控制add_months函数计算逻辑配置项。设置end_month_calculate配置项时,如果param1的日期(Day字段)为月末,并且param1的日期(Day字段)比result月份的月末日期比小,计算结果中的日期字段(Day字段)和result的月末日期保持一致。 解决方法: 需要将数据库中参数behavior_compat_options的兼容性配置项设置为一致。该参数类型为USERSET类型,可session级别设置或集群级修改。
  • 使用UN LOG GED表 场景: 使用unlogged表后,在集群重启后,关联查询结果集异常,查看unlogged表缺少部分数据。 原因分析: 如果设置max_query_retry_times为0,且在建表时指定UNLOGGED关键字,则创建的表为非日志表。在非日志表中写入的数据不会被写入到预写日志中,这样就会比普通表快很多。但是非日志表在冲突、执行操作系统重启、强制重启、切断电源操作或异常关机后会被自动截断,会造成数据丢失的风险。非日志表中的内容也不会被复制到备服务器中。在非日志表中创建的索引也不会被自动记录。因此当集群发生异常重启(进程重启、节点故障、集群重启)时,会导致部分内存中的数据未及时落盘,造成部分数据丢失,从而导致结果集异常。 解决方法: unlogged表在集群异常情况下的安全性无法保证,一般不能作为业务表使用,更多的场景是作为临时表使用。当出现集群故障后,为了保证数据正常,需要重建unlogged表或将数据备份后重新导入数据库。
  • 自定义函数属性设置不合理 场景:自定义函数get_count()并调用该函数出现结果不一致场景。 1 2 3 4 5 6 7 8 9 10 11 CREATE FUNCTION get_count() returns int SHIPPABLE as $$ declare result int; begin result = (select count(*) from test); --test表是hash表 return result; end; $$ language plpgsql; 调用该函数。 1 2 3 4 5 6 7 8 9 10 11 SELECT get_count(); get_count ----------- 2106 (1 row) SELECT get_count() FROM t_src; get_count ----------- 1032 (1 row) 原因分析: 由于该函数指定了SHIPPABLE的函数属性,因此生成计划时该函数会下推到DN上执行,该函数下推到DN后,由于函数定义中的test表是hash表,因此每个DN上只有该表的一部分数据,所以select count(*) from test; 返回的结果不是test表全量数据的结果,而是每个DN上部分数据的结果,因此导致加上from表后函数返回预期发生变化。 解决方法: 以下两种方法任选其一即可(推荐第一种方法): 将函数改为不下推:ALTER FUNCTION get_count() not shippable; 将函数中用到的表改为复制表,这样每个DN上都是一份该表的全量数据,即使下推到DN执行,也能保证结果集符合预期。
  • 窗口函数中使用排序后取部分结果 场景: 窗口函数row_number()中使用排序后查询表t3的c列,两次查询结果不同。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 SELECT * FROM t3 order by 1,2,3; a | b | c ---+---+--- 1 | 2 | 1 1 | 2 | 2 1 | 2 | 3 (3 rows) SELECT c,rn FROM (select c,row_number() over(order by a,b) as rn from t3) where rn = 1; c | rn ---+---- 1 | 1 (1 row) SELECT c,rn FROM (select c,row_number() over(order by a,b) as rn from t3) where rn = 1; c | rn ---+---- 3 | 1 (1 row) 原因分析: 如上所示,执行同一条语句:select c,rn from (select c,row_number() over(order by a,b) as rn from t3) where rn = 1; 两次查询结果不同,因为在窗口函数的排序列a、b上存在重复值1、2且重复值在c列上的值不同,就会导致每次按照a,b列排序结果取第一条时,所取的数据是随机的,造成结果集不一致。 解决方法: 该场景需要将取值列c列也加到排序中,使排序结果获取的第一条数据固定。 1 2 3 4 5 SELECT c,rn FROM (select c,row_number() over(order by a,b,c) as rn from t3) where rn = 1; c | rn ---+---- 1 | 1 (1 row)
  • 子视图/子查询中使用排序 场景: 创建表test和视图v后,子查询中使用排序查询表test,出现查询结果不一致。 1 2 3 4 5 6 CREATE TABLE test(a serial ,b int); INSERT INTO test(b) VALUES(1); INSERT INTO test(b) SELECT b FROM test; … INSERT INTO test(b) SELECT b FROM test; CREATE VIEW v as SELECT * FROM test ORDER BY a; 问题SQL: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 SELECT * FROM v limit 1; a | b ---+--- 3 | 1 (1 row) SELECT * FROM (select * from test order by a) limit 10; a | b ----+--- 14 | 1 (1 row) SELECT * FROM test order by a limit 10; a | b ---+--- 1 | 1 (1 row) 原因分析: 对于子视图和子查询中order by是无效的。 解决方法: 不建议在子视图和子查询中使用order by,若要保证结果有序,需在最外层查询中使用order by。
  • 子查询limit 场景:子查询中使用limit,两次查询结果不一致。 1 2 3 4 5 6 7 8 9 10 11 SELECT * FROM (select a from test limit 1 ) order by 1; a --- 5 (1 row) SELECT * FROM (select a from test limit 1 ) order by 1; a --- 1 (1 row) 原因分析: 子查询中的limit会导致获取随机结果,从而最终查询结果为随机提取。 解决方法: 要保证最终查询结果的稳定,需避免在子查询中使用limit。
  • DWS以pg_toast_temp*或pg_temp*开头的Schema是什么? 查询Schema列表的时候,发现查询结果存在pg_temp*或pg_toast_temp*的Schema,如下图所示。 1 SELECT * FROM pg_namespace; 这些Schema是在创建临时表时,该临时表通过每个会话独立的以pg_temp开头的Schema来保证只对当前会话可见,因此,不建议用户在日常操作中手动删除以pg_temp,pg_toast_temp开头的Schema。 临时表只在当前会话可见,本会话结束后会自动删除,这些相应的Schema也会被删除。 父主题: 数据库使用
  • 场景一:返回结果集很大 以行存表的Seq Scan和Index Scan为例: Seq Scan:按照表的记录的排列顺序从头到尾依次检索扫描,每次扫描要取到所有的记录。这也是最简单最基础的扫表方式,扫描的代价比较大。 Index Scan:对于指定的查询,先扫描一遍索引,从索引中找到符合要求的记录的位置(指针),再定位到表中具体的Page去获取,即先走索引,再读表数据。 因此,根据两种扫描方式的特点可知,多数情况下,Index Scan要比Seq Scan快。但是如果获取的结果集占所有数据的比重很大时(超过70%),这时Index Scan因为要先扫描索引再读表数据反而不如直接全表扫描的速度快。
  • 查询分区边界 1 2 3 4 5 6 7 8 9 SELECT relname, partstrategy, boundaries FROM pg_partition where parentid=(select parentid from pg_partition where relname='my_table'); relname | partstrategy | boundaries -------------+--------------+------------ my_table | r | my_table_p1 | r | {600} my_table_p2 | r | {800} my_table_p3 | r | {950} my_table_p4 | r | {1000} (5 rows)
  • 查询数据在各DN分布 1 2 3 4 5 6 7 8 9 10 SELECT table_skewness('my_table'); table_skewness ------------------------------------ ("dn_6007_6008 ",3,50.000%) ("dn_6009_6010 ",2,33.333%) ("dn_6003_6004 ",1,16.667%) ("dn_6001_6002 ",0,0.000%) ("dn_6005_6006 ",0,0.000%) ("dn_6011_6012 ",0,0.000%) (6 rows)
  • 查询某一有数据分布DN上分区P1所对应的cudesc和delta表名称 1 2 3 4 5 6 EXECUTE DIRECT ON (dn_6003_6004) 'select a.relname from pg_class a, pg_partition b where (a.oid=b.reldeltarelid or a.oid=b.relcudescrelid) and b.relname=''my_table_p1'''; relname ---------------------- pg_delta_part_60317 pg_cudesc_part_60317 (2 rows)
共100000条
提示

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