简单连接中未使用的主键索引

dez*_*zso 17 postgresql performance index join primary-key

我有以下表格和索引定义:

CREATE TABLE munkalap (
    munkalap_id serial PRIMARY KEY,
    ...
);

CREATE TABLE munkalap_lepes (
    munkalap_lepes_id serial PRIMARY KEY,
    munkalap_id integer REFERENCES munkalap (munkalap_id),
    ...
);

CREATE INDEX idx_munkalap_lepes_munkalap_id ON munkalap_lepes (munkalap_id);
Run Code Online (Sandbox Code Playgroud)

为什么在以下查询中没有使用 munkalap_id 上的任何索引?

EXPLAIN ANALYZE SELECT ml.* FROM munkalap m JOIN munkalap_lepes ml USING (munkalap_id);

QUERY PLAN
Hash Join  (cost=119.17..2050.88 rows=38046 width=214) (actual time=0.824..18.011 rows=38046 loops=1)
  Hash Cond: (ml.munkalap_id = m.munkalap_id)
  ->  Seq Scan on munkalap_lepes ml  (cost=0.00..1313.46 rows=38046 width=214) (actual time=0.005..4.574 rows=38046 loops=1)
  ->  Hash  (cost=78.52..78.52 rows=3252 width=4) (actual time=0.810..0.810 rows=3253 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 115kB
        ->  Seq Scan on munkalap m  (cost=0.00..78.52 rows=3252 width=4) (actual time=0.003..0.398 rows=3253 loops=1)
Total runtime: 19.786 ms
Run Code Online (Sandbox Code Playgroud)

即使我添加过滤器也是一样的:

EXPLAIN ANALYZE SELECT ml.* FROM munkalap m JOIN munkalap_lepes ml USING (munkalap_id) WHERE NOT lezarva;

QUERY PLAN
Hash Join  (cost=79.60..1545.79 rows=1006 width=214) (actual time=0.616..10.824 rows=964 loops=1)
  Hash Cond: (ml.munkalap_id = m.munkalap_id)
  ->  Seq Scan on munkalap_lepes ml  (cost=0.00..1313.46 rows=38046 width=214) (actual time=0.007..5.061 rows=38046 loops=1)
  ->  Hash  (cost=78.52..78.52 rows=86 width=4) (actual time=0.587..0.587 rows=87 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 4kB
        ->  Seq Scan on munkalap m  (cost=0.00..78.52 rows=86 width=4) (actual time=0.014..0.560 rows=87 loops=1)
              Filter: (NOT lezarva)
Total runtime: 10.911 ms
Run Code Online (Sandbox Code Playgroud)

dbe*_*hur 29

很多人都听说过“顺序扫描不好”的指导,并试图将它们从他们的计划中消除,但事实并非如此简单。如果查询要覆盖表中的每一行,顺序扫描是获取这些行的最快方法。这就是您原来的连接查询使用 seq 扫描的原因,因为两个表中的所有行都是必需的。

在规划查询时,Postgres 的规划器会估计不同可能方案下各种操作(计算、顺序和随机 IO)的成本,并选择其估计成本最低的计划。当从旋转存储(磁盘)进行 IO 时,随机 IO 通常比顺序 IO 慢得多,random_page_cost 和 seq_page_cost的默认 pg 配置估计成本差异为 4:1。

在考虑使用索引与顺序扫描表的连接或过滤方法时,这些考虑因素会起作用。使用索引时,计划可能会通过索引快速找到一行,然后必须考虑随机块读取来解析行数据。对于添加了过滤谓词的第二个查询WHERE NOT lezarva,您可以在 EXPLAIN ANALYZE 结果中看到这如何影响规划估计。规划器估计连接产生 1006 行(与 964 的实际结果集非常接近)。考虑到较大的表 munkalap_lepes 包含大约 38K 行,规划器看到连接将必须访问表中大约 1006/38046 或 1/38 的行。它还知道平均行宽是 214 字节,一个块是 8K,所以大约有 38 行/块。

有了这些统计信息,规划器认为连接可能必须读取所有或大部分表的数据块。由于索引查找也不是免费的,并且扫描评估过滤条件的块的计算相对于 IO 非常便宜,规划器选择顺序扫描表并在计算 seq 扫描时避免索引开销和随机读取会更快。

在现实世界中,数据通常通过操作系统页面缓存在内存中可用,因此并非每个块读取都需要 IO。很难预测缓存对于给定查询的有效性,但 Pg 规划器确实使用了一些简单的启发式方法。配置值effective_cache_size通知招致实际IO成本的情形产生的规划者估计。较大的值将导致它估计随机 IO 的成本较低,因此可能会使其偏向于顺序扫描的索引驱动方法。

  • 很好的解释。不过,行/数据页的计算有点偏离。您必须考虑页头(24 个字节)+ 每个每行项目指针的 4 个字节+行头 `HeapTupleHeader`(每行 23 个字节)+ NULL 位掩码 + 根据 MA​​XALIGN 对齐。最后,由于数据对齐而产生的未知填充量取决于列的数据类型及其序列。在这种情况下,一个 8 kb 的页面上总共有不超过 33 行。(不考虑 TOAST。) (2认同)

a_h*_*ame 8

您正在从两个表中检索所有行,因此使用索引扫描没有真正的好处。索引扫描仅在您仅从表中选择几行时才有意义(通常小于 10%-15%)