I. 教程
II. SQL 语言
III. 服务器管理
- 章14. 安装指导
- 章15. 在 Windows 上安装客户端
- 章16. 操作系统环境
- 章17. 服务器配置
- 章18. 数据库角色和权限
- 章19. 管理数据库
- 章21. 区域
- 章20. 用户认证
- 章22. 日常数据库维护工作
- 章23. 备份与恢复
- 章24. 高可用性与负载均衡
- 章25. 监控数据库的活动
- 章26. 监控磁盘使用情况
- 章27. 可靠性和预写式日志
- 章28. 回归测试
IV. 客户端接口
- Chapter 28. libpq - C 库
- 章29.
- Chapter 30. ecpg - 在 C 里嵌入 SQL
- Chapter 31. 信息模式
- 31.2. 数据类型
- 31.3. informationschemacatalog_name
- 31.4. applicable_roles
- 31.5. check_constraints
- 31.6. columndomainusage
- 31.7. column_privileges
- 31.8. columnudtusage
- 31.9. columns
- 31.10. constraintcolumnusage
- 31.11. constrainttableusage
- 31.12. datatypeprivileges
- 31.13. domain_constraints
- 31.14. domainudtusage
- 31.15. domains
- 31.16. element_types
- 31.17. enabled_roles
- 31.18. keycolumnusage
- 31.20. referential_constraints
- 31.21. rolecolumngrants
- 31.22. roleroutinegrants
- 31.23. roletablegrants
- 31.24. roleusagegrants
- 31.25. routine_privileges
- 31.26. routines
- 31.27. schemata
- 31.28. sql_features
- 31.29. sqlimplementationinfo
- 31.30. sql_languages
- 31.31. sql_packages
- 31.32. sql_sizing
- 31.33. sqlsizingprofiles
- 31.34. table_constraints
- 31.35. table_privileges
- 31.36. tables
- 31.37. triggers
- 31.38. usage_privileges
- 31.39. viewcolumnusage
- 31.40. viewtableusage
- 31.41. views
V. 服务器端编程
- 章33. 扩展 SQL
- 章34. 触发器
- 章35. 规则系统
- 章36. 过程语言
- 章37. PL/pgSQL - SQL 过程语言
- 章38. PL/Tcl - Tcl 过程语言
- 章39. PL/Perl - Perl 过程语言
- 章40. PL/Python - Python 过程语言
- 章41. 服务器编程接口
VI. 参考手册
- I. SQL 命令
- ABORT
- ALTER AGGREGATE
- ALTER CONVERSION
- ALTER DATABASE
- ALTER DOMAIN
- ALTER FUNCTION
- ALTER GROUP
- ALTER INDEX
- ALTER LANGUAGE
- ALTER OPERATOR
- ALTER OPERATOR CLASS
- ALTER ROLE
- ALTER SCHEMA
- ALTER SEQUENCE
- ALTER TABLE
- ALTER TABLESPACE
- ALTER TRIGGER
- ALTER TYPE
- ALTER USER
- ANALYZE
- BEGIN
- CHECKPOINT
- CLOSE
- CLUSTER
- COMMENT
- COMMIT PREPARED
- COMMIT
- COPY
- CREATE AGGREGATE
- CREATE CAST
- CREATE CONSTRAINT TRIGGER
- CREATE CONVERSION
- CREATE DATABASE
- CREATE DOMAIN
- CREATE FUNCTION
- CREATE GROUP
- CREATE INDEX
- CREATE LANGUAGE
- CREATE OPERATOR CLASS
- CREATE OPERATOR
- CREATE ROLE
- CREATE RULE
- CREATE SCHEMA
- CREATE SEQUENCE
- CREATE TABLE
- CREATE TABLE AS
- CREATE TABLESPACE
- CREATE TRIGGER
- CREATE TYPE
- CREATE USER
- CREATE VIEW
- DEALLOCATE
- DECLARE
- DELETE
- DROP OWNED
- DROP AGGREGATE
- DROP CAST
- DROP CONVERSION
- DROP DATABASE
- DROP DOMAIN
- DROP FUNCTION
- DROP GROUP
- DROP INDEX
- DROP LANGUAGE
- DROP OPERATOR CLASS
- DROP OPERATOR
- DROP ROLE
- DROP RULE
- DROP SCHEMA
- DROP SEQUENCE
- DROP TABLE
- DROP TABLESPACE
- DROP TRIGGER
- DROP TYPE
- DROP USER
- DROP VIEW
- END
- EXECUTE
- EXPLAIN
- FETCH
- GRANT
- INSERT
- LISTEN
- LOAD
- LOCK
- MOVE
- NOTIFY
- PREPARE TRANSACTION
- PREPARE
- REASSIGN OWNED
- REINDEX
- RELEASE SAVEPOINT
- RESET
- REVOKE
- ROLLBACK PREPARED
- ROLLBACK TO SAVEPOINT
- ROLLBACK
- SAVEPOINT
- SELECT
- SELECT INTO
- SET
- SET CONSTRAINTS
- SET ROLE
- SET SESSION AUTHORIZATION
- SET TRANSACTION
- SHOW
- START TRANSACTION
- TRUNCATE
- UNLISTEN
- UPDATE
- VACUUM
- II. PostgreSQL 客户端应用程序
- III. PostgreSQL 服务器应用程序
VII. 内部
- 章42. PostgreSQL 内部概貌
- 章43. 系统表
- 43.1. 概述
- 43.2. pg_aggregate
- 43.3. pg_am
- 43.4. pg_amop
- 43.5. pg_amproc
- 43.6. pg_attrdef
- 43.7. pg_attribute
- 43.8. pg_authid
- 43.9. pgauthmembers
- 43.10. pg_autovacuum
- 43.11. pg_cast
- 43.12. pg_class
- 43.13. pg_constraint
- 43.14. pg_conversion
- 43.15. pg_database
- 43.16. pg_depend
- 43.17. pg_description
- 43.18. pg_index
- 43.19. pg_inherits
- 43.20. pg_language
- 43.21. pg_largeobject
- 43.22. pg_listener
- 43.23. pg_namespace
- 43.24. pg_opclass
- 43.25. pg_operator
- 43.26. pg_pltemplate
- 43.27. pg_proc
- 43.28. pg_rewrite
- 43.29. pg_shdepend
- 43.30. pg_shdescription
- 43.31. pg_statistic
- 43.32. pg_tablespace
- 43.33. pg_trigger
- 43.34. pg_type
- 43.35. 系统视图
- 43.36. pg_cursors
- 43.37. pg_group
- 43.38. pg_indexes
- 43.39. pg_locks
- 43.40. pgpreparedstatements
- 43.41. pgpreparedxacts
- 43.42. pg_roles
- 43.43. pg_rules
- 43.44. pg_settings
- 43.45. pg_shadow
- 43.46. pg_stats
- 43.47. pg_tables
- 43.48. pgtimezoneabbrevs
- 43.49. pgtimezonenames
- 43.50. pg_user
- 43.51. pg_views
- 章44. 前/后端协议
- 章45. PostgreSQL 编码约定
- 章46. 本地语言支持
- 章47. 书写一个过程语言处理器
- 章48. 基因查询优化器
- 章49. 索引访问方法接口定义
- 章50. GiST 索引
- 章51. GIN 索引
- 章52. 数据库物理存储
- 章53. BKI 后端接口
- 章54. 规划器如何使用统计信息
VIII. 附录
13.3. 用明确的 JOIN 控制规划器
我们可以在一定程度上用明确的 JOIN
语法控制查询规划器。要明白为什么有这茬事,我们首先需要一些背景知识。
在简单的连接查询里,比如
SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;
规划器可以按照任何顺序自由地连接给出的表。比如,它可以生成一个查询规划先用 WHERE
条件 a.id = b.id
把 A 连接到 B ,然后用另外一个 WHERE
条件把 C 连接到这个表上来,或者也可以先连接 B 和 C 然后再连接 A ,同样得到这个结果。或者也可以连接 A 到 C 然后把结果与 B 连接,不过这么做效率比较差,因为必须生成完整的 A 和 C 的迪卡尔积,而在查询里没有可用的 WHERE
子句可以优化该连接(PostgreSQL 执行器里的所有连接都发生在两个输入表之间,所以在这种情况下它必须先得出一个结果)。重要的一点是这些连接方式给出语义上相同的结果,但在执行开销上却可能有巨大的差别。因此,规划器会对它们进行检查并找出最高效的查询规划。
如果查询只涉及两或三个表,那么在查询里不会有太多需要考虑的连接。但是潜在的连接顺序的数目随着表数目的增加程指数增加的趋势。当超过十个左右的表以后,实际上根本不可能对所有可能做一次穷举搜索,甚至对六七个表都需要相当长的时间进行规划。如果有太多输入的表,PostgreSQL 规划器将从穷举搜索切换为基因概率搜索,以减少可能性数目(样本空间)。切换的阈值是用运行时参数 geqo_threshold 设置的。基因搜索花的时间少,但是并不一定能找到最好的规划。
当查询涉及外部连接时,规划器就不像对付普通(内部)连接那么自由了。比如,看看下面这个查询
SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
尽管这个查询的约束和前面一个非常相似,但它们的语义却不同,因为如果 A 里有任何一行不能匹配B和C的连接里的行,那么该行都必须输出。因此这里规划器对连接顺序没有什么选择:它必须先连接 B 到 C ,然后把 A 连接到该结果上。因此,这个查询比前面一个花在规划上的时间少。在其它情况下,规划器就有可能确定多种连接顺序都是安全的。比如,对于
SELECT * FROM a LEFT JOIN b ON (a.bid = b.id) LEFT JOIN c ON (a.cid = c.id);
将 A 首先连接到 B 或 C 都是有效的。当前,只有 FULL JOIN
完全强制连接顺序。大多数 LEFT JOIN
或 RIGHT JOIN
都可以在某种程度上重新排列。
明确的连接语法(INNER JOIN
, CROSS JOIN
或无修饰的 JOIN
)语义上和 FROM
中列出输入关系是一样的,因此我们没有必要约束连接顺序。
即使大多数 JOIN
并不完全强迫连接顺序,但仍然可以明确的告诉 PostgreSQL 查询规划器 JOIN
子句的连接顺序。比如,下面三个查询逻辑上是等效的:
SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id; SELECT * FROM a CROSS JOIN b CROSS JOIN c WHERE a.id = b.id AND b.ref = c.id; SELECT * FROM a JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
但如果我们告诉规划器遵循 JOIN
的顺序,那么第二个和第三个还是要比第一个花在规划上的时间少。这个作用对于只有三个表的连接而言是微不足道的,但对于数目众多的表,可能就是救命稻草了。
要强制规划器遵循准确的 JOIN
连接顺序,我们可以把运行时参数 join_collapse_limit 设置为 1(其它可能的数值在下面讨论)。
你完全不必为了缩短搜索时间来约束连接顺序,因为在一个简单的 FROM
列表里使用 JOIN
操作符就很好了。比如考虑:
SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...;
如果设置 join_collapse_limit
= 1 ,那么这句话就相当于强迫规划器先把A连接到B ,然后再连接到其它的表上,但并不约束其它的选择。在本例中,可能的连接顺序的数目减少了 5 倍。
按照上面的想法考虑规划器的搜索问题是一个很有用的技巧,不管是对减少规划时间还是对引导规划器生成好的规划都很有帮助。如果缺省时规划器选择了一个糟糕的连接顺序,你可以用 JOIN
语法强迫它选择一个更好的(假设知道一个更好的顺序)。所以我们建议多试验。
一个非常相近的影响规划时间的问题是把子查询压缩到它们的父查询里面。比如,考虑下面的查询
SELECT * FROM x, y, (SELECT * FROM a, b, c WHERE something) AS ss WHERE somethingelse;
这个情况可能在那种包含连接的视图中出现;该视图的 SELECT
规则将被插入到引用视图的场合,生成非常类似上面的查询。通常,规划器会试图把子查询压缩到父查询里,生成
SELECT * FROM x, y, a, b, c WHERE something AND somethingelse;
这样通常会生成一个比独立的子查询更好些的规划。比如,外层的 WHERE
条件可能先把X连接到 A 上,这样就消除了 A 中的许多行,因此避免了形成全部子查询逻辑输出的需要。但是同时,我们增加了规划的时间;在这里,我们有一个用五路连接代替两个独立的三路连接的问题,这样的差距是巨大的,因为可能的规划数的是按照指数增长的。规划器将在父查询可能超过 from_collapse_limit
个 FROM
项的时候,不再压缩子查询,以此来避免巨大的连接搜索数。你可以通过调整这个运行时参数来在规划时间和规划质量之间作出平衡。
from_collapse_limit 和 join_collapse_limit 名字类似是因为他们做的事情几乎相同:一个控制规划器何时把子查询"平面化",另外一个控制何时把明确的连接平面化。通常,你要么把 join_collapse_limit
设置成和 from_collapse_limit
一样(明确连接和子查询的行为类似),要么把 join_collapse_limit
设置为 1(如果你想用明确连接控制连接顺序)。但是你可以把它们设置成不同的值,这样你就可以在规划时间和运行时间之间进行仔细的调节。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论