当数据库必须执行到另一个表的联接时,它可以从以下三种策略之一中广泛选择:
此处的理由是,如果需要保留大多数记录,则完全忽略索引,避免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查找的成本有多重要?
有人可以解释一下相关性在这里意味着什么吗?也许我的困惑是由于不了解数据库如何执行索引扫描。
答案 0 :(得分:2)
该关联仅对具有总顺序的数据类型的列有意义,也就是说,它支持属于btree
访问方法(<
, <=
,=
,>=
和>
运算符。
如果在表格的物理末端附近倾向于出现较大的值,而在表格的开始附近倾向于出现较小的值,则相关性为正。值1表示值按排序顺序存储在表中,值-1表示它们按降序存储。
PostgreSQL中的索引扫描是这样的:
第一个匹配项位于索引中。
如果operator family指示相应的表块仅包含每个人都可见的元组 ,我们不需要索引中未存储的列,结果,然后继续执行第4步(如果优化程序认为这适用于大多数索引条目,则将计划一个visibility map)。
从表中获取相应的行并检查可见性。如果可见并且满足过滤条件,我们已经找到了结果。
沿扫描方向遍历索引以找到下一个索引条目,并查看其是否满足扫描条件。如果是,请返回第二步,否则我们就完成了。
这将导致对表块进行随机读取,除非它们已经在共享缓冲区中。
现在,如果相关性很高,则更有可能发生两件事:
在索引扫描中找到的下一个元组与上一个元组在同一表块中。那么它已经在共享缓冲区中并且不会引起读取。
总而言之,您最终会碰到更少的不同表块:彼此相邻的索引条目也往往彼此靠近,通常在同一块中。
如果下一个索引条目未指向与上一个相同的表块,则很可能指向下一个表块。这导致顺序读取表块,在旋转磁盘上比随机读取更有效。
让我通过一个例子说明这一点,假设索引在完全相关的列上:
找到的第一个索引条目也指向表块42,第二个也指向表块42,第三个到第30个指向表块43,接下来的20个索引条目将指向块44。
因此,索引扫描将访问50个元组,但它只会从磁盘读取3个块,并且它们按顺序读取(首先是块42,然后是块43,然后是块44)。
如果没有相关性,则50个元组可能位于不同的表块中(假设表很大),这意味着50次随机磁盘读取。
因此,在相关性较高时,索引扫描便宜,而在相关性较低时,向后索引扫描便宜。优化程序使用相关性来相应地调整估计成本。
答案 1 :(得分:1)
我认为可以通过阅读源代码找到真正的定义;-)
作为示例,这是在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次查找才能找到数据。从头到尾批量读取整个表可能更好。对该索引的额外搜索只会使情况变得更糟,而批量读取现在对提高速度没有多大作用。
请让我知道这是否完全可以解释它。