即使未定义 varchar_pattern_ops 且未使用 "C" 区域设置,也使用 LIKE 查询执行 Postgresql 索引扫描

Postgresql Index scan performed with LIKE query even though no varchar_pattern_ops is defined and not using "C" Locale

鉴于以下 table:

CREATE UNLOGGED TABLE table (
  col1       VARCHAR(64)   NOT NULL,
  col2       VARCHAR(255)      NOT NULL,
  CONSTRAINT table_pk PRIMARY KEY (col1,col2)
);

以及以下 SHOW LC_COLLATE; 结果:

lc_collate
en_US.utf8

以下查询执行索引扫描:

SELECT * FROM table WHERE col1 ='val1' AND col2 LIKE ('prefix%') LIMIT 2;

从它的 EXPLAIN ANALYZE 输出可以看出:

Limit  (cost=0.14..8.16 rows=1 width=662) (actual time=0.018..0.022 rows=0 loops=1)
  ->  Index Only Scan using table_pk on "table"  (cost=0.14..8.16 rows=1 width=662) (actual time=0.008..0.012 rows=0 loops=1)
        Index Cond: (col1 = 'val1'::text)
        Filter: ((col2)::text ~~ 'prefix%'::text)
        Heap Fetches: 0
Planning time: 4.562 ms
Execution time: 0.068 ms

问题是包含 LIKE 运算符的查询如何执行索引扫描?

根据 this article as well as this blog 使用 LIKE 运算符但没有“C”语言环境并且没有在索引上设置 varchar_pattern_ops 的情况,应该无法执行索引扫描。

我 运行 在 postgres:9.6

的 docker 图片上

仔细看会发现只有col1作为“索引条件”,即col2上的条件不扫描索引。 col2 上的条件在“过滤器”中。

所以一切都如你所愿。这次索引扫描的事件顺序是这样的:

  1. 获取具有 col1 = 'val1'

    的下一个索引条目
  2. 获取该条目的 table 行

  3. 检查table行是否可见并满足col2 LIKE 'prefix%';如果是,return 那一行

  4. 继续扫描步骤 1 中的索引,直到完成

为了帮助您解决潜在问题:如果您可以使用非标准运算符定义主键,那就太好了class,这样主键索引就可以完美地支持您的查询,但那是不可能。这些是您的选择:

  • 删除主键并用 text_pattern_ops 定义唯一索引。如果两列都是 NOT NULL,那也一样好。

  • 单独在 col2 上创建一个 text_pattern_ops 索引。既可以单独使用,也可以和主键索引结合使用。

  • 在两列上创建第二个索引。