下面我们来看看LIMIT的影响:
EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000 LIMIT 2; QUERY PLAN ------------------------------------------------------------------------------------- Limit (cost=0.29..14.48 rows=2 width=244) -> Index Scan using tenk1_unique2 on tenk1 (cost=0.29..71.27 rows=10 width=244) Index Cond: (unique2 > 9000) Filter: (unique1 < 100)这条查询的where条件和上面的一样,只是增加了LIMIT,所以不是所有数据都需要返回,规划器改变了规划。在索引扫描节点总消耗和返回记录数是运行玩查询之后的数值,但Limit节点预期时间消耗是15,所以总时间消耗是15.增加LIMIT会使启动时间小幅增加(0.25->0.29)。
来看一下通过索引字段的表连接:
EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 < 10 AND t1.unique2 = t2.unique2; QUERY PLAN -------------------------------------------------------------------------------------- Nested Loop (cost=4.65..118.62 rows=10 width=488) -> Bitmap Heap Scan on tenk1 t1 (cost=4.36..39.47 rows=10 width=244) Recheck Cond: (unique1 < 10) -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..4.36 rows=10 width=0) Index Cond: (unique1 < 10) -> Index Scan using tenk2_unique2 on tenk2 t2 (cost=0.29..7.91 rows=1 width=244) Index Cond: (unique2 = t1.unique2)这个规划中有一个内连接的节点,它有两个子节点。节点摘要行的缩进反映了规划树的结构。最外层是一个连接节点,子节点是一个Bitmap扫描。外部节点位图扫描的消耗和记录数如同我们使用SELECT...WHERE unique1 < 10,因为这时t1.unique2 = t2.unique2还不相关。接下来为每一个从外部节点得到的记录运行内部查询节点。这里外部节点得到的数据的t1.unique2值是可用的,所以我们得到的计划和SELECT...WHEREt2.unique2=constant的情况类似。(考虑到缓存的因素评估的消耗可能要小一些)
外部节点的消耗加上循环内部节点的消耗(39.47+10*7.91)再加一点CPU时间就得到规划的总消耗。
再看一个例子:
EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 < 10 AND t2.unique2 < 10 AND t1.hundred < t2.hundred; QUERY PLAN --------------------------------------------------------------------------------------------- Nested Loop (cost=4.65..49.46 rows=33 width=488) Join Filter: (t1.hundred < t2.hundred) -> Bitmap Heap Scan on tenk1 t1 (cost=4.36..39.47 rows=10 width=244) Recheck Cond: (unique1 < 10) -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..4.36 rows=10 width=0) Index Cond: (unique1 < 10) -> Materialize (cost=0.29..8.51 rows=10 width=244) -> Index Scan using tenk2_unique2 on tenk2 t2 (cost=0.29..8.46 rows=10 width=244) Index Cond: (unique2 < 10)条件t1.hundred<t2.hundred不在tenk2_unique2索引中,所以这个条件出现在连接节点中。这将减少连接节点的评估输出记录数,但不会改变子节点的扫描数。
注意这次规划器选择使用Meaterialize节点,将条件加入内部节点,这以为着内部节点的索引扫描只做一次,即使嵌套循环需要读取这些数据10次,Meterialize节点将数据保存在内存中,每次循环都从内存中读取数据。
如果我们稍微改变一下查询,会看到完全不同的规划:
EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2; QUERY PLAN ------------------------------------------------------------------------------------------ Hash Join (cost=230.47..713.98 rows=101 width=488) Hash Cond: (t2.unique2 = t1.unique2) -> Seq Scan on tenk2 t2 (cost=0.00..445.00 rows=10000 width=244) -> Hash (cost=229.20..229.20 rows=101 width=244) -> Bitmap Heap Scan on tenk1 t1 (cost=5.07..229.20 rows=101 width=244) Recheck Cond: (unique1 < 100) -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..5.04 rows=101 width=0) Index Cond: (unique1 < 100)这里规划器选择使用hash join,将一个表的数据存入内存中的哈希表,然后扫描另一个表并和哈希表中的每一条数据进行匹配。
注意缩进反应的规划结构。在tenk1表上的bitmap扫描结果作为Hash节点的输入建立哈希表。然后Hash Join节点读取外层子节点的数据,再循环检索哈希表的数据。