Greenplum Database 管理员指南 (V6.2.1) - 4

 

  Index      Manuals     Greenplum Database 管理员指南 (V6.2.1)

 

Search            copyright infringement  

 

   

 

   

 

Content      ..     2      3      4      5     ..

 

 

 

Greenplum Database (V6.2.1) - 4

 

 

Greenplum Database 管理员指南 V6.2.1
=# DROP INDEX title_idx;
在装载大量数据时应该先删除索引、再装载数据、然后再重新创建索引这样可
能比直接装载数据要快很多。编者建议使用这样的操作。编者提醒有些版本存在不会
自动删除分区索引的情况在删除ROOT表的索引时分区的索引不会自动删除(
6版本会自动删除)如果没有自动删除可能需要逐个分区删除。
创建和管理视图
对于那些使用频繁或者比较复杂的查询通过创建视图(VIEW)可以把其当作一张
表来使用SELECT语句访问。视图不会存储真实的数据(后面提到的物化视图会存储数
)。每当视图被访问时创建视图的查询语句会被作为子查询而执行。
使用视图可能需要考虑以下几个因素
创建视图的最佳实践 -- 如何创建视图才最合理。
视图的依赖关系 -- 查看视图信息查看视图依赖哪些对象GP中视图有强依
赖关系这些依赖信息存储在系统表中。
视图是如何被存储的 -- 描述视图依赖的机制。
创建视图
使用CREATE VIEW命令将查询语句定义为一个视图。例如
=# CREATE VIEW comedies AS SELECT * FROM films WHERE kind = 'comedy';
注意官方文档总是这样说视图会忽略ORDER BY或者SORT操作虽然在定义视图
的语句中可以使用ORDER BY子句但该子句不会得到执行除非有LIMIT子句同时出
现。但实际上很多时候VIEW中定义了ORDER BY子句之后查询的时候真的
会是有排序的(也许有版本差异6版本真的会执行)
删除视图
使用DROP VIEW命令删除已有的视图。例如
版权所有Esena(陈淼 ) 编写陈淼 - 150 -
Greenplum Database 管理员指南 V6.2.1
=# DROP VIEW topten;
如果视图之上还有其他视图依赖此视图使用DROP VIEW . . . CASCADE命令
可以将这些依赖的对象一同删除在此视图被其他视图依赖时没有CASCADE选项的
DROP VIEW的操作将会报错失败。
创建视图的最佳实践
在创建视图时需要时刻谨记视图其实就是一个SQL实际执行时其与直接写出
SQL没有任何区别优化器可能会把视图中的SQL拆开进行优化调整计算的顺序。
视图的常见用处
视图可以把一个复杂的SQL作为一个简单的对象来重复使用。
视图可以把一张表以另一种形式呈现例如设置不同的数据过滤条件不同的访问
权限等这样不需要再创建一张表。
如果一种SQL查询只是在个别语句中用到可以使用SELECT命令的WITH子句来实
可能不需要为此而创建一张很少用到的视图。另外WITH子句中的查询还可以将
执行计划分离WITH子句中的查询优化器不会尝试进行拆解而是直接当做一个整
体来执行因此往往会是一个非常好的选择。
通常不要创建多层视图 -- 就是基于其他视图来创建视图这样会极大增加视
图的管理难度因为在GP中视图是有强依赖关系的当需要删除并重建(CREATE OR
REPLACE命令不可以修改视图的字段定义)某个视图时所有依赖该视图的上层视图
都需要被删除。
有两类使用视图的方式是应该避免的刚刚讲的多层视图还有其他弊端
定义了很多层的视图最后的查询语句看起来很简单 -- 这样的设计看起来一点
都不酷因为当遇到问题需要排查时执行计划可能会变得很复杂导致无从下手。
以前有不少人以写得出一个巨大无比的单条SQL搞定一个复杂的问题而自我陶醉
带来的后续维护问题是痛苦的反而拆分为多个相对简单的步骤更便于排查问题和
维护。编者认为优雅而高效的解决问题才最重要故意把问题复杂化不值得提倡
那不能证明能力。
定义一个大而全的视图涉及很多表然后可以用于各种场景 -- 这种设计也是
极其糟糕的乍一看很酷实际上因为适用的场景多就很难兼顾到每个场景
所以可能有的场景SQL执行的比较优化而有的场景SQL执行的很糟糕这样的
视图想要优化到每个场景都用起来都能表现出很好的性能那几乎是不可能的。
版权所有Esena(陈淼 ) 编写陈淼 - 151 -
Greenplum Database 管理员指南 V6.2.1
视图的依赖关系
如果要删除一张表而该表上有视图依赖则必须使用CASCADE来删除或者先
把依赖的视图全部删掉。如下面的例子所示
=# CREATE TABLE t (
id integer,
name text
);
=# CREATE VIEW v AS SELECT * FROM t;
=# DROP TABLE t;
ERROR: cannot drop table t because other objects depend on it
DETAIL: view v depends on table t
HINT: Use DROP ... CASCADE to drop the dependent objects too.
=# ALTER TABLE t DROP id;
ERROR: cannot drop column id of table t because other objects depend on it
DETAIL: view v depends on column id of table t
HINT: Use DROP ... CASCADE to drop the dependent objects too.
这个例子表明当视图对表有依赖两者的CREATE必须是有顺序的必须先创建
了表才能基于该表创建视图。不可能在创建好需要的表之前创建视图。
如果要修改一张表例如要把字段的数据类型从integer改为bigint这种需求
很正常可能因为业务的调整需要存储更大的数字但是因为表上有视图使用了这
个字段那就没办法直接完成修改可以先删除这些依赖的视图然后修改字段类型
之后再重建这些被删掉的视图。此时就需要用到视图依赖信息。
查看视图的依赖关系
为了便于阅读和学习实践下面将使用举例的方式来展示表及字段与视图之间的依
赖关系。这里会介绍以下内容
查看一张表上直接依赖的视图
查看一个字段上的直接依赖
版权所有Esena(陈淼 ) 编写陈淼 - 152 -
Greenplum Database 管理员指南 V6.2.1
查看视图与依赖表的模式信息
查看视图的定义
查看视图的多层依赖关系
首先创建一些表和视图用于本节的示例使用
=# CREATE TABLE t1 (
id integer PRIMARY KEY,
val text NOT NULL
);
=# INSERT INTO t1 VALUES (1, 'one'), (2, 'two'), (3, 'three');
=# CREATE FUNCTION f() RETURNS text
LANGUAGE sql AS 'SELECT ''suffix''::text';
=# CREATE VIEW v1 AS SELECT max(id) AS id FROM t1;
=# CREATE VIEW v2 AS SELECT t1.val FROM t1 JOIN v1 USING (id);
=# CREATE VIEW v3 AS SELECT val || f() FROM t1;
=# CREATE VIEW v5 AS SELECT f();
=# CREATE SCHEMA mytest;
=# CREATE TABLE mytest.tm1 (
id integer,
val text NOT NULL
);
=# INSERT INTO mytest.tm1 VALUES (1, 'one'), (2, 'two'), (3, 'three');
=# CREATE VIEW vm1 AS SELECT id FROM mytest.tm1 WHERE id < 3;
=# CREATE VIEW mytest.vm1 AS SELECT id FROM public.t1 WHERE id < 3;
=# CREATE VIEW vm2 AS SELECT max(id) AS id FROM mytest.tm1;
=# CREATE VIEW mytest.v2a AS SELECT t1.val FROM public.t1
JOIN public.v1 USING (id);
查看一张表上直接依赖的视图
要查看哪些视图直接依赖于t1使用一个关联多张系统表的SQL来查询这些系
统表中包含了依赖关系并限制查询只返回视图类的依赖
=# SELECT v.oid::regclass AS view,d.refobjid::regclass AS ref_object
FROM pg_depend AS d JOIN pg_rewrite AS r ON r.oid = d.objid
JOIN pg_class AS v ON v.oid = r.ev_class
WHERE v.relkind = 'v' AND d.classid = 'pg_rewrite'::regclass
AND d.deptype = 'n' AND d.refclassid = 'pg_class'::regclass
AND d.refobjid = 't1'::regclass
GROUP BY 1,2 ORDER BY 1,2;
版权所有Esena(陈淼 ) 编写陈淼 - 153 -
Greenplum Database 管理员指南 V6.2.1
查询中使用了对象标识符类型的强制类型转换相关信息可以参考PostgreSQL
相关文档。由于原英文手册展示比较复杂体现出了多字段依赖关系这里已经进行了
分组去重。对上述SQL进行适当的修改之后可以查询哪些视图依赖于f函数
=# SELECT v.oid::regclass AS view,d.refobjid::regproc as ref_object
FROM pg_depend AS d JOIN pg_rewrite AS r ON r.oid = d.objid
JOIN pg_class AS v ON v.oid = r.ev_class
WHERE v.relkind = 'v' AND d.classid = 'pg_rewrite'::regclass
AND d.deptype = 'n' AND d.refclassid = 'pg_proc'::regclass
AND d.refobjid = 'f'::regproc
GROUP BY 1,2 ORDER BY 1,2;
查看一个字段上的直接依赖
修改上面的查询语句可以用于查询依赖于某个字段的视图当需要修改表上的某
个字段或者删除该字段时(编者建议不要在一张大表上直接修改字段定义可能会是
一个性能很差的操作应该制定规范采用重建表的方式进行)会用到这种查询
里将会用到pg_attribute系统表
=# SELECT v.oid::regclass AS view,d.refobjid::regclass AS ref_object,
a.attname AS col_name
FROM pg_attribute AS a
JOIN pg_depend AS d ON d.refobjsubid = a.attnum AND d.refobjid = a.attrelid
JOIN pg_rewrite AS r ON r.oid = d.objid
JOIN pg_class AS v ON v.oid = r.ev_class
WHERE v.relkind = 'v' AND d.classid = 'pg_rewrite'::regclass
AND d.refclassid = 'pg_class'::regclass AND d.deptype = 'n'
AND a.attrelid = 't1'::regclass AND a.attname = 'id'
ORDER BY 1,2,3;
版权所有Esena(陈淼 ) 编写陈淼 - 154 -
Greenplum Database 管理员指南 V6.2.1
查看视图与依赖表的模式信息
如果在多个模式中创建了视图而视图依赖的表也分散在不同的模式中则可以将
视图以及依赖的表以及所属的模式信息一起查询出来。这里将涉及pg_namespace
统表不过会忽略系统模式
=# SELECT v.oid::regclass AS view,ns.nspname AS schema,
d.refobjid::regclass AS ref_object
FROM pg_depend AS d
JOIN pg_rewrite AS r ON r.oid = d.objid
JOIN pg_class AS v ON v.oid = r.ev_class
JOIN pg_namespace AS ns ON ns.oid = v.relnamespace
WHERE v.relkind = 'v' AND d.classid = 'pg_rewrite'::regclass
AND d.refclassid = 'pg_class'::regclass
AND d.deptype = 'n' AND (ns.oid >= 16384 OR ns.nspname = 'public')
AND NOT (v.oid = d.refobjid)
GROUP BY 1,2,3 ORDER BY 1,2,3;
查看视图的定义
通过该SQL查询出依赖t1表的视图信息包括依赖的字段和创建视图的SQL
=# SELECT v.relname AS view,d.refobjid::regclass as ref_object,
string_agg(a.attnum||':'||a.attname,',' order by a.attnum) ref_cols,
'CREATE VIEW ' || v.relname || ' AS ' || pg_get_viewdef(v.oid) AS view_def
FROM pg_depend AS d
版权所有Esena(陈淼 ) 编写陈淼 - 155 -
Greenplum Database 管理员指南 V6.2.1
JOIN pg_rewrite AS r ON r.oid = d.objid
JOIN pg_class AS v ON v.oid = r.ev_class
JOIN pg_class AS t ON t.oid = d.refobjid
JOIN pg_attribute AS a
ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid
WHERE NOT (v.oid = d.refobjid) AND d.refobjid = 't1'::regclass
GROUP BY 1,2,4
ORDER BY 1,2;
查看视图的多层依赖关系
这是一个CTE语句(这不是RECURSIVE CTE所以几乎所有版本都支持)WITH
中查询出所有的视图主体查询中查出哪些视图依赖其他视图
=# WITH views AS (
SELECT v.relname AS view,d.refobjid AS ref_object,
v.oid AS view_oid,ns.nspname AS namespace
FROM pg_depend AS d
JOIN pg_rewrite AS r ON r.oid = d.objid
JOIN pg_class AS v ON v.oid = r.ev_class
JOIN pg_namespace AS ns ON ns.oid = v.relnamespace
WHERE v.relkind = 'v' AND (ns.oid >= 16384 OR ns.nspname = 'public')
AND d.deptype = 'n' AND NOT (v.oid = d.refobjid)
)
SELECT views.view, views.namespace AS schema,
views.ref_object::regclass AS ref_view,
ref_nspace.nspname AS ref_schema
FROM views
JOIN pg_depend as dep ON dep.refobjid = views.view_oid
JOIN pg_class AS class ON views.ref_object = class.oid
JOIN pg_namespace AS ref_nspace ON class.relnamespace = ref_nspace.oid
WHERE class.relkind = 'v'
AND dep.deptype = 'n';
版权所有Esena(陈淼 ) 编写陈淼 - 156 -
Greenplum Database 管理员指南 V6.2.1
实际上编者在编写并行DDL备份恢复脚本时已经将这些复杂的视图依赖关系都
拆解了通过编者的脚本备份出的DDL会自动把视图按照多层依赖关系进行分拆
确保恢复DDL的时候视图按照依赖关系进行并行恢复同一层级相互没有依赖关系的
视图可以一起并行恢复。编者在实现集群之间DDL增量比对脚本时也实现了依赖拆解和
并行恢复对实现灾备集群提供了有力的支持。
视图是如何被存储的
视图与表相似都是relation名称存储在pg_class系统表中也都有字段属
字段属性与表一样存储在pg_attribute系统表中。下面是一些区别
视图没有数据文件 -- 因为视图不存储数据。
pg_class系统表中的relkind属性是v而数据表是r
每个视图有一个ON SELECT事件名称为_RETURNrewrite规则。
视图的rewrite规则存储在pg_rewrite系统表中视图的定义存储在该系统表
ev_action字段中。关于视图的更多详细信息可以参考PostgreSQL的相关文档。
视图的定义不是以字符串的形式存储的存储的是解析后的查询树在视图被创建
时生成的查询解析树这样会有几方面的影响
对象名称是在视图创建时解析的所以创建时的search_path会影响到视图的定
如果使用时的search_path与创建时不一致可能会导致找不到表的报错。
视图对其他对象的引用是通过OID来实现的因此修改依赖的表或者字段的名称
并不会影响视图的依赖关系。也就是说如果视图依赖是表名是oldold改为
new之后依赖的表就是new
GP可以精确的获取视图使用了哪些对象所以能够存储严谨的依赖关系。
注意GP处理视图的方式和处理函数的方式完全不同对于函数GP存储的是字符串
创建时不会解析为查询树因为函数中的具体执行情况无法预知只有具体的参数和具
体的数据在执行时才能确定涉及的对象所以没有办法精准获取函数的依赖关系。编
者在社区遇到很多次关于函数涉及的表如何查询的问题这个的确是无能为力的即便
版权所有Esena(陈淼 ) 编写陈淼 - 157 -
Greenplum Database 管理员指南 V6.2.1
通过pg_proc系统表的prosrc(存储function的全部源码)字段来查找也只能匹配
明文写出的对象名称。
视图的依赖信息存储在哪里
这些表中存储着视图依赖哪些对象
pg_class -- 存储所有relation的信息包括表、视图、索引、外部表和序列
等。通过relkind来区分不同类型的对象。
pg_depend -- 存储着数据库中非共享对象的依赖关系。
pg_rewrite -- 存储着表和视图的rewrite规则。
pg_attribute -- 存储着字段信息。
pg_namespace -- 存储着模式信息。
需要注意的是视图对其依赖的对象没有直接的依赖关系而是通过rewrite
则来实现依赖关系。其实这话的意义不大因为依赖关系仍然是明确的且强制。
创建和管理物化视图
物化视图与普通视图类似都是将一个常用的查询保存为一个relation之后就
可以如同访问一张表一样执行SELECT操作。而不同的是物化视图会直接将查询结果
持久化为数据文件类似数据表的存储形式所以访问物化视图时是直接访问持久化
的数据往往比通过视图访问数据表更快但数据不是实时的。
物化视图的数据无法直接修改只能通过REFRESH MATERIALIZED VIEW 命令
来刷新数据用于存储物化视图的查询语句与普通视图的查询语句的存储方式相同。例
如可以这样创建一个物化视图
=# CREATE MATERIALIZED VIEW sales_summary AS
SELECT seller_no, invoice_date,
sum(invoice_amt)::numeric(13,2) as sales_amt
FROM invoice
WHERE invoice_date < CURRENT_DATE
GROUP BY seller_no, invoice_date
ORDER BY seller_no, invoice_date;
版权所有Esena(陈淼 ) 编写陈淼 - 158 -
Greenplum Database 管理员指南 V6.2.1
=# CREATE UNIQUE INDEX sales_summary_seller
ON sales_summary (seller_no, invoice_date);
可以使用如下命令定期刷新视图的数据
=# REFRESH MATERIALIZED VIEW sales_summary;
GP中的物化视图的属性信息与表或普通视图是一样的都是relation表和普通
视图也是relation。当查询一个物化视图时数据直接从物化视图的数据文件获取
就如同访问普通的数据表一样而物化视图中的查询语句仅用于产生数据以填充物化
视图。
如果业务上可以接受定期更新物化视图的数据将会为查询带来极大的性能提升。
物化视图还可以建立在外部表之上以提升外部数据的访问性能物化视图上还可
以创建索引外部表上是不能创建索引的。不过这种场景可能只适合从固定的外部表
查询固定的数据或者外部表的数据有周期性变化编者认为通过物化视图来加速外
部表的访问并不是物化视图特有的功能在外部表上创建物化视图同样需要读取外部表
的全部数据这与把数据加载到一张普通的数据表没有任何差异。而物化视图的刷
新与普通数据表的TRUNCATE并重新INSERT效果相同。
如果一种SQL查询只是在个别语句中用到可以使用SELECT命令的WITH子句来实
可能不需要为此而创建一张很少用到的视图。编者再次提醒不要乱用视图更不
要随意创建多层视图虽然编者的脚本可以处理这些难题但日常维护会非常困难。
创建物化视图
使用CREATE MATERIALIZED VIEW命令基于一个查询语句来创建物化视图
=# CREATE MATERIALIZED VIEW us_users AS
SELECT u.id, u.name, a.zone
FROM users u, address a WHERE a.country = 'USA';
如果查询语句中包含ORDER BYSORT子句只会影响物化视图的数据生成
不会影响物化视图的查询也就是说生成的物化视图的数据会是有序的但针对物化
视图的查询不保证顺序不过这不等于说排序是完全无意义的有序的数据可以有助于
物化视图创建聚集索引。
版权所有Esena(陈淼 ) 编写陈淼 - 159 -
Greenplum Database 管理员指南 V6.2.1
刷新或停用物化视图
使用REFRESH MATERIALIZED VIEW命令来刷新物化视图的数据
=# REFRESH MATERIALIZED VIEW us_users;
使用WITH NO DATA子句来刷新物化视图物化视图中的数据将被清空并且不
会产生新的数据此时物化视图将不能再被查询查询这种物化视图将会得到一个报
错信息。
=# REFRESH MATERIALIZED VIEW us_users WITH NO DATA;
=# SELECT * FROM us_users;
ERROR: materialized view "us_users" has not been populated
HINT: Use the REFRESH MATERIALIZED VIEW command.
删除物化视图
使用DROP MATERIALIZED VIEW命令来删除物化视图的定义以及数据。例如
DROP MATERIALIZED VIEW us_users;
使用命令DROP MATERIALIZED VIEW . . . CASCADE将可以级联删除所有依
赖该物化视图的对象例如另一个物化视图依赖该物化视图也会一同被删除此时如
果没有CASCADE子句DROP MATERIALIZED VIEW命令将会报错失败。物化视图的
依赖关系和普通视图是一样的。
例如修改"查看视图的依赖关系"章节的相关示例SQL可以查询物化视图的依赖
信息注意物化视图在pg_class中存储的relkind属性为m。例如查询依赖t1
的物化视图
=# SELECT v.oid::regclass AS view,d.refobjid::regclass AS ref_object
FROM pg_depend AS d JOIN pg_rewrite AS r ON r.oid = d.objid
JOIN pg_class AS v ON v.oid = r.ev_class
WHERE v.relkind = 'm' AND d.classid = 'pg_rewrite'::regclass
AND d.deptype = 'n' AND d.refclassid = 'pg_class'::regclass
AND d.refobjid = 't1'::regclass
GROUP BY 1,2 ORDER BY 1,2;
版权所有Esena(陈淼 ) 编写陈淼 - 160 -
Greenplum Database 管理员指南 V6.2.1
版权所有Esena(陈淼 ) 编写陈淼 - 161 -
Greenplum Database 管理员指南 V6.2.1
第八章数据的分布与倾斜
GP 要求数据在 Instance 上均匀分布 MPP Share-Nothing 数据库中
于一个查询来说所有操作都完成才算完成那么这个总的耗时就是最慢 Instance
的耗时。如果有数据的倾斜处理数据更多的 Instance完成计算所需要的时间就
越久所以如果所有的 Instance 处理的数据量相当那么总体的执行时间就会保
持一致如果个别 Instance 要处理更多的数据将可能导致严重的资源消耗且拖慢
整体的处理时间。
在进行大表关联时合理的数据分布很重要当进行关联时匹配的记录必须在
Instance 本地如果不能满足这个条件执行计划中将会加入数据移动的算子
要将一个表或多个表做数据重分布这样将消耗很多的网络资源当然在有些时候
如果其中一个表很小还可能会选择将小表进行广播。重分布操作是每个 Instance
按照关联字段重新计算 HASH 值得到记录应该发送到哪个 Instance 然后发送过去。
本地关联
使用 HASH 分布的情况下表的记录均匀的分散到所有 Instance 所以
进行关联查询时表之间匹配的数据都在本地计算将直接在本地完成这种情况称为
本地关联。本地关联将可以最大程度的避免数据移动所有的 Instance 都独立处理
本地的数据而不需要在 Instance 之间通过内联网络交换数据。
要实现大表之间的本地关联需要确保关联字段包含全部的分布键这部分在"
GP 分布策略"章节已经做了很多详细介绍当关联的数据都在 Instance 本地
可以显著提升处理的性能。另外 CREATE TABLE 时应该确保关联的字段在不同的
表中采用相同的字段类型因为不同的数据类型对应不同的底层数据结构相同的记
录因为底层存储的差异会分散到不同的 Instance 这种情况在进行关联查询时
仍然会涉及数据的重分布。正如"解读 GP 分布策略"章节所述尽可能只选择一个字段
作为分布键(这是非常重要的)
数据倾斜
数据倾斜一般是由于选择了错误的分布键而造成的结果或者是因为在 CREATE
TABLE 时没有指定分布键而自动以第一个字段作为分布键。通常可能会表现出查询性
能差甚至出现内存不足的报错。数据倾斜会直接影响表扫描的性能同时也会影响相
关的关联查询和分组汇总等计算的性能。
版权所有Esena(陈淼 ) 编写陈淼 - 162 -
Greenplum Database 管理员指南 V6.2.1
检验数据分布是否均匀非常重要无论是初次加载数据之后还是增量数据加载之
后。有时数据量不大时可能不会明显的表现出倾斜所以需要定期检查倾斜情况。
虽然在官方文档中介绍了查询表中记录数分布情况的方法但编者不想介绍这种方
编者认为这种方法是陈旧而落后的因为其需要使用 count(*)的方式来计算
表中的记录数因为对于很大的表来说这种操作无疑是难以忍受的。编者推荐通过
计算一张表在不同 Instance 上所占的空间尺寸来评估是否发生倾斜。例如
=# SELECT gp_segment_id,pg_relation_size('t1')
FROM gp_dist_random('gp_id') ORDER BY 2 DESC;
gp_toolkit 中有可用于查看表倾斜情况的视图虽然编者从来不用这些视图
但还是有必要介绍一下后续编者将介绍更高效的实现方案。
gp_toolkit.gp_skew_coefficients视图一个非常复杂的视图经过编者
了解该视图最终会针对每张表分为AO表和非AO表来分别计算记录数情况AO
会通过get_ao_distribution函数来计算记录数Heap表会通过count(*)
计算记录数真是一个神奇的设计。该视图以Instance记录数的标准差除以平均
值再乘以100来表示倾斜的严重程度值越大倾斜越严重。
gp_toolkit.gp_skew_idle_fractions视图一个非常复杂的视图经过编
者了解该视图会计算Instance中记录数的最大值与平均值的差值然后除以
最大值得到一个不大于1的浮点数值越大倾斜越严重。不过在获取表的信息
时与gp_toolkit.gp_skew_coefficients视图是一模一样的所以没有性
能优势。
编者来说说自己的实现不去轮询查询每张表的信息因为这些系统视图性能极差
的根本原因是都要循环获取每张表的信息尤其是表的数量很大的时候每个表的信
息获取都会变慢Heap 表的 count(*)操作更是致命的。我们的目的是检查倾斜情况
而反应倾斜情况的未必一定要通过记录数来体现如前面所述可以通过尺寸来体现。
编者使用如下函数来获取文件信息
CREATE OR REPLACE FUNCTION gp_toolkit.gp_table_file_info() RETURNS SETOF
VARCHAR[] AS $$
import os
_rslt = plpy.execute("""select current_database() dbname,
inet_server_port() port;""")
(_dbname, _port) = (_rslt[0]["dbname"], str(_rslt[0]["port"]))
_rslt = plpy.execute("""select oid,dattablespace from pg_database where
datname = '%s';""" % (_dbname))
(_dboid, _dbspc) = (str(_rslt[0]["oid"]), str(_rslt[0]["dattablespace"]))
def getSqlValue(_sql):
_utility = "PGOPTIONS='-c gp_session_role=utility' psql -v
版权所有Esena(陈淼 ) 编写陈淼 - 163 -
Greenplum Database 管理员指南 V6.2.1
ON_ERROR_STOP=1"
_cmd = """%s -d '%s' -p %s -tAXF '|' 2>&1 <<_END_OF_SQL\n""" % (_utility,
_dbname, _port) + _sql + "\n_END_OF_SQL"
try:
val = os.popen(_cmd).read()
return val.strip()
except Exception, e:
plpy.error(str(e))
_dftpath = getSqlValue("""show data_directory""") + "/base/" + _dboid + "/"
_version = int(getSqlValue("""SELECT
(string_to_array((string_to_array(version(),'Greenplum Database
'))[2],'.'))[1];"""))
_rslt
if _version < 6:
_rslt = plpy.execute("""select
t.oid,trim(n.location_1)||'/'||t.oid||'/'||'%s'||'/' path
from pg_tablespace t,pg_filespace f,gp_persistent_filespace_node n
where t.spcfsoid = f.oid and f.oid = n.filespace_oid;""" % (_dboid))
else:
_dirc = getSqlValue("""show data_directory""")
_rslt = plpy.execute("""select
t.oid,'%s'||'/pg_tblspc'||t.oid||'/'||'GPDB_*'||'/%s/' path from
pg_tablespace t;""" % (_dirc,_dboid))
_spcarray = []
_spcarray.append([str(1663), _dftpath])
for _row in _rslt:
(_spcoid, _spcpath) = (str(_row["oid"]), _row["path"])
if not(os.path.exists(_spcpath)):
continue
if os.path.isfile(_spcpath):
continue
_spcarray.append([_spcoid, _spcpath])
_sizemap = {}
for _spcinfo in _spcarray:
(_spcoid, _spcpath) = (_spcinfo[0], _spcinfo[1])
_lscmd = """ls -lL --full-time %s|awk '{print $9"\t"$5"\t"$6" "$7}'|grep
'^[0-9]'|sort -n""" % (_spcpath)
_rslt = os.popen(_lscmd).read().strip()
if _rslt == "":
continue
for _row in _rslt.split("\n"):
(_relfile, _size, _time) = _row.split("\t")
_relfile = _relfile.split(".")[0]
_key = _spcoid + "-" + _relfile
版权所有Esena(陈淼 ) 编写陈淼 - 164 -
Greenplum Database 管理员指南 V6.2.1
if _sizemap.has_key(_key):
_sizemap[_key] = [_sizemap[_key][0] + int(_size),
_sizemap[_key][1] + 1, _sizemap[_key][2] + "\n" + _time]
else:
_sizemap[_key] = [int(_size),1,_time]
_rslt = plpy.execute("""select
n.nspname,c.relname,c.reltablespace,c.relfilenode,c.relstorage
from pg_class c, pg_namespace n where c.relnamespace = n.oid
and c.relkind = 'r' and c.relstorage <> 'x' and c.reltablespace <> 1664
and not c.relhassubclass
and n.nspname not like E'pg\_temp\_%' and n.nspname not like
E'pg\_toast\_temp\_%';""")
for _row in _rslt:
(_nspname, _relname, _relspc) = (_row["nspname"], str(_row["relname"]),
str(_row["reltablespace"]))
(_relfile, _storage) = (str(_row["relfilenode"]), _row["relstorage"])
if _relspc == "0":
_relspc = _dbspc
_key = _relspc + "-" + _relfile
if _sizemap.has_key(_key):
if _storage == "h":
yield (_nspname, _relname, _sizemap[_key][0], _sizemap[_key][1],
_relspc, _sizemap[_key][2])
else:
yield (_nspname, _relname, _sizemap[_key][0], _sizemap[_key][1],
_relspc, None)
else:
yield (_nspname, _relname, 0, 0, None)
$$ LANGUAGE PLPYTHONU;
该函数较长使用时请注意缩进函数是用 Python 写的所以缩进不能出错。
在函数中直接通过操作系统的 ls 命令查看数据库目录下的所有文件的信息然后与
系统表进行关联。通过下面的 SQL 使用刚刚创建的函数来获取表的文件信息
=# select nspname, relname, tablesize, filecount, expectfilecount,
round(filecount / expectfilecount,1) filecountratio, minsize, maxsize,
fileflag from (
select size[1] as nspname, size[2] as relname,
sum(size[3]::bigint) tablesize,string_agg(size[3],','),
sum(size[4]::bigint) filecount,
min(size[3]::bigint) minsize,
max(size[3]::bigint) maxsize,
md5(string_agg(size[6],E'\n' order by segment_id)) fileflag from (
select gp_toolkit.gp_table_file_info() size, gp_segment_id
版权所有Esena(陈淼 ) 编写陈淼 - 165 -
Greenplum Database 管理员指南 V6.2.1
segment_id from gp_dist_random('gp_id')
) x group by 1,2
) x left join (
select nspname, relname, decode(relstorage, 'c', attcount, 1) * y.segs
expectfilecount from (
select nspname, relname, relstorage, count(*) attcount
from pg_namespace n, pg_class c, pg_attribute a
where n.oid = c.relnamespace and c.oid = a.attrelid
and c.relkind = 'r' and c.relstorage <> 'x' and c.reltablespace <>
1664 and not c.relhassubclass
and n.nspname not like E'pg\_temp\_%' and n.nspname not like
E'pg\_toast\_temp\_%'
group by 1,2,3
) x, (
select count(*) as segs from gp_segment_configuration where role =
'p' and content <> -1
) y
) y using(nspname, relname) order by 6 desc;
这段查询输出的每条记录包含 9 个属性分别是模式名称、表名称、该表的总
尺寸、该表总的文件数、预期文件数、文件数膨胀倍数、最小的尺寸(单个 Instance)
最大尺寸(单个 Instance)Heap 表的文件时间戳 MD5 值。
该方法经历过多次大规模集群的检验有数万张表目录下有几十万个文件的情况
也能在几分钟内完成全库的信息收集可以将查询结果导出到文件中以便于进一步
的详细分析。
复制表的注意事项
CREATE TABLE 时指定分布策略为 DISTRIBUTED REPLICATED 就可以创建
复制表。复制表会在每个 Instance 上存储一份完整的数据所以复制表是绝对不
会倾斜的因为每个实例的记录都完全相同。更多关于复制表的注意事项可以参考"
制表的主要应用场景"章节。
计算倾斜
当数据倾斜到个别 Instance 它往往是 GP 数据库性能和稳定性的罪魁祸首
(编者想说的是有无数的问题的根源都在倾斜上当然不仅仅说的是数据倾斜计算
倾斜是更隐蔽的问题往往可能造成更严重的影响而且难以被发现和解决)不过
于数据分布的倾斜发现和处理往往不难。然而当倾斜发生在关联、排序、聚合等各
种算子的计算过程中时事情就变的十分复杂这种情况我们称之为计算倾斜。而要处
理计算倾斜可以说十分困难一般的技术人员很难发现更不用说解决这种问题
版权所有Esena(陈淼 ) 编写陈淼 - 166 -
Greenplum Database 管理员指南 V6.2.1
这也是用好 GP 非常关键的一门武功此门绝技练到第十层就是大师级的人物了。
如果单个 Instance 出现了故障这有可能与计算倾斜有关(X86 经不住长期超负
荷的资源压榨不堪重负必有内伤)目前处理计算倾斜还是一个手动的过程(编者
也想不出如何实现自动因为计算倾斜是个过程)。处理计算倾斜时首先可以看一下
溢出文件的情况如果有计算倾斜但又没有出现溢出文件可能这种倾斜并不会造成严
重的后果。如果发现有计算倾斜现象出现下面是一些步骤和方法可以参考。
gp_toolkit.gp_workfile_usage_per_segment -- 通过该视图可以查询
每个Instance目前使用的Workfile溢出文件的尺寸和文件数量。通过该视图
可以清晰的发现哪些Instance有严重的溢出文件问题。
gp_toolkit.gp_workfile_usage_per_query -- 通过该视图可以查询每
Query在每个Instance上的Workfile的使用情况显示的信息包括数据库
名称进程号会话IDcommand count用户名查询语句SegID溢出
文件尺寸溢出文件数量。
通常通过这两个视图就可以确定正在发生倾斜的查询而要解决这些问题往往
需要重新优化SQL例如确认统计信息是否严重失真如果是应该尝试更新统计信息
找到执行计划中不合理的算子通过修改可能的参数来干预执行计划使用WITH子句
来分拆SQL以达到隔离执行计划的目的使用临时表以强制拆分执行步骤强制执行计
划选择两阶段AGG或者三阶段AGG总之优化的最高目标就是让数据库生成的执
行计划符合预期最佳的预期需要基于对MPP的分布式理解和对数据的理解如果只是
从其他数据库的使用中学到了一些支离破碎的知识可能预期还不如数据库自动生成的
执行计划当能够确切的知道什么样的执行计划才是最优的那么距离优化出这样的执
行计划已经很近了。编者认为对于GP的学习首先要能够完全看懂执行计划其次
是知道哪些步骤是有问题的然后才能优化。优化的最基本前提是看懂执行计划否则
一切都是空谈。让数据库按照期望的最佳路径生成执行计划是最高功力不过往往
绝大部分技术人员只是了解一些SQL语法并不清楚数据库是如何一步一步计算得到
结果的也就不可能知道何为最佳路径所以缺乏这些基本的内功是不可能练出十
层绝学的。
版权所有Esena(陈淼 ) 编写陈淼 - 167 -
Greenplum Database 管理员指南 V6.2.1
第九章数据增删改
本章讲述关于数据管理和GP中的并发访问。包含如下内容
关于GP的并发控制
插入新记录
更新记录
删除记录
使用事务
全局死锁检测
回收空间
关于 GP 的并发控制
与事务型数据库系统通过锁机制来控制并发访问的机制不同GP(PostgreSQL
一样)使用多版本控制(Multiversion Concurrency Control/MVCC)保证数据一
致性。这意味着在查询数据库时每个事务看到的只是数据的快照其确保当前的事务
不会看到其他事务在相同记录上的修改。据此为数据库的每个事务提供事务隔离保护。
MVCC以避免给数据库事务显式上锁的方式最大化减少锁争用以确保多用户环境
下的性能。在并发控制方面使用MVCC而不是使用锁机制的最大优势是MVCC机制下
查询()的锁与写的锁不存在冲突并且读与写操作之间从不会互相阻塞。
GP提供了各种锁机制来控制对表数据的并发访问。大多数GPSQL命令可以自动
获取适当模式的锁以确保在命令执行时相应的表不会被删除或者修改。对于不能适应
MVCC锁的应用来说可以使用LOCK命令来显式的获取必要的锁。然而恰当的使用
MVCC比使用LOCK有更好的性能表现。
GP中的锁模式
锁模式
相关 SQL 命令
冲突
ACCESS SHARE
SELECT
ACCESS EXCLUSIVE
ROW SHARE
SELECT . . . FOR UPDATE
EXCLUSIVEACCESS EXCLUSIVE
SELECT FOR SHARE
ROW EXCLUSIVE
INSERTCOPY
SHARESHARE ROW EXCLUSIVE
EXCLUSIVEACCESS EXCLUSIVE
SHARE UPDATE
VACUUM (no FULL)
SHARE UPDATE EXCLUSIVESHARE
版权所有Esena(陈淼 ) 编写陈淼 - 168 -
Greenplum Database 管理员指南 V6.2.1
EXCLUSIVE
ANALYZE
SHARE ROW EXCLUSIVEEXCLUSIVE
ACCESS EXCLUSIVE
SHARE
CREATE INDEX
ROW EXCLUSIVESHARE UPDATE
EXCLUSIVESHARE ROW EXCLUSIVE
EXCLUSIVEACCESS EXCLUSIVE
SHARE ROW
ROW EXCLUSIVESHARE UPDATE
EXCLUSIVE
EXCLUSIVESHARESHARE ROW
EXCLUSIVEEXCLUSIVEACCESS
EXCLUSIVE
EXCLUSIVE
DELETE, UPDATE
ROW SHAREROW EXCLUSIVESHARE
SELECT . . . FOR UPDATE,
UPDATE EXCLUSIVESHARESHARE ROW
REFRESH MATERIALIZED
EXCLUSIVEEXCLUSIVEACCESS
VIEW CONCURRENTLY
EXCLUSIVE
ACCESS
ALTER TABLDROP TABLE
ACCESS SHAREROW SHAREROW
EXCLUSIVE
TRUNCATEREINDEX
EXCLUSIVESHARE UPDATE EXCLUSIVE
CLUSTER,
SHARESHARE ROW EXCLUSIVE
REFRESH MATERIALIZED
EXCLUSIVEACCESS EXCLUSIVE
VIEW (without
CONCURRENTLY), VACUUM
FULL
注意对于Heap表的UPDATEDELETESELECT . . . FOR UPDATE操作GP
据库缺省使用EXCLUSIVE锁。当开启全局死锁检测时对于Heap表的UPDATE
DELETE操作将可以使用ROW EXCLUSIVE锁。对于SELECT . . . FOR UPDATE
作仍然需要使用表级别的锁。
插入新记录
在表刚被创建时是没有数据的。在数据库进行更多使用之前的第一步是插入数据。
要插入新的记录使用INSERT命令。该命令需要表名和该表每个字段的值。数据的值
按照字段在表中出现的顺序排列使用逗号分割。例如
=# INSERT INTO products VALUES (1, 'Cheese', 9.99);
如果不知道字段在表中的顺序还可以将字段显式的列出来。很多用户认为总是列
出字段名称是一种比较好的习惯。例如
=# INSERT INTO products (name, price, product_no)
VALUES ('Cheese', 9.99, 1);
将一张表中符合条件的记录插入另一张表中
=# INSERT INTO films SELECT * FROM tmp_films WHERE date_prod < '2004-05-07';
使用一个命令插入多条记录。例如
=# INSERT INTO products (product_no, name, price) VALUES
(1, 'Cheese', 9.99),
(2, 'Bread', 1.99),
版权所有Esena(陈淼 ) 编写陈淼 - 169 -
Greenplum Database 管理员指南 V6.2.1
(3, 'Milk', 2.99);
在同时插入大量数据时应该考虑使用外部表(CREATE EXTERNAL TABLE)或者
COPY命令。在装载大量记录时这些装载机制比使用INSERT VALUES更高效(是高效
好几个数量级)。更多关于批量装载数据的信息参见"装载和卸载数据"相关章节。
AO表已经为批量装载做了优化。不建议在AO表上使用单条的INSERT VALUES
句。GP数据库最多支持单个AO表上127个并发INSERT数据不过编者建议尽量避免
AO表上的并发INSERT因为底层的数据文件会分裂最多达到127
更新记录
UPDATE意味着对数据库中现有的数据进行修改。可以修改表中一条记录、一部分
记录或者全部记录。每个字段都可以被单独更新而不影响其他字段。
要执行更新需要如下3方面的信息
1. 要被更新的表和字段的名称。
2. 字段的新值。
3. 用于过滤需要被更新字段的记录的条件。
例如下面的命令更新products表中所有price5的记录的price10
=# UPDATE products SET price = 10 WHERE price = 5;
GP中使用UPDATE命令有如下的限制
Orca支持更新分布键字段6版本之前的PostgreSQL优化器不支持(编者实测如
而如果断然的说不支持跟后续的[全局死锁检测]中所述冲突)
在启用了Mirror的情况下UPDATE语句中不能有STABLE或者VOLATILE类型的
函数。
PostgreSQL优化器不能UPDATE分区字段Orca可以UPDATE分区字段。
删除记录
使用DELETE命令从指定的表中删除符合WHERE条件的记录。如果没有使用WHERE
版权所有Esena(陈淼 ) 编写陈淼 - 170 -
Greenplum Database 管理员指南 V6.2.1
子句将会删除该表的所有记录。例如products表中删除所有price10的记录
=# DELETE FROM products WHERE price = 10;
或者删除表中所有记录
=# DELETE FROM products;
GP中使用DELETE操作的限制
在启用了Mirror的情况下DELETE语句中不能有STABLE或者VOLATILE类型的
函数。
清空表
若想要快速删除表中的所有记录应该考虑使用TRUNCATE命令。例如
=# TRUNCATE mytable;
该命令一次清空表中的全部记录。值得注意的是TRUNCATE不扫描表直接将数
据文件清空继承此表的其他表不会被清空只是被TRUNCATE的表会受到影响。分区
表会被视作一个整体在父表上执行TRUNCATE操作会清空所有相关叶子分区的数据。
使用事务
事务允许将多个SQL语句放在一起当作一个整体来执行所有SQL一起成功或失败。
GP中执行事务的SQL命令为
使用BEGINSTART TRANSACTION开始一个事务。
使用ENDCOMMIT提交事务。
使用ROLLBACK回滚事务(放弃所有修改)(序列的增长不会被回滚)
使用SAVEPOINT选择性的保存事务点。
使用ROLLBACK TO SAVEPOINT回滚到之前保存的事务点。
使用RELEASE SAVEPOINT来释放之前保存的事务点。
版权所有Esena(陈淼 ) 编写陈淼 - 171 -
Greenplum Database 管理员指南 V6.2.1
更多信息参考相关SQL说明编者认为在GP中应用场景不多。
事务隔离级别
GP数据库兼容标准的SQL事务级别的情况如下
READ UNCOMMITTEDREAD COMMITTED的表现类似标准的READ COMMITTED
REPEATABLE READSERIALIZABLE的表现类似REPEATABLE READ
下面描述GP的不同事务隔离级别的特征
读未提交和读已提交
GP数据库不允许任何命令看得到另一个并行事务未提交的更新(其实对于heap
设置gp_select_invisible参数为on之后是可以看到其他事务未提交的数据的
也看得到未被VACUUM回收的UPDATEDELETE的数据INSERT回滚数据也看的到
一旦VACUUM就看不到了不过这个参数要特别慎用)。所以READ UNCOMMITTED
的效果与READ COMMITTED一致READ COMMITTED提供了简单高效的事务隔离机制。
相当于SELECTUPDATEDELETE命令在开始执行时(注意是命令开始时不是事
务开始时)在数据库上做了个快照。
READ COMMITTED事务的SELECT命令将会
看得到查询语句开始之前所有已提交的数据(不是事务开始前)
看得到当前事务已经修改的数据。
看不到其他事务未提交的数据。
看得到当前事务启动之后其他并发事务已经提交的修改。
如果其他事务在当前事务的不同查询之间提交修改当前事务中的不同SELECT
看到不同的数据。UPDATEDELETE命令只能看得到命令启动时已经提交的数据
不是事务启动的时间在事务期间有其他事务提交的修改UPDATEDELETE
依然可见这就是[读已提交]只要命令开始时是已经提交的数据就可见。而不用
关心当前的事务是何时开始的。
READ COMMITTED事务隔离级别允许一个事务中的UPDATEDELETE操作开始
之前其他并发的事务同样可以查询和修改记录。也就是说READ COMMITTED不能
保证在事务期间所涉及的数据不会发生变化所以对于一些要求事务期间必须保证
版权所有Esena(陈淼 ) 编写陈淼 - 172 -
Greenplum Database 管理员指南 V6.2.1
数据库完全一致的应用来说READ COMMITTED事务隔离级别是不够的。
可重复读取和可串行化
根据SQL标准定义的SERIALIZABLE事务隔离级别其可以保证并发事务的运行结
果与一个接一个的运行结果完全相同。如果指定了SERIALIZABLE事务隔离级别GP
数据会将其降级到REPEATABLE READ事务隔离级别。REPEATABLE READ事务隔离级
别不需要重量级的锁就可以避免脏读、不可重复读和幻读GP数据库不会检测并发
事务期间所有可能的可串行化的相互影响(就是不能保证得到可串行化的结果)。所以
可以通过检查并行事务之间可能的影响来排查这种可串行化的相互影响可以通过显式
LOCK操作来避免并发事务之间的影响(在事务开始的时候就显式的把相关的表加上
必要的锁)。这段很难懂英文解释也很难懂总之GP数据库本身无法保证可串行化
的效果就是说并发事务不能保证和串行执行一样的结果。
REPEATABLE READ事务的SELECT命令将会
在事务开始时(而不是在查询开始时)确定数据的快照。
只能看得到事务开始之前已经提交的数据。
看得到当前事务已经修改的数据。
看不到其他事务未提交的数据。
看不到当前事务启动之后其他事务提交的修改。
在当前事务中多次执行SELECT命令总是得到相同的数据。
UPDATEDELETESELECT FOR UPDATESELECT FOR SHARE命令只能看得
到事务开始之前的记录如果其他并发的事务已经修改、删除或LOCK了同一条记
REPEATABLE READ事务(修改同一条记录的操作)需要等待该事务提交或者
回滚这些修改如果其他并发的事务提交了修改当前的REPEATABLE READ事务
将会失败回滚如果其他并发事务回滚了修改当前的REPEATABLE READ事务将
可以提交当前的修改。
GP数据库缺省的事务隔离级别是READ COMMITTED要修改事务隔离级别可以
在开始事务时显式的指定事务隔离级别或者在事务开始之后通过SET TRANSACTION
来设置。例如
=# BEGIN ISOLATION LEVEL REPEATABLE READ;
=# END;
=# BEGIN;
=# SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
=# END;
版权所有Esena(陈淼 ) 编写陈淼 - 173 -
Greenplum Database 管理员指南 V6.2.1
关于事务隔离级别这一段真的很难懂编者觉得用中文讲这样算是比较易懂了
吧。简单总结来说读已提交就是只要其他事务COMMIT就你看得到多次查询
的结果可能是会变的(结果不可重复)。可重复读就是在一个事务的生命周期内
次查询看到的数据是不变的(结果可重复)
全局死锁检测
GP 数据库的全局死锁检测通过后台进程收集所有 Instance 的锁信息通过检
测算法来检测死锁情况。这样就可以放宽 Heap 表的并发 UPDATE DELETE 操作
的锁限制。对于 AO UPDATEDELETE SELECT . . . FOR UPDATE 仍然需
EXCLUSIVE 锁。
缺省情况下全局死锁检测并未开启GP 执行 Heap 表的 UPDATE DELETE
令需要进行串行化操作。
可以通过设置 GUC 参数 gp_enable_global_deadlock_detector 来开启全
局死锁检测这样就可以对 Heap 执行并发单条 UPDATE DELETE 操作了。
在开启了全局死锁检测后相关的后台进程会跟随数据库启动一起启动通过设置
gp_global_deadlock_detector_period 参数来指定收集和分析分布式死锁信
息的时间间隔。
如果全局死锁检测发现了死锁会中断相关事务中最新的一些事务来解除死锁。
如果全局死锁检测发现了下列的死锁类型将只会有一个事务成功其他事务会失
并且会收到报错信息。
concurrent updates to the same row is not allowed
同一条记录上的并发事务第一个事务是UPDATE后面的的事务是UPDATE或者
DELETE并且其执行计划包含一个Motion算子。
PostgreSQL优化器执行的并发UPDATE操作在一张Heap表的同一个分布键上。
Orca优化器生成的在一张Hash分布的Heap表上同一条记录的并发UPDATE
注意GP 数据库使用 deadlock_timeout 参数来判断本地死锁由于本地死锁与全
局死锁的检测算法不同可能会被本地死锁检测发现也可能被全局死锁检测发现
取决于哪个先发现。
版权所有Esena(陈淼 ) 编写陈淼 - 174 -
Greenplum Database 管理员指南 V6.2.1
注意如果 lock_timeout 开启且设置的值比 deadlock_timeout
gp_global_deadlock_detector_period 可能一个查询在被检测到死锁之前
就因为锁等待时间超时而被中断。
通过执行 pg_catalog.gp_dist_wait_status()函数可以查看所有
Instance 的锁等待信息通过输出信息可以确认哪些事务在等待锁哪些事务
在持有锁锁资源的类型(锁什么样的对象例如 relationrow )和模式(锁的等
)等待锁的 SessionID持有锁的 SessionID锁的 SegID。例如
=# SELECT * FROM pg_catalog.gp_dist_wait_status();
当全局死锁检测解除一个死锁会有如下报错信息
ERROR: canceling statement due to user request: "cancelled by global
deadlock detector"
全局死锁检测对 UPDATE DELETE 的并发容许
全局死锁检测可以管理 Heap 表上这些类型的 UPDATE DELETE 命令的并发执
(这些类型之间的容许情况见本节最后的表格)
单表的简单UPDATE。由PostgreSQL优化器执行的没有FROM子句或者子查询的
非分布键字段的UPDATE
=# UPDATE t SET c2 = c2 + 1 WHERE c1 > 10;
单表的简单DELETE。命令的FROM或者WHERE子句中没有子查询。
=# DELETE FROM t WHERE c1 > 10;
裂变更新(Split UPDATE -- 在执行计划中的算子叫Split)PostgreSQL
化器可以通过UPDATE命令更新分布键字段。
=# UPDATE t SET c = c + 1; -- c is a distribution key
版权所有Esena(陈淼 ) 编写陈淼 - 175 -
Greenplum Database 管理员指南 V6.2.1
对于Orca优化器可以通过UPDATE命令更新分布键字段和分区字段。
=# UPDATE t SET b = b + 1 WHERE c = 10; -- c is a distribution key
复杂更新。UPDATE命令包含多表关联。
=# UPDATE t1 SET c = t1.c+1 FROM t2 WHERE t1.c = t2.c;
或者包含子查询的 UPDATE
=# UPDATE t SET c = c + 1 WHERE c > ALL(SELECT * FROM t1);
复杂删除。类似于复杂UPDATE包含多表关联或者子查询。
=# DELETE FROM t USING t1 WHERE t.c > t1.c;
下表列举了全局死锁检测管理的 UPDATE DELETE 命令的互相容许情况。涉及
哪些命令之间有没有冲突。例如在同一条记录上的并发简单更新是容许的并发复杂
更新和简单更新将只有一个 UPDATE 会被执行另一个 UPDATE 会失败报错。
简单更新
简单删除
裂变更新
复杂更新
复杂删除
简单更新
YES
YES
NO
NO
NO
简单删除
YES
YES
NO
YES
YES
裂变更新
NO
NO
NO
NO
NO
复杂更新
NO
YES
NO
NO
NO
复杂删除
NO
YES
NO
NO
YES
回收空间
由于MVCC事务并发模型的原因已经删除或者更新的记录仍然占据着磁盘空间
虽然其对于新的事务来说已经不可见。如果数据库有大量的更新和删除操作其将会产
生大量的过期记录。定期的运行VACUUM命令可以回收这些过期的记录空间。例如
=# VACUUM mytable;
VACUUM命令还会收集表级别的统计信息例如记录数、占用磁盘页面数所以
在装载数据之后对全表执行VACUUM是有必要的这条规律同样适用AO表。
编者认为应该更多的了解VACUUM命令。例如对于系统表来说应该定期执行
VACUUM操作系统表会因为数据库的DDL操作逐渐膨胀如果长时间不做VACUUM
版权所有Esena(陈淼 ) 编写陈淼 - 176 -
Greenplum Database 管理员指南 V6.2.1
统表的空间会严重膨胀带来性能问题甚至导致集群异常(这绝非危言耸听编者遇
到不止一次因为系统表膨胀严重导致的性能问题或集群故障)所以编者会为客户的集
群配置每日定时任务 -- 执行系统表的VACUUM操作。对于太长时间没有做VACUUM
系统表可能会面临需要花费数小时来运行VACUUM FULL的麻烦。
对于业务Heap(如果不是的确有必要尽可能用AO)虽然目前的版本VACUUM
命令的性能已经有了极大的提升但还是建议做必要的对比测试如果有可能也许做
REORGANIZE会有更好的效率。
对于AOgp_appendonly_compaction_threshold参数决定了AO表的
VACUUM操作的实际行为对于膨胀比例没有超过阈值的情况不会真的执行VACUUM
操作不过如果执行了VACUUM FULL则会忽略该参数。所以对于AO也可以
定期执行VACUUM命令来尝试回收空间而且在AO表上执行VACUUM命令时空间将得
到回收并将空间返回给文件系统这与Heap表不同Heap表是记录垃圾空间以便重
新利用。
配置自由空间映射
注意6版本开始已经不再需要自由空间映射该章节所讲述的内容是6版本之前
的特性6版本开始因为PostgreSQL版本升级Heap表中可用tuple的信息将在
文件中进行存储6版本之前这些信息是存储在内存中的。
6版本之前对于Heap表来说过期的记录会被存在叫做自由空间映射的地方。
自由空间映射的大小必须足够容纳数据库中的所有过期记录。如果尺寸不够大超出自
由空间映射的过期记录占用的空间将无法被VACUUM命令回收。
VACUUM FULL命令将回收Heap表中所有无效记录并将空间返回给文件系统
这是一个很昂贵的操作对于一张大表该操作可能会花费无法接受的时间长度。如果
自由空间映射已经溢出最好的做法是及时的使用CREATE TABLE AS命令来重建数据
表并删除旧的表也可以使用REORGANIZE来整理表两者的效率可能是相当的后者
的好处在于不需要重建表也不会涉及依赖关系的处理。
最好将自由空间映射设置为一个合适的值。自由空间映射由下面的参数来设置
max_fsm_pages
max_fsm_relations
更多信息可参考PostgreSQL相关文档。
版权所有Esena(陈淼 ) 编写陈淼 - 177 -
Greenplum Database 管理员指南 V6.2.1
版权所有Esena(陈淼 ) 编写陈淼 - 178 -
Greenplum Database 管理员指南 V6.2.1
第十章数据查询
本章讲述在GP中如何使用SQL语言对数据库中的数据进行查询。可以使用标准的
PostgreSQL命令行工具psql来执行SQL语句或者使用其他客户端工具来执行SQL
语句。
理解 GP 的查询处理
本节讲述 GP 数据库是如何处理查询的。深入理解这些概念对于写出高效的 SQL
SQL 调优将是至关重要的。用户向 GP 数据库提交 SQL 查询的方式与其他数据库是
相似的通过客户端工具(例如 psql)连接到 Master 机器执行 SQL 语句。
理解执行计划与分发
查询被 Master 接收、处理、优化、创建一个并行的或者定向的执行计划(根据查
询语句决定)。之后 Master 将执行计划分发到相关的 Instance 去执行每个
Instance 只负责处理自己本地的那部分数据如果需要用到其他 Instance 的数据
执行计划会在每一步的处理之前进行数据移动(Motion)
大部分的算子 -- 例如表扫描(Scan)、关联(Join)、聚合(Aggregation)
排序(Sort)都是在 Instance 本地被执行每个 Instance 同时独立执行不涉及
其他 Instance 的资源当某个算子需要使用其他 Instance 的数据时执行计划中
会被加入一个称为移动(Motion)的算子以帮助其他算子准备需要的数据。
版权所有Esena(陈淼 ) 编写陈淼 - 179 -
Greenplum Database 管理员指南 V6.2.1
一些特定的语句可能只使用到一个 Instance例如单行的 INSERTUPDATE
DELETE 或者 SELECT 操作还有一些直接过滤 DK 的查询。这些语句不会被分发到全
Instance而是定向的发送到包含该 DK Instance。当一个语句直接查询一张
复制表时该语句只需要得到一个 Instance 的响应。
版权所有Esena(陈淼 ) 编写陈淼 - 180 -
Greenplum Database 管理员指南 V6.2.1
理解执行计划
执行计划是 GP 根据特定的查询语句的处理逻辑生成的一系列算子的有序集合
执行计划是一个安排一个规划 Instance 要做哪些运算这些运算的顺序等安
排出来。执行计划的每一步代表着特定的算子例如扫表、关联、聚合、排序等。执
行计划被从下向上执行。与典型的数据库的处理不同的是GP 有一个特有的算子
(Motion)。移动算子(Motion)涉及到查询处理期间在 Instance 之间移动数据。
不过并非所有的查询都需要移动数据。例如针对系统表( Master )的查询不会涉
及通过内联网络移动数据因为这些数据只需要从 Master 获取不需要访问
Instance
为了最大限度的实现并行化处理GP 会将执行计划分割为多个步骤。每个步骤是
执行计划的一部分其可以在 Instance 上被独立执行。当需要移动数据时执行计
划会被分割开两个算子分处在数据移动算子的两侧先执行一步算子然后执行
数据移动再执行下一步算子。
例如下面两个 Table 的关联查询
=# SELECT customer, amount FROM sales JOIN customer USING (cust_id)
WHERE sale_date = '2020-06-06';
下图解释了执行计划。每个 Instance 获取到执行计划的副本然后并行开始执行。
对于该执行计划来说有一个重分布算子(Redistribute motion)这是为了完成
连接(Join)而执行的数据移动。执行计划被重分布算子分割为两步(slice 1
slice 2)。该执行计划还有另外一种数据移动称为汇总移动(Gather motion)
总是 Instance 将计算结果反馈到 Master 从而可以反馈给客户端的一种操作。由于
这是一个 SELECT 操作所以会有汇总算子(slice 3)。不是所有的查询都有汇总算
例如CREATE TABLE . . . AS SELECT . . . 语句就不需要汇总算子。
版权所有Esena(陈淼 ) 编写陈淼 - 181 -
Greenplum Database 管理员指南 V6.2.1
理解并行执行
GP 会创建多个 DB 进程来处理查询。在 Master 上被称为查询分发器(Query
Dispatcher/QD)QD 负责创建、分发执行计划汇总反馈最终结果。在 Instance
处理进程被称为查询执行器(Query executor/QE)QE 负责完成自身部分的处
理工作以及与其他 QE 之间交换可能需要移动的中间结果数据。
执行计划的每个处理部分都至少涉及一个处理工作。执行进程只处理属于自己部分
的工作。在查询被执行期间每个 Instance 会并行的执行一系列的处理工作。
同一部分相关的处理工作被称为簇(Gangs这是编者的翻译想不出更好的翻译
所以一直沿用至今实际上这个概念对于日常使用来说并不重要)。在一部分处理完成
数据将从当前处理向上传递直到执行计划被完成。Instance 之间的通信涉及到
GP 的内联网络组件。
下图展示查询处理如何在 Master 2 Instance 之间被逐步执行的
版权所有Esena(陈淼 ) 编写陈淼 - 182 -
Greenplum Database 管理员指南 V6.2.1
关于 ORCA 优化器
GP 数据库中目前 Orca(optimizer 参数控制)优化器与 PostgreSQL 优化
器共存不是取代关系(因为取代不了以前可能想过要取代)。在一些复杂 SQL 场景
还有一些多分区的分区表场景Orca 有时会更有优势。目前 5 版本和 6 版本中
Orca 是缺省打开的数据库会尽可能使用 Orca 优化器对于 Orca 优化器还不能处
理的场景会自动切换到 PostgreSQL 优化器。
Orca 优化器在以下场景会表现出更好的性能
针对分区表的查询(如果是多级分区表必须是规整的)
包含子查询的查询。
包含CTE的查询。
DML操作(主要是功能的增强)
目前Orca 优化器与 PostgreSQL 优化器共存缺省使用 Orca 优化器如果
Orca 优化器不适用会自动切换到 PostgreSQL 优化器。下图显示Orca 优化器和
PostgreSQL 优化器的共存逻辑
版权所有Esena(陈淼 ) 编写陈淼 - 183 -
Greenplum Database 管理员指南 V6.2.1
注意在使用 Orca 优化器时Orca 优化器在生成执行计划时会忽略所有 PostgreSQL
的执行计划参数Orca 有自己的一套参数。对于 Orca 搞不定的 SQL自动切换到
PostgreSQL 优化器时PostgreSQL 的执行计划参数将会被使用这些 PostgreSQL
的执行计划参数一般是以 enable_开头或者 gp_enable_开头一般分别在 guc.c
guc_gp.c 文件中可以找到这些参数。
启用或禁用 Orca
5 版本和 6 版本中Orca 是缺省启用状态可以通过参数 optimizer 来设置
启用或者禁用 orca 优化器。
可以在不同的级别来设置是否开启 Orca例如系统级别数据库级别ROLE
级别会话级别语句级别按照这些级别来看级别越低优先级越高例如
Database 级别的设置会覆盖系统级别的设置依次类推所有的参数(当前优先级可
以设置的话)都遵循这种优先级的规则。
注意可以通过参数 optimizer_control 来禁止修改 optimizer 的设置这个参
数的缺省值为 on只有 SUPERUSER 可以修改。不过编者想说慎用这个参数。
在系统级别开启 Orca
1 使用 gpadmin 用户登录 Master Host 主机。
版权所有Esena(陈淼 ) 编写陈淼 - 184 -
Greenplum Database 管理员指南 V6.2.1
2 使用 gpconfig 命令来设置 optimizer 参数
$ gpconfig -c optimizer -v on --masteronly
编者认为是否需要指定--masteronly 选项并不重要。
3 重新加载 postgresql.conf 配置文件使修改生效此处不是真正停止数据库
的操作这类似 PostgreSQL pg_ctl reload 操作。
$ gpstop -u
要在系统级别禁用 Orca类似以上的操作 optimizer 参数设置为 off 即可。
在数据库级别开启 Orca
要在指定的数据库范围来设置启用 Orca可以使用 ALTER DATABASE name SET
命令来实现。例如
=# ALTER DATABASE test_db SET OPTIMIZER = ON ;
要在数据库级别禁用 Orca optimizer 参数设置为 off 即可。例如
=# ALTER DATABASE test_db SET OPTIMIZER = OFF ;
还可以恢复缺省设置该级别不做设置而是从系统级别继承。例如
=# ALTER DATABASE test_db RESET OPTIMIZER;
ROLE 级别开启 Orca
要在指定的 ROLE 来设置启用 Orca可以使用 ALTER ROLE name SET 命令来
实现。例如
=# ALTER ROLE user1 SET OPTIMIZER = ON ;
版权所有Esena(陈淼 ) 编写陈淼 - 185 -
Greenplum Database 管理员指南 V6.2.1
要在 ROLE 级别禁用 Orca optimizer 参数设置为 off 即可。例如
=# ALTER ROLE user1 SET OPTIMIZER = OFF ;
还可以恢复缺省设置该级别不做设置而是从更高的别继承。例如
=# ALTER ROLE user1 RESET OPTIMIZER;
另外 6 版本中还可以在 ROLE 级别针对特定的数据库进行参数设置这些信
息存储在 pg_db_role_setting 系统表中。例如
=# ALTER ROLE user1 IN DATABASE test_db SET OPTIMIZER TO OFF;
在会话级别或者语句级别启用 Orca
除了之前的多个级别可以设置参数外和很多其他参数一样可以在会话和语句级
别进行设置通过 SET 命令来完成可以在一组 SQL 语句的任何一个语句之前进行设
SET 设置之后将影响后续的语句。例如
=# SET OPTIMIZER TO ON;
收集 ROOT 分区的统计信息
对于分区表Orca 必须使用 ROOT 分区的统计信息来生成执行计划这些信息非
常重要这将决定了 Orca 会如何评估每个算子的代价(Cost)选择 Join 的顺序
如何执行 AGG 操作。但对于 PostgreSQL 优化器而言使用的是叶子分区的统计信息。
如果使用 Orca 来查询分区表ROOT 分区上需要有统计信息并且这些信息需要
保持更新这样 Orca 才能生成更准确的执行计划。如果 ROOT 分区的统计信息过于陈
旧或者缺失ORCA 仍然会选择动态分区评估但可能会生成很差的执行计划。这不等
于说必须定期在 ROOT 分区上收集统计信息因为这个代价往往会很大后面会介绍关
于叶子分区统计信息上透的内容。
版权所有Esena(陈淼 ) 编写陈淼 - 186 -
Greenplum Database 管理员指南 V6.2.1
执行 ANALYZE 命令
缺省情况下直接在ROOT分区上执行ANALYZE命令会自动为所有叶子分区收集
统计信息也会为ROOT分区收集统计信息同时还会收集叶子分区的频度统计信息
(HyperLogLog)个人理解是一种可增量更新的统计信息后面会提到有关叶子分
区的统计信息自动上透到ROOT分区。ANALYZE ROOTPARTITION命令将只收集ROOT
分区的统计信息。通过参数optimizer_analyze_root_partition可以控制是否
需要ROOTPARTITION关键字来收集ROOT分区的统计信息不过这个情况从编者
的测试来看不同的版本表现的情况不同optimizer_analyze_root_partition
参数和gp_statistics_pullup_from_child_partition参数关闭之后6
本中没有使用关键字ROOTPARTITIONANALYZE ROOT分区同样会更新ROOT分区
的统计信息而在5版本关闭这两个参数之后的确不会更新ROOT分区的统计信息
在明确使用了ROOTPARTITION关键字之后会更新ROOT分区的统计信息。不过编者
认为这里不需要过多的考虑使用缺省的配置即可这样叶子分区的统计信息更新
会自动上透到ROOT分区如果这时Orca生成的执行计划有问题再尝试收集ROOT
区的统计信息或关闭Orca
ANALYZE 在收集 ROOT 分区统计信息时会扫描全部叶子分区的数据如果该分区
表非常大这将是一个极其耗时的操作。ANALYZE 需要 ShareUpdateExclusive
与很多操作是冲突的例如 TRUNCATE VACUUM ANALYZE 本身也是冲突的
应该合理安排 ANALYZE 的时机例如在业务空闲时期最佳的选择是在业务处理
过程中在数据发生大规模修改之后及时进行 ANALYZE 操作。
以下是一个关于在分区表上执行 ANALYZE ANALYZE ROOTPARTITION 的最佳实践
一个分区表在完成数据初始化后执行ANALYZE <root_partition>命令。一个
新的叶子分区在完成数据初始化后或者发生大规模数据变化后执行ANALYZE
<leaf_partition>命令。缺省情况下在叶子分区收集统计信息会自动上透
ROOT分区。
在长期没有更新ROOT分区的统计信息之后如果在执行计划中发现执行计划变差
或者叶子分区有大规模的数据变化之后可能自动上透的统计信息会有失准
此时应该更新ROOT分区的统计信息。例如在上一次收集ROOT分区统计信息之后
增加了很多叶子分区导致了大规模的数据变化此时应该考虑执行ANALYZE
ANALYZE ROOTPARTITION命令来更新ROOT分区的统计信息。
对于超级大表应该把ANALYZE或者ANALYZE ROOTPARTITION的间隔设置很长
例如每次间隔一周或者更久。所有表的统计信息收集间隔都取决于该表中的数据变
化比例和频繁程度。
避免执行ANALYZE命令而不带任何参数因为这样会对数据库中的所有表收集统
计信息包括所有分区对于一个大规模的数据库这种操作的耗时是不可控的。
版权所有Esena(陈淼 ) 编写陈淼 - 187 -
Greenplum Database 管理员指南 V6.2.1
如果IO资源充足可以并行执行ANALYZE <table_name>或者ANALYZE
ROOTPARTITION <table_name>命令来加速收集统计信息的速度。
可以使用GP自带的analyzedb命令来更新表的统计信息该命令会自动判断一张
表从上次收集了统计信息之后到目前为止是否发生了变化如果没有发生变化
不用重新收集统计信息这个自动判断的粒度可以精确到每个叶子分区。
Orca 和叶子分区的统计信息
对于在分区表上使用Orca优化器来说维护ROOT分区的统计信息非常重要这将
直接决定了Orca生成的执行计划的优劣不过维护叶子分区的统计信息同样很重要。
有些查询可能无法适用Orca优化器此时就会使用PostgreSQL优化器
PostgreSQL优化器是必须使用叶子分区的统计信息来生成执行计划的。对于直接查询
叶子分区的查询Orca也需要使用叶子分区的统计信息来生成执行计划。例如当明
确知道需要查询的数据在特定的叶子分区时可以直接查询该叶子分区表这种情况下
Orca也需要使用该叶子分区的统计信息。
禁用自动收集 ROOT 分区统计信息
如果不打算使用Orca来查询分区表(设置参数optimizeroff)则可以禁用
ROOT分区的统计信息自动收集通过optimizer_analyze_root_partition参数
来控制是否需要明确的指定ROOTPARTITION关键字来收集ROOT分区的统计信息。缺
省值为ONANALYZE一个分区表的ROOT分区时不需要指定ROOTPARTITION关键字
会自动收集ROOT分区的统计信息通过设置该参数为OFF来关闭这种自动收集。如果
关闭了这个参数则需要指定ROOTPARTITION关键字才能收集ROOT分区的统计信息。
不过正如编者在前面提到的这个情况还是以实际的测试为准至少目前测试下来
5版本的表现符合这个表述6版本的表现不符合这个描述。
1 使用 gpadmin 用户登录 Master Host 主机。
2 使用 gpconfig 命令来设置 ptimizer_analyze_root_partition 参数
$ gpconfig -c ptimizer_analyze_root_partition -v off --masteronly
编者认为是否需要指定--masteronly 选项并不重要。
版权所有Esena(陈淼 ) 编写陈淼 - 188 -
Greenplum Database 管理员指南 V6.2.1
3 重新加载 postgresql.conf 配置文件使修改生效此处不是真正停止数据库
的操作这类似 PostgreSQL pg_ctl reload 操作。
$ gpstop -u
使用 Orca 的注意事项
要用好Orca需要注意以下事项
分区表中不能有多字段分区键。
多级分区表必须是规整的(每个分区的子分区结构完全相同例如第一个分区有3
个子分区第二个分区有4个子分区这种就不是规整的多级分区表)
需要确保optimizer_enable_master_only_queries参数设置为ON才能在
查询只在Master上存在的系统表时使用Orca优化器。实际上并不需要启用该参
因为带来的坏处比好处更大。
ROOT分区上已经收集了统计信息。
注意打开 optimizer_enable_master_only_queries 参数会降低针对系统表
的短查询性能请只在必要时在会话中打开。
如果一个分区表包含了超过 20000 个分区应该考虑重新规划表的设计。编者认
为超过 500 个分区就已经很不正常了。分区粒度要适中不能太随意否则后患无穷。
以下这些参数将会影响 Orca 如何生成执行计划
optimizer_cte_inlining_bound -- 编者没有理解这个参数的具体含义
编者查找了github上的测试用例对每个测试场景进行了测试不管该参数设置
为多少所有测试用例的执行计划是否走Orca和该参数的开关没有任何关系。
optimizer_force_multistage_agg -- 强制Orca选择多阶段聚合该参数
5版本的缺省值为TRUE6版本的缺省值为FALSE。为FALSEOrca根据
Cost评估选择一阶段聚合或二阶段聚合。编者认为三阶段聚合的适用面更广。
optimizer_force_three_stage_scalar_dqa -- 强制Orca选择三阶段聚
合。该该参数缺省值为TRUE建议不要修改。
版权所有Esena(陈淼 ) 编写陈淼 - 189 -
Greenplum Database 管理员指南 V6.2.1
optimizer_join_order -- 设置OrcaJoin的顺序调整的激进程度有四
个可选级别分别为querygreedyexhaustiveexhaustive2缺省的
级别为exhaustive各个级别大概的含义分别是不调整、尝试、努力、穷举。
通常不建议降低该参数的级别因为可能会导致很多查询无法找到最优的执行计
划。
optimizer_join_order_threshold -- OrcaJoin尝试调整顺序的Join
数量的上限。缺省值为10编者认为Join的表的数量超过该参数设置的值之后
Orca不会再尝试调整Join的顺序这与一些人的理解有出入有些人的理解是
超过10张表JoinOrca将会按照每组不超过10张表进行分组优化编者的测
试显示超过10张表的Join生成执行计划的结果和耗时与把该参数设置为1
是完全一致的。而对于PostgreSQL的类似参数join_collapse_limit则更
符合分组优化的解释。Join的表数量过多时的确无法穷举所有排序的可能性
因为N张表Join顺序的穷举数量是N的阶乘数量级增长过快CPU无法处理。
optimizer_nestloop_factor -- 设置Orca评估Nestloop Join时的Cost
因子。缺省值为1024该值越小越容易选择Nestloop Join例如在IOPS
力非常好的SSD系统可以缩小该参数提高选择索引关联的可能性。
optimizer_parallel_union -- 是否对UNIONUNION ALL执行并行扫描。
缺省值为OFF当设置为ONOrca将可以针对UNIONUNION ALL的不同部分
执行并行扫描任务会被拆分成更多的Slice执行计划也会更复杂有时未必会
带来性能的提升建议不要随意修改。
optimizer_sort_factor -- 设置Orca选择排序时的成本因子缺省值为1
最小值为0越大的值越可能避免走排序。
gp_enable_relsize_collection -- 设置当一张表没有统计信息时Orca
PostgreSQL优化器如何处理该表。缺省情况下Orca使用一个默认值来(一般
1条记录)作为没有统计信息的表的统计信息评估结果如果该参数设置为ON
Orca会通过该表的尺寸来评估统计信息。
对于一个分区表来说Orca查询ROOT分区时如果ROOT分区上没有统计信息
Orca并不会去评估表的尺寸因为ROOT表中没有数据而是继续使用默认值。
PostgreSQL优化器会获取叶子分区的尺寸。这种情况下可能需要使用ANALYZE
ROOTPARTITION命令来收集ROOT分区的统计信息。
以下参数控制着Orca的日志信息的输出
optimizer_print_missing_stats -- 控制在Orca执行一个查询时其中
的字段缺失统计信息时是否显示字段的信息。不过编者实测目前的5版本和6
本都不会有相关信息或者日志输出而且也的确不需要这个信息。
optimizer_print_optimization_stats -- 控制Orca是否输出对执行计
版权所有Esena(陈淼 ) 编写陈淼 - 190 -
Greenplum Database 管理员指南 V6.2.1
划进行优化的过程计量信息包括一些资源的消耗和耗时等主要用于跟踪Orca
的性能。缺省为FALSE
使用 Orca 时还可以通过 optimizer_minidump 参数来控制生成 Orca
minidump 文件该文件会存储在 Master 工作目录下的 minidumps 子目录中该文
件不是一个平面文件主要用于原厂支持人员分析诊断问题例如
Minidump_20200618_222043_11_33.mdp
该参数的缺省值为ONERROR设置为ALWAYS则会每次生成minidump文件。该参
数可以在Session中设置生效。
使用 Orca 执行 EXPLAIN 或者 EXPLAIN ANALYZE 命令时执行计划只会显示
分区消除的数量并不会显示详细的分区信息号称设置参数
gp_log_dynamic_partition_pruning ON 就可以显示经过编者实测目前的 5
版本和 6 版本并不会显示这些信息。
Orca 特性与增强
Orca号称是 GP 的下一代查询优化器在某些查询和算子的场景中优势明显。
针对分区表的查询。
包含子查询的查询。
包含CTE的查询。
DML操作的增强。
在有些方面 Orca 也有改进
Join顺序的调整。
关联聚合的顺序调整。
Sort顺序的优化。
对数据倾斜的评估。
版权所有Esena(陈淼 ) 编写陈淼 - 191 -
Greenplum Database 管理员指南 V6.2.1
针对分区表的查询
Orca 对于分区表的查询有如下的增强
分区消除得到改善。
可以支持规整的多级分区表(一般不存在多级分区表所以无所谓是否规整)
执行计划可以包含一个算子作为动态分区消除的条件对于常量分区条件执行计
划会给出分区消除的数量信息非常量分区条件则不会给出分区消除的数量信息。
执行计划中不会枚举所有分区(动态分区扫描整体作为一个算子)
对于常量分区过滤条件Orca 会在执行计划中列出分区消除的数量。例如
-> Partition Selector for sales (dynamic scan id: 2) . . .
Partitions selected: 1 (out of 12)
-> Dynamic Table Scan on sales (dynamic scan id: 2) . . .
Filter: date = '2020-01-02'::date
对于分区过滤条件是一个算子的情况具体的分区消除的数量只有在执行时才能知
所以Orca 不会在执行计划中列出分区消除的数量但在 EXPLAIN ANALYZE
的输出中会有体现。例如下面是 EXPLAIN ANALYZE 的一部分
-> Dynamic Table Scan on sales (dynamic scan id: 1) . . .
Rows out: Avg 105.0 rows x 2 workers
Partitions scanned: Avg 1.0 (out of 12) x 2 workers. Max 1 parts (seg0).
执行计划的尺寸与分区数量无关。
大大缓解了因为分区数量引起的内存不足的报错。
例如使用 CREATE TABLE 命令创建了一张 RANGE 分区表
CREATE TABLE sales (
id int,
date date,
amt decimal(10,2)
)DISTRIBUTED BY (id) PARTITION BY RANGE (date) (
START (date '2020-01-01') INCLUSIVE
END (date '2021-01-01') EXCLUSIVE EVERY (INTERVAL '1 month')
);
版权所有Esena(陈淼 ) 编写陈淼 - 192 -
Greenplum Database 管理员指南 V6.2.1
Orca 改进了该分区表的这些查询场景
全表扫描的查询执行计划中不会枚举所有分区。
=# SELECT * FROM sales;
使用常量作为分区过滤条件执行计划中显示了分区消除的信息。
=# SELECT * FROM sales WHERE date = '2020-01-02';
使用范围过滤条件执行计划中显示了分区消除的信息。
=# SELECT * FROM sales WHERE date BETWEEN '2020-01-02' AND '2020-01-03';
通过一个子查询来指定分区过滤条件执行计划中只显示动态分区消除而不显
示分区消除的数量。
=# SELECT * FROM sales WHERE date =
(SELECT max(date+100) FROM sales WHERE date='2020-01-02');
包含子查询的查询
Orca 可以更好的处理子查询子查询是嵌套在一个查询中的查询语句例如在
WHERE 查询中的 SELECT 就是一个子查询。例如
=# SELECT * FROM part
WHERE price > (SELECT avg(price) FROM part);
Orca 可以更好的处理关联子查询关联子查询是在子查询中引用了外部查询的
字段例如下面这个查询brand 字段在子查询中被引用
=# SELECT * FROM part p1
WHERE price > (
SELECT avg(p2.price) FROM part p2
WHERE p2.brand = p1.brand
);
Orca 为以下类型的子查询生成更高效的执行计划
版权所有Esena(陈淼 ) 编写陈淼 - 193 -
Greenplum Database 管理员指南 V6.2.1
查询的字段列表中包含关联子查询。
=# SELECT *,
(SELECT min(p2.price) FROM part p2 WHERE p1.brand = p2.brand) AS foo
FROM part p1;
OR条件中包含关联子查询。
=# SELECT FROM part p1 WHERE p1.p_size > 40 OR p1.p_retailprice > (
SELECT avg(p2.p_retailprice)
FROM part p2
WHERE p2.p_brand = p1.p_brand
);
跨级关联的嵌套子查询(内层的子查询引用了更外层查询的字段 -- 跨级引用)
=# SELECT * FROM part p1 WHERE p1.p_partkey IN (
SELECT p2.p_partkey FROM part p2
WHERE p2.p_retailprice = (
SELECT min(p3.p_retailprice) FROM part p3
WHERE p3.p_brand = p1.p_brand
)
);
注意PostgreSQL优化器不支持这种越级关联的嵌套子查询。
包含AGG和非等值关联的关联子查询。
=# SELECT * FROM part p1 WHERE p1.p_retailprice = (
SELECT min(p2.p_retailprice) FROM part p2
WHERE p2.p_brand <> p1.p_brand
);
关联子查询中的子查询对于每个关联值只能返回单条记录。
=# SELECT p_partkey,
(SELECT p2.p_retailprice FROM part p2 WHERE p2.p_brand = p1.p_brand)
FROM part p1;
注意如果子查询对于每个关联值返回多条记录将会在运行时期间报错SQL语法并
不会报错。
版权所有Esena(陈淼 ) 编写陈淼 - 194 -
Greenplum Database 管理员指南 V6.2.1
包含 CTE 的查询
Orca 增强了对包含 WITH 子句的查询的支持。这种查询也被称为 CTEWITH
的语句块可以类似临时表一样来使用。例如
=# WITH v AS (SELECT a, sum(b) as s FROM T where c < 10 GROUP BY a)
SELECT *FROM v AS v1 , v AS v2
WHERE v1.a <> v2.a AND v1.s < v2.s;
Orca 还可以将条件下推到 CTE 中。例如
=# WITH v AS (SELECT a, sum(b) as s FROM T GROUP BY a)
SELECT * FROM v as v1, v as v2, v as v3
WHERE v1.a < v2.a AND v1.s < v3.s
AND v1.a = 10 AND v2.a = 20 AND v3.a = 30;
Orca 还可以处理这些类型的 CTE
定义了多个ASCTE。例如
=# WITH cte1 AS (
SELECT a, sum(b) as s FROM T where c < 10 GROUP BY a
), cte2 AS (
SELECT a, s FROM cte1 where s > 1000
)
SELECT * FROM cte1 as v1, cte2 as v2, cte2 as v3
WHERE v1.a < v2.a AND v1.s < v3.s;
嵌套CTE
=# WITH v AS (
WITH w AS (
SELECT a, b FROM t WHERE b < 5
)
SELECT w1.a, w2.b FROM w AS w1, w AS w2
WHERE w1.a = w2.a AND w1.a > 2
)
SELECT v1.a, v2.a, v2.b
FROM v as v1, v as v2
WHERE v1.a < v2.a;
版权所有Esena(陈淼 ) 编写陈淼 - 195 -
Greenplum Database 管理员指南 V6.2.1
注意编者提醒这些 CTE 像不像小学生写的切记不要在业务中写出一堆这种非等
值关联的查询如果不懂数据库就不要乱给业务写 SQL害人害己。
DML 操作的增强
Orca 还增强了 DML 操作例如 INSERTUPDATE DELETE
在执行计划中DML也是一个算子。
作为一个常规的算子可以出现在执行计划的任何地方(目前只在顶端)
可以有后续算子(这与上一条的注释矛盾啊可能目前还不存在吧)
UPDATE操作通过Split算子来支持如下操作
在分布键字段执行UPDATE命令。
在分区字段执行UPDATE命令。
例如这是一个 Split 算子的例子
Update
(cost=0.00..431.13 rows=1 width=1)
-> Result
(cost=0.00..431.00 rows=1 width=34)
-> Split
(cost=0.00..431.00 rows=1 width=30)
-> Result
(cost=0.00..431.00 rows=1 width=30)
-> Seq Scan on t
(cost=0.00..431.00 rows=1 width=26)
Optimizer: Pivotal Optimizer (GPORCA)
官方文档说引入了一个新的算子Assert用于约束检查编者就目前最新的56
版本测不出这种算子而是一堆Result算子编者认为这一块可能已经改了。
Orca 带来的改变
PostgreSQL 优化器相比Orca 优化器带来了一些新的变化(有些是依然没有支持)
允许在分布键字段上执行UPDATE命令(6版本PostgreSQL优化器也已经支持
6版本之前只有Orca优化器可以支持)
版权所有Esena(陈淼 ) 编写陈淼 - 196 -
Greenplum Database 管理员指南 V6.2.1
允许在分区字段上执行UPDATE命令。
支持对规整(每个分区的子分区结构完全相同)的多级分区表的查询。
分区表的叶子分区有外部表的Orca会自动切换成PostgreSQL优化器。
除了INSERT命令不允许直接对分区表进行DML操作。
可以直接对叶子分区执行 INSERT 操作仍需要满足分区条件的约束非叶子分
区不允许执行 INSERT 操作。
ROOT分区需要统计信息来帮助Orca生成正确的执行计划。
在执行计划中引入了新的算子
Partition selector算子 -- 支持动态分区扫描。
Split算子 -- 支持对分布键字段和分区字段的UPDATE
使用Orca生成的执行计划与PostgreSQL优化器不同(最后一行会标识)
的确带来了很多改变有时不知道该用 Orca 优化器还是用 PostgreSQL 优化
二者皆有长短Orca 可以在一些场景有提升总归是一件好事吧。
Orca 的限制
Orca 不是万能的目前GP 数据库中Orca 优化器和 PostgreSQL 优化器并
因为Orca 在一些场景有优势但是又不能完全搞定所有问题所以只能共存
编者也不知道 Orca 的未来在何处。
不支持的SQL类型。
性能差异。
不支持的 SQL 类型
Orca 不支持的 SQL 类型
版权所有Esena(陈淼 ) 编写陈淼 - 197 -
Greenplum Database 管理员指南 V6.2.1
参数化的Prepared statement(Java连过数据库的都清楚这是啥)
表达式索引(Orca不会为此选择索引扫描也不切换到PostgreSQL优化器)
SP-GiST索引。Orca支持的索引类型为B-treebitmapGINGiST
对于不支持的索引Orca是当作其不存在例如上一条。
不规整的多级分区表(每个分区的子分区结构不完全相同)
叶子分区有外部表的分区表。
表名前带有ONLY关键字的SELECTUPDATEDELETE操作。
注意在官方文档中提到的一些其他的限制编者觉得微不足道或者没能测试验证的
此处不做列举。
性能差异
一些 Orca PostgreSQL 优化器已知的性能差异
Orca的短查询性能不好因为Orca生成执行计划的开销比起PostgreSQL优化器
来说大很多虽然在5.20左右的版本有了较大提升但相比于后者还是有极大
的差异虽然这些差异对于复杂计算(例如耗时在分钟级别或以上)不算什么
对于原本只需要几十毫秒的短查询来说不可忽略。
ANALYZE -- 对于Orca针对ROOT分区的ANALYZE会收集ROOT分区的统计信
PostgreSQL优化器不会因为其不需要这个统计信息。
DML操作 -- Orca增强了对分布键字段和分区字段的更新支持但也需要更多的
前置(overhead姑且就是这个含义吧)开销。
注意Orca 生成执行计划的耗时的确比 PostgreSQL 优化器多很多这个是一个
已知的问题。
验证查询是否使用了 Orca
版权所有Esena(陈淼 ) 编写陈淼 - 198 -
Greenplum Database 管理员指南 V6.2.1
可以通过检查 EXPLAIN 的输出来验证当执行一个 GP 的查询时是否使用了 Orca
优化器。
例如在 Orca 开启的情况下下面的例子表示查询使用了 Orca 优化器
Settings: optimizer=on
Optimizer status: Pivotal Optimizer (GPORCA) version . . .
例如下面的例子表明 Orca 开启的情况下自动切换到了 PostgreSQL 优化器
Settings: optimizer=on
Optimizer status: Postgres query optimizer
例如下面的例子表明 Orca 关闭的情况下使用了 PostgreSQL 优化器
Settings: optimizer=off
Optimizer status: Postgres query optimizer
注意关于 Orca编者就讲到这里想了解更多知识请参考官方文档。
定义查询
查询是一个查看、修改或者分析数据库中数据的命令。本节介绍如何在GP中构造
SQL查询。
SQL修辞
SQL值表达式
SQL 修辞
SQL(结构化查询语言)是用来访问数据库的一种语言。SQL语言有特定的修辞和词
(单词、特征等)据此构造数据库引擎可以理解的查询或命令。
SQL由一系列的命令组成。命令由一系列按照语法规范编写的修辞组成以分号(;)
结尾。
GP基于PostgreSQL并遵循相同的SQL结构和语法(一些MPP相关的有差异)。大
多情况下GP的语法与PostgreSQL对等不过在GP中有些命令可能会有增量或者语
法限制(MPP的特性决定的)
版权所有Esena(陈淼 ) 编写陈淼 - 199 -

 

 

 

 

 

 

 

Content      ..     2      3      4      5     ..