sql - 为什么 PostgreSQL 不*仅*根据其 IN() 子句的内容在此查询中使用覆盖索引?

标签 sql postgresql indexing query-optimization covering-index

我有一个包含覆盖索引的表,它应该只使用索引来响应查询,而根本不检查表。事实上,如果 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 个号码:

Index Scan using index_phone_numbers_on_hashed_phone_number on phone_numbers (cost=0.41..8.43 rows=1 width=4)
  Index Cond: ((hashed_phone_number)::text = 'bebd43a6eb29b2fda3bcb63dcc7ffaf5433e78660ccd1a495c1180a3eaaf6b6a'::text)
  Filter: (NOT ghost)"

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 (...)
  Filter: (NOT ghost)"

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

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 条记录,没有多大意义。

任何想法可能会发生什么,我是否可以做任何事情来改进它?


更新:将覆盖索引中列的顺序更改为先是 ghost,然后是 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 (...)

最佳答案

索引的选择基于优化器所说的查询的最佳解决方案。 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 documentation很好地解释了如何使用复合索引。

本质上,发生的事情是 Postgres 需要对 in 列表中的每个元素进行索引查找。这可能会因使用字符串而变得复杂——因为归类/编码会影响比较。最终,Postgres 决定其他方法更有效。如果您将 ghost 放在首位,那么它只会跳转到索引的正确部分并在那里找到它需要的行。

关于sql - 为什么 PostgreSQL 不*仅*根据其 IN() 子句的内容在此查询中使用覆盖索引?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30457508/

相关文章:

java - #0001 : java. sql.SQLException : Parameter index out of range (1 > number of parameters, 即 0)

php - MYSQL或PHP中如何使用SQL LIKE语句过滤过于相似的结果

mysql - 如何在 MYSQL 中通过另一列选择具有 MAX(列值)、PARTITION 的行?

xml - 如何使用 xpath 表达式在 PostgreSQL 中的 XML 列上创建索引?

python - 在特定索引上启动 iterrows() 循环

mysql类别树搜索

sql - Postgresql - 从对象数组中提取字段到文本数组

linux - 克隆 PostgreSQL 数据库

sql - SELECT 请求中的多表关系

python - 搜索、计数和添加 - Python