了解PostgreSQL中的相关性

时间:2018-09-12 06:05:28

标签: postgresql indexing correlation

当数据库必须执行到另一个表的联接时,它可以从以下三种策略之一中广泛选择:

  • 顺序扫描(当我们需要大多数记录时)
  • 位图索引扫描(当我们需要一些记录时)
  • 索引扫描(当我们想要相对较少的记录且具有相关索引时)

此处的理由是,如果需要保留大多数记录,则完全忽略索引,避免I / O惩罚并按顺序读取整个表的效率更高。在另一个极端,显然,如果我们只需要从索引中读取几个叶节点,那将比读取整个表要快。

不是对我来说清楚的是,关联在这里扮演什么角色,以及我们应该如何思考。

着眼于Postgres,documentation在这里描述了相关性:

  

列值的物理行顺序和逻辑顺序之间的统计相关性。范围是-1至+1。当该值接近-1或+1时,由于减少了对磁盘的随机访问,因此估计该列上的索引扫描比接近零时便宜。 (如果列数据类型没有<运算符,则此列为null。)

这是一种获取给定表中每一列的相关值的方法:

SELECT attname, correlation
FROM pg_stats
WHERE tablename = 'your_table';

据我了解,始终使用二级索引 要求对聚集索引进行I / O查找,以找到数据。据我所知,唯一使I / O更好或更糟的事情是,二级索引是否非常靠近磁盘上的群集索引。但是我一直不清楚,因为始终需要进行查找,所以相关性对于确定I / O查找的成本有多重要?

有人可以解释一下相关性在这里意味着什么吗?也许我的困惑是由于不了解数据库如何执行索引扫描。

3 个答案:

答案 0 :(得分:2)

该关联仅对具有总顺序的数据类型的列有意义,也就是说,它支持属于btree访问方法(<<==>=>运算符。

如果在表格的物理末端附近倾向于出现较大的值,而在表格的开始附近倾向于出现较小的值,则相关性为正。值1表示值按排序顺序存储在表中,值-1表示它们按降序存储。

PostgreSQL中的索引扫描是这样的:

  1. 第一个匹配项位于索引中。

  2. 如果operator family指示相应的表块仅包含每个人都可见的元组 ,我们不需要索引中未存储的列,结果,然后继续执行第4步(如果优化程序认为这适用于大多数索引条目,则将计划一个visibility map)。

  3. 从表中获取相应的行并检查可见性。如果可见并且满足过滤条件,我们已经找到了结果。

  4. 沿扫描方向遍历索引以找到下一个索引条目,并查看其是否满足扫描条件。如果是,请返回第二步,否则我们就完成了。

这将导致对表块进行随机读取,除非它们已经在共享缓冲区中。

现在,如果相关性很高,则更有可能发生两件事:

  • 在索引扫描中找到的下一个元组与上一个元组在同一表块中。那么它已经在共享缓冲区中并且不会引起读取。

    总而言之,您最终会碰到更少的不同表块:彼此相邻的索引条目也往往彼此靠近,通常在同一块中。

  • 如果下一个索引条目未指向与上一个相同的表块,则很可能指向下一个表块。这导致顺序读取表块,在旋转磁盘上比随机读取更有效。

让我通过一个例子说明这一点,假设索引在完全相关的列上:

找到的第一个索引条目也指向表块42,第二个也指向表块42,第三个到第30个指向表块43,接下来的20个索引条目将指向块44。

因此,索引扫描将访问50个元组,但它只会从磁盘读取3个块,并且它们按顺序读取(首先是块42,然后是块43,然后是块44)。

如果没有相关性,则50个元组可能位于不同的表块中(假设表很大),这意味着50次随机磁盘读取。

因此,在相关性较高时,索引扫描便宜,而在相关性较低时,向后索引扫描便宜。优化程序使用相关性来相应地调整估计成本。

答案 1 :(得分:1)

我认为可以通过阅读源代码找到真正的定义;-)

  • 统计收集器可能使用{ctid,attribval​​ue}对来估计物理<->逻辑相关
  • 计划者可以使用这些系数来估计要获取的页面数

作为示例,这是在Raspberry Pi上运行的我的twitter-sucker-DB的统计信息: (当前约350万行)


\connect twitters

SELECT version();
SELECT count(*) from tweets;

\d tweets

SELECT attname, correlation, n_distinct -- , null_frac
FROM pg_stats
WHERE tablename = 'tweets'
AND schemaname = 'public';

You are now connected to database "twitters" as user "postgres".
                                                          version                                                           
----------------------------------------------------------------------------------------------------------------------------
 PostgreSQL 9.6.9 on armv6l-unknown-linux-gnueabihf, compiled by gcc (Raspbian 6.3.0-18+rpi1+deb9u1) 6.3.0 20170516, 32-bit
(1 row)

  count  
---------
 3525068
(1 row)

                                      Table "public.tweets"
     Column     |           Type           |                      Modifiers                       
----------------+--------------------------+------------------------------------------------------
 seq            | bigint                   | not null default nextval('tweets_seq_seq'::regclass)
 id             | bigint                   | not null
 user_id        | bigint                   | not null
 sucker_id      | integer                  | not null default 0
 created_at     | timestamp with time zone | 
 is_dm          | boolean                  | not null default false
 body           | text                     | 
 in_reply_to_id | bigint                   | not null default 0
 parent_seq     | bigint                   | not null default 0
 is_reply_to_me | boolean                  | not null default false
 is_retweet     | boolean                  | not null default false
 did_resolve    | boolean                  | not null default false
 is_stuck       | boolean                  | not null default false
 need_refetch   | boolean                  | not null default false
 is_troll       | boolean                  | not null default false
 fetch_stamp    | timestamp with time zone | not null default now()
Indexes:
    "tweets_pkey" PRIMARY KEY, btree (seq)
    "tweets_id_key" UNIQUE CONSTRAINT, btree (id)
    "tweets_userid_id" UNIQUE, btree (user_id, id)
    "tweets_created_at_idx" btree (created_at)
    "tweets_du_idx" btree (created_at, user_id)
    "tweets_id_idx" btree (id) WHERE need_refetch = true
    "tweets_in_reply_to_id_created_at_idx" btree (in_reply_to_id, created_at) WHERE is_retweet = false AND did_resolve = false AND in_reply_to_id > 0
    "tweets_in_reply_to_id_fp" btree (in_reply_to_id)
    "tweets_parent_seq_fk" btree (parent_seq)
    "tweets_ud_idx" btree (user_id, created_at)
Foreign-key constraints:
    "tweets_parent_seq_fkey" FOREIGN KEY (parent_seq) REFERENCES tweets(seq)
    "tweets_user_id_fkey" FOREIGN KEY (user_id) REFERENCES tweeps(id)
Referenced by:
    TABLE "tweets" CONSTRAINT "tweets_parent_seq_fkey" FOREIGN KEY (parent_seq) REFERENCES tweets(seq)

    attname     | correlation | n_distinct 
----------------+-------------+------------
 seq            |   -0.519016 |         -1  #<<-- PK
 id             |   -0.519177 |         -1  #<<-- NaturalKey
 user_id        |  -0.0994714 |       1024  # FK to tweeps, cadinality ~= 5000)
 sucker_id      |    0.846975 |          5  # Low Card
 created_at     |   -0.519177 |  -0.762477  # High Card
 is_dm          |           1 |          1
 body           |   0.0276537 |  -0.859618
 in_reply_to_id |    0.104481 |      25956  # FK to self
 parent_seq     |    0.954938 |       1986  # FK To self
 is_reply_to_me |           1 |          2
 is_retweet     |    0.595322 |          2
 did_resolve    |    0.909326 |          2
 is_stuck       |           1 |          1
 need_refetch   |           1 |          1
 is_troll       |           1 |          1
 fetch_stamp    |   -0.519572 |      95960  # High Card
(16 rows)

这里奇怪的是,(大部分)升序列{seq,id,created_at,fetch_stamp}具有负相关,自引用FK {in_reply_to_id,parent_seq}是正相关。我的猜测是,对于n_distinct = -1(:= unique)列,根本不使用相关性,也许只使用了符号。

答案 2 :(得分:0)

首先,我们在做一些事情,很难完全解释,但我会尽力的,如果其中一些对您来说已经很明显了,请原谅。

因此,随机IO昂贵,因为我们正在整个磁盘上读取随机数据块。 批量读取多个块(特别是在非SSD的磁驱动器上)的速度要快得多

现在,下一步,您引用的文档正在专门讨论索引扫描,它通常是一个范围,而不是确切的值。

因此,当索引相关时(可以通过将表与相关索引进行聚类来强制执行),并且它正在寻找值范围IE(其中ID在1000000和1001000之间),则返回的位置(块位置)由“扫描”索引很可能位于磁盘上几乎相同的位置。

因此它可以对ABC进行索引,找到1000行并找出需要读取的块,并可能以很少的查找次数获得它们。额外花了1个(或大约1个)来获取索引是值得的。

现在,如果没有相关性,它通过索引进行查找,发现那1000行全部位于不同块中,在驱动器上的不同位置,那么最多将需要进行1000次查找才能找到数据。从头到尾批量读取整个表可能更好。对该索引的额外搜索只会使情况变得更糟,而批量读取现在对提高速度没有多大作用。

请让我知道这是否完全可以解释它。