4

我有一个带有覆盖索引的表,它应该只使用索引来响应查询,而不需要检查表。事实上,如果 IN() 子句中有 1 个或几个元素,Postgres 会这样做。但是,如果 IN 子句有很多元素,它似乎是在索引上进行搜索,然后去表并重新检查条件......

我不明白为什么 Postgres 会这样做。它可以直接从索引提供查询,也可以不提供,如果它(理论上)没有其他要添加的内容,为什么要进入表?

桌子:

CREATE TABLE phone_numbers
(
  id serial NOT NULL,
  phone_number character varying,
  hashed_phone_number character varying,
  user_id integer,
  created_at timestamp without time zone,
  updated_at timestamp without time zone,
  ghost boolean DEFAULT false,
  CONSTRAINT phone_numbers_pkey PRIMARY KEY (id)
)
WITH (
  OIDS=FALSE
);

CREATE INDEX index_phone_numbers_covering_hashed_ghost_and_user
  ON phone_numbers
  USING btree
  (hashed_phone_number COLLATE pg_catalog."default", ghost, user_id);

我正在运行的查询是:

SELECT "phone_numbers"."user_id" 
FROM "phone_numbers" 
WHERE "phone_numbers"."hashed_phone_number" IN (*several numbers*) 
  AND "phone_numbers"."ghost" = 'f'

如您所见,索引具有回复该查询所需的所有字段。

如果我在 IN 子句中只有一个或几个数字,它会:

1个号码:

在 phone_numbers 上使用 index_phone_numbers_on_hashed_phone_number 进行索引扫描(成本=0.41..8.43 行=1 宽度=4)
  索引条件:((hashed_phone_number)::text = 'bebd43a6eb29b2fda3bcb63dcc7ffaf5433e78660ccd1a495c1180a3eaaf6b6a'::text)
  过滤器:“

3个数字:

Index Only Scan using index_phone_numbers_covering_hashed_ghost_and_user on phone_numbers (cost=0.42..17.29 rows=1 width=4)
  Index Cond: ((hashed_phone_number = ANY ('{8228a8116f1fdb12e243102cb85ecd859ebf7873d9332dce5f1343a481ec72e8,43ddeebdca2ea829d468d5debc84d475c8322cf4bf6edca286c918b04216387e,1578bf773eb6eb8a9b57a130922a28c9c91f1bda67202ef5936b39630ca4cfe4}'::text[])) AND (.. .)
  过滤器:(不是幽灵)”

但是,当我在 IN 子句中有很多数字时,Postgres 正在使用索引,但随后击中表,我不知道为什么:

Bitmap Heap Scan on phone_numbers (cost=926.59..1255.81 rows=106 width=4) Recheck
  Cond: ((hashed_phone_number)::text = ANY ('{b6459ce58f21d99c462b132cce7adc9ea947fa522a3849321e9fb65893006a5e,8228a8116f1fdb12e243102cb85ecd859ebf7873d9332dce5f1343a481ec72e8,ab3554acc1f287bb2e22ff20bb855e19a4177ef552676689d217dbb2a1a6177b,7ec9f58 (...)
  Filter: (NOT ghost)
  -> Bitmap Index Scan on index_phone_numbers_covering_hashed_ghost_and_user (cost=0.00..926.56 rows=106 width=0)
        Index Cond: (((hashed_phone_number)::text = ANY ('{b6459ce58f21d99c462b132cce7adc9ea947fa522a3849321e9fb65893006a5e,8228a8116f1fdb12e243102cb85ecd859ebf7873d9332dce5f1343a481ec72e8,ab3554acc1f287bb2e22ff20bb855e19a4177ef552676689d217dbb2a1a6177b,7e (... )

当前正在执行此查询,该查询在总行数为 50k 的表中查找 250 条记录,大约是在另一个表上查找类似查询的两倍,后者在具有 500 万行的表中查找 250 条记录,而不是很有意义。

任何想法可能会发生什么,以及我是否可以做任何事情来改善这一点?


更新:将覆盖索引中的列顺序更改为先显示重影,然后再使用 hashed_phone_number 也不能解决问题:

Bitmap Heap Scan on phone_numbers (cost=926.59..1255.81 rows=106 width=4) Recheck
  Cond: ((hashed_phone_number)::text = ANY ('{b6459ce58f21d99c462b132cce7adc9ea947fa522a3849321e9fb65893006a5e,8228a8116f1fdb12e243102cb85ecd859ebf7873d9332dce5f1343a481ec72e8,ab3554acc1f287bb2e22ff20bb855e19a4177ef552676689d217dbb2a1a6177b,7ec9f58 (...)
  Filter: (NOT ghost)
  -> Bitmap Index Scan on index_phone_numbers_covering_ghost_hashed_and_user (cost=0.00..926.56 rows=106 width=0)
        Index Cond: ((ghost = false) AND ((hashed_phone_number)::text = ANY ('{b6459ce58f21d99c462b132cce7adc9ea947fa522a3849321e9fb65893006a5e,8228a8116f1fdb12e243102cb85ecd859ebf7873d9332dce5f1343a481ec72e8,ab3554acc1f287bb2e22ff20bb855e19a4177ef55267668 (...)

4

1 回答 1

1

索引的选择基于优化器所说的查询的最佳解决方案。Postgres 正在努力使用您的索引,但它不是查询的最佳索引。

最好的索引ghost首先是:

CREATE INDEX index_phone_numbers_covering_hashed_ghost_and_user
  ON phone_numbers
  USING btree
  (ghost, hashed_phone_number COLLATE pg_catalog."default", user_id);

我碰巧认为 MySQL文档很好地解释了如何使用复合索引。

本质上,发生的事情是 Postgres 需要对in列表中的每个元素进行索引查找。这可能会因使用字符串而更加复杂——因为排序规则/编码会影响比较。最终,Postgres 决定其他方法更有效。如果你ghost先放,那么它只会跳转到索引的右侧并在那里找到它需要的行。

于 2015-05-26T11:51:23.820 回答