sql - 如果我使用包含 5 个字符的搜索文本,Postgresql 不会使用索引。有 6 个就可以了。为什么?

标签 sql postgresql performance indexing postgresql-13

我正在使用 Postgresql 13。

对于此查询,PostgreSQL 使用索引:

SELECT *
FROM
    "players"
WHERE team_id = 3
    AND (
    code ILIKE 'lushij'
    OR
    REPLACE(lastname||firstname,' ','') ILIKE '%lushij%'
    OR REPLACE(firstname||lastname,' ','') ILIKE '%lushij%'
    OR personal_info->>'houses' ILIKE '%lushij%'
    )
LIMIT 15
Limit  (cost=333.01..385.77 rows=15 width=360)
  ->  Bitmap Heap Scan on players  (cost=333.01..4061.29 rows=1060 width=360)
        Recheck Cond: ((code ~~* 'lushij'::text) OR (replace((lastname || firstname), ' '::text, ''::text) ~~* '%lushij%'::text) OR (replace((firstname || lastname), ' '::text, ''::text) ~~* '%lushij%'::text) OR ((personal_info ->> 'houses'::text) ~~* '%lushij%'::text))
        Filter: (team_id = 3)
        ->  BitmapOr  (cost=333.01..333.01 rows=1060 width=0)
              ->  Bitmap Index Scan on players_code_trgm  (cost=0.00..116.75 rows=100 width=0)
                    Index Cond: (code ~~* 'lushij'::text)
              ->  Bitmap Index Scan on players_replace_last_first_name_trgm  (cost=0.00..66.40 rows=320 width=0)
                    Index Cond: (replace((lastname || firstname), ' '::text, ''::text) ~~* '%lushij%'::text)
              ->  Bitmap Index Scan on players_replace_first_last_name_trgm  (cost=0.00..66.40 rows=320 width=0)
                    Index Cond: (replace((firstname || lastname), ' '::text, ''::text) ~~* '%lushij%'::text)
              ->  Bitmap Index Scan on players_personal_info_houses_trgm_idx  (cost=0.00..82.40 rows=320 width=0)
                    Index Cond: ((personal_info ->> 'houses'::text) ~~* '%lushij%'::text)

对于相同的查询,但搜索文本少了一个字符(从 lushijlushi)未使用索引:

SELECT *
FROM
    "players"
WHERE team_id = 3
    AND (
    code ILIKE 'lushi'
    OR
    REPLACE(lastname||firstname,' ','') ILIKE '%lushi%'
    OR REPLACE(firstname||lastname,' ','') ILIKE '%lushi%'
    OR personal_info->>'houses' ILIKE '%lushi%'
    )
LIMIT 15
Limit  (cost=0.00..235.65 rows=15 width=360)
  ->  Seq Scan on players  (cost=0.00..76853.53 rows=4892 width=360)
        Filter: ((team_id = 3) AND ((code ~~* 'lushi'::text) OR (replace((lastname || firstname), ' '::text, ''::text) ~~* '%lushi%'::text) OR (replace((firstname || lastname), ' '::text, ''::text) ~~* '%lushi%'::text) OR ((personal_info ->> 'houses'::text) ~~* '%lushi%'::text)))

为什么?

更新:

如果我注释 LIMIT 15 行,则使用索引。


结构如下:

玩家表结构
-- ----------------------------
-- Table structure for players
-- ----------------------------
DROP TABLE IF EXISTS "public"."players";
CREATE TABLE "public"."players" (
  "id" int8 NOT NULL DEFAULT nextval('players_id_seq'::regclass),
  "created_at" timestamptz(6) NOT NULL DEFAULT now(),
  "updated_at" timestamptz(6),
  "team_id" int8 NOT NULL,
  "firstname" text COLLATE "pg_catalog"."default",
  "lastname" text COLLATE "pg_catalog"."default",
  "code" text COLLATE "pg_catalog"."default",
  "personal_info" jsonb
)
;

-- ----------------------------
-- Indexes structure for table players
-- ----------------------------
CREATE INDEX "players_personal_info_houses_trgm_idx" ON "public"."players" USING gin (
  (personal_info ->> 'houses'::text) COLLATE "pg_catalog"."default" "public"."gin_trgm_ops"
);
CREATE INDEX "players_code_trgm" ON "public"."players" USING gin (
  "code" COLLATE "pg_catalog"."default" "public"."gin_trgm_ops"
);
CREATE INDEX "players_lower_code" ON "public"."players" USING btree (
  lower(code) COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST
);
CREATE INDEX "players_replace_first_last_name_trgm" ON "public"."players" USING gin (
  replace(firstname || lastname, ' '::text, ''::text) COLLATE "pg_catalog"."default" "public"."gin_trgm_ops"
);
CREATE INDEX "players_replace_last_first_name_trgm" ON "public"."players" USING gin (
  replace(lastname || firstname, ' '::text, ''::text) COLLATE "pg_catalog"."default" "public"."gin_trgm_ops"
);

-- ----------------------------
-- Primary Key structure for table players
-- ----------------------------
ALTER TABLE "public"."players" ADD CONSTRAINT "players_pkey" PRIMARY KEY ("id");

-- ----------------------------
-- Foreign Keys structure for table players
-- ----------------------------
ALTER TABLE "public"."players" ADD CONSTRAINT "players_team_id_fkey" FOREIGN KEY ("team_id") REFERENCES "public"."teams" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION;

最佳答案

好的..这是基于我对 SQL Server 和 SQL 的一般了解,但它可能也适用于此。

首先...因为您正在执行 SELECT *,所以它需要在某个时候转到聚簇索引。

使用非聚集索引(如果使用的话)是为了识别相关的行,然后它会 1 对 1 地挑选出那些行(嵌套循环连接,或有时称为索引搜索/扫描 + 键查找)。

如果行太多,这实际上是低效的 - 你最终会做更多的读取等,而不是简单地读取整个表。

减少 LIKE 过滤器的长度会增加基数估计,例如,增加过滤器预期在查询计划器/优化器中匹配的行数。

我猜 SQL 引擎进行了猜测(包括索引/数据的统计信息)并确定简单地从聚集索引中读取所有数据可能比确定行并逐个读取它们更有效-1.


OP 更新后的更新取消限制。

好吧……再一次,这取决于它根据过滤器估计存在多少行。

想象一下,如果您在原始查询中执行 ILIKE '%e%'。每第二行可能与此匹配。由于您没有排序,它只需要读取(比如说)聚集索引的前 30 行,它就会得到您的答案。再一次,查询规划器/优化器可能会得出结论,这将是获得这些的最有效方法。

但是,如果没有限制,它将需要读取所有行才能获得所有结果。

  • 对于 %e%,只进行完整的聚簇索引扫描可能更有效,因为它期望匹配很多行
  • 对于更复杂/选择性的过滤,首先搜索索引(然后直接查找聚集索引中的数据)通常更有效

关于sql - 如果我使用包含 5 个字符的搜索文本,Postgresql 不会使用索引。有 6 个就可以了。为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64309320/

相关文章:

sql - 错误: syntax error at or near "." LINE 4: ON like. takerId =frame.likeId;

Javascript 创建元素问题

java - 每 5 小时创建一个表

sql - hive 计数和计数不同不正确

postgresql - 如何恢复/倒带我的 PostgreSQL 数据库

node.js - Sequelize - 在 where 子句中的列上运行

sql - 删除超过一定长度的字符串中的空格

java - 使用准备好的语句的动态列名 + 包含变量的 sql 查询

c# - 计算对数算法的时间

c# - 我怎样才能改进这个性能不佳、糟糕的串行端口代码?