【问题标题】:PostgreSQL LIKE query performance variationsPostgreSQL LIKE 查询性能变化
【发布时间】:2010-12-06 16:57:45
【问题描述】:

我发现LIKE 对我数据库中特定表的查询的响应时间变化很大。有时我会在 200-400 毫秒内得到结果(非常可接受),但有时可能需要 30 秒才能返回结果。

我知道LIKE 查询非常耗费资源,但我只是不明白为什么响应时间会有如此大的差异。我在owner1 字段上建立了一个btree 索引,但我认为它对LIKE 查询没有帮助。有人有什么想法吗?

示例 SQL:

SELECT gid, owner1 FORM parcels
WHERE owner1 ILIKE '%someones name%' LIMIT 10

我也试过了:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%') LIMIT 10

还有:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('someones name%') LIMIT 10

结果相似。
表行数:约 95,000。

【问题讨论】:

    标签: postgresql indexing pattern-matching query-optimization sql-like


    【解决方案1】:

    FTS 不支持LIKE

    previously accepted answer 不正确。 Full Text Search 及其全文索引对于 LIKE 运算符根本不是,它有自己的运算符并且不适用于任意字符串。它基于字典和词干对单词进行操作。它确实支持词的前缀匹配,但不支持LIKE运算符:

    LIKE 的三元组索引

    安装附加模块pg_trgm,它为GIN and GiST trigram indexes 提供运算符类以支持所有LIKEILIKE 模式,而不仅仅是左锚模式:

    示例索引:

    CREATE INDEX tbl_col_gin_trgm_idx  ON tbl USING <b>gin</b>  (col <b>gin_trgm_ops</b>);

    或者:

    CREATE INDEX tbl_col_gist_trgm_idx ON tbl USING <b>gist</b> (col <b>gist_trgm_ops</b>);

    查询示例:

    SELECT * FROM tbl WHERE col LIKE '%foo%';   -- leading wildcard
    SELECT * FROM tbl WHERE col ILIKE '%foo%';  -- works case insensitively as well

    三字?较短的字符串呢?

    索引值中少于 3 个字母的单词仍然有效。 The manual:

    每个单词被认为有两个空格前缀和一个空格 在确定字符串中包含的三元组时添加后缀。

    以及少于 3 个字母的搜索模式? The manual:

    对于LIKE 和正则表达式搜索,请记住 没有可提取三元组的模式将退化为全索引扫描。

    意思是,索引/位图索引扫描仍然有效(准备好的语句的查询计划不会中断),它只是不会为您带来更好的性能。通常没有大的损失,因为 1 或 2 字母字符串几乎没有选择性(超过基础表匹配的百分之几),并且索引支持一开始不会提高性能,因为全表扫描更快。

    text_pattern_opsCOLLATE "C" 用于前缀匹配

    更新

    从 Postgres 9.1 开始,COLLATE "C" 更好。见:

    原答案

    对于仅左锚定模式(没有前导通配符),您可以通过合适的operator class 获得最佳btree 索引:text_pattern_opsvarchar_pattern_ops。这两个标准 Postgres 的内置功能,不需要额外的模块。性能相似,但索引要小得多。

    示例索引:

    CREATE INDEX tbl_col_text_pattern_ops_idx ON tbl(col <b>text_pattern_ops</b>);

    查询示例:

    SELECT * FROM tbl WHERE col LIKE <b>'foo%'</b>;  -- no leading wildcard

    或者,如果您应该使用 'C' 语言环境(实际上是 no 语言环境)运行数据库,那么所有内容都按照无论如何都要以字节顺序和具有默认运算符类的普通 btree 索引来完成这项工作。


    进一步阅读

    【讨论】:

    • 在 500K 行的表中没有前导通配符,带有 gin_trgm_ops 的 gin 索引似乎比 btree 快 10 倍
    • @nicolas:比较取决于许多变量。密钥长度、数据分布、模式长度、可能的仅索引扫描……最重要的是:Postgres 版本。 GIN 指数在 pg 9.4 和 9.5 中得到了显着改善。新版本的 pg_trgm(将与 pg 9.6 一起发布)将带来更多改进。
    • 如果我得到了正确的文档,pg_trgm 您需要长度至少为 3 个字符的查询字符串,例如 fo% 不会命中索引,而是进行扫描。需要注意的一点。
    • @TuukkaMustonen:好点。好吧,(位图)索引扫描仍然有效,它们只是不会为您带来更好的性能。我在上面添加了一些说明。
    【解决方案2】:

    可能快速的模式是具有大小写敏感的锚定模式,例如可以使用索引。即匹配字符串的开头没有通配符,因此执行程序可以使用索引范围扫描。 (the relevant comment in the docs is here)lower 和 ilike 也会失去使用索引的能力,除非您专门为此目的创建索引(请参阅functional indexes)。

    如果您想在字段中间搜索字符串,您应该查看full texttrigram indexes。第一个在 Postgres 核心中,另一个在 contrib 模块中可用。

    【讨论】:

    • 我没有想过在字段的小写值上创建索引。这样我可以在查询之前在后端将查询文本转换为小写。
    【解决方案3】:

    您可以在 PostgreSQL 中安装 Wildspeed,这是一种不同类型的索引。 Wildspeed 确实适用于 %word% 通配符,没问题。缺点是索引的大小,这可能很大,非常大。

    【讨论】:

      【解决方案4】:

      我最近遇到了一个包含 200000 条记录的表的类似问题,我需要重复 LIKE 查询。就我而言,正在搜索的字符串是固定的。其他领域各不相同。因为那样,我能够重写:

      SELECT owner1 FROM parcels
      WHERE lower(owner1) LIKE lower('%someones name%');
      

      作为

      CREATE INDEX ix_parcels ON parcels(position(lower('someones name') in lower(owner1)));
      
      SELECT owner1 FROM parcels
      WHERE position(lower('someones name') in lower(owner1)) > 0;
      

      当查询快速返回并验证索引正在与EXPLAIN ANALYZE 一起使用时,我很高兴:

       Bitmap Heap Scan on parcels  (cost=7.66..25.59 rows=453 width=32) (actual time=0.006..0.006 rows=0 loops=1)
         Recheck Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
         ->  Bitmap Index Scan on ix_parcels  (cost=0.00..7.55 rows=453 width=0) (actual time=0.004..0.004 rows=0 loops=1)
               Index Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
       Planning time: 0.075 ms
       Execution time: 0.025 ms
      

      【讨论】:

        【解决方案5】:

        当您在具有 LIKE、ILIKE、upper、lower 等函数的列上使用子句时。那么 postgres 不会考虑您的正常索引。它将通过每一行对表进行全面扫描,因此速度会很慢。

        正确的方法是根据您的查询创建一个新索引。例如,如果我想匹配不区分大小写的列,而我的列是 varchar。然后你就可以这样做了。

        create index ix_tblname_col_upper on tblname (UPPER(col) varchar_pattern_ops);
        

        同样,如果您的专栏是文本,那么您也可以这样做

        create index ix_tblname_col_upper on tblname (UPPER(col) text_pattern_ops);
        

        同样,您可以将函数上限更改为您想要的任何其他函数。

        【讨论】:

          【解决方案6】:

          请执行下面提到的查询以提高 postgresql 中的 LIKE 查询性能。 为更大的表创建这样的索引:

          CREATE INDEX <indexname> ON <tablename> USING btree (<fieldname> text_pattern_ops)
          

          【讨论】:

          • 这仅适用于模式不以通配符开头的情况 - 在这种情况下,前两个示例查询都以通配符开头。
          【解决方案7】:

          对于它的价值,Django ORM 倾向于对所有 LIKE 查询使用 UPPER(text) 以使其不区分大小写,

          UPPER(column::text) 上添加索引大大加快了我的系统速度,这与其他任何事情都不同。

          就前导百分比而言,是的,它不会使用索引。有关详细说明,请参阅此博客:

          https://use-the-index-luke.com/sql/where-clause/searching-for-ranges/like-performance-tuning

          【讨论】:

            【解决方案8】:

            您的like 查询可能无法使用您创建的索引,因为:

            1) 您的 LIKE 标准以通配符开头。

            2) 您使用了符合 LIKE 标准的函数。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2021-12-06
              • 2021-02-20
              • 2014-10-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-10-30
              • 1970-01-01
              相关资源
              最近更新 更多