【问题标题】:Improving query speed: simple SELECT with LIKE提高查询速度:带 LIKE 的简单 SELECT
【发布时间】:2016-04-27 22:22:55
【问题描述】:

我继承了一个在 django 1.5 中运行的大型遗留代码库,我当前的任务是加速网站的某个部分,该部分需要 ~1 分钟 才能加载。

我做了一个应用程序的配置文件并得到了这个:

罪魁祸首是以下查询(为简洁起见):

SELECT COUNT(*) FROM "entities_entity" WHERE (
  "entities_entity"."date_filed" <= '2016-01-21' AND (
    UPPER("entities_entity"."entity_city_state_zip"::text) LIKE UPPER('%Atherton%') OR
    UPPER("entities_entity"."entity_city_state_zip"::text) LIKE UPPER('%Berkeley%') OR
    -- 34 more of these
    UPPER("entities_entity"."agent_city_state_zip"::text) LIKE UPPER('%Atherton%') OR
    UPPER("entities_entity"."agent_city_state_zip"::text) LIKE UPPER('%Berkeley%') OR
    -- 34 more of these
  )
)

这基本上包括对两个字段entity_city_state_zipagent_city_state_zip的大相似查询,它们是character varying(200) | not null字段。

该查询被执行 两次 (!),每次花费 18814.02ms,再一次将 COUNT 替换为 SELECT 占用额外的时间20216.49(我要缓存COUNT的结果)

解释如下:

Aggregate  (cost=175867.33..175867.34 rows=1 width=0) (actual time=17841.502..17841.502 rows=1 loops=1)
  ->  Seq Scan on entities_entity  (cost=0.00..175858.95 rows=3351 width=0) (actual time=0.849..17818.551 rows=145075 loops=1)
        Filter: ((date_filed <= '2016-01-21'::date) AND ((upper((entity_city_state_zip)::text) ~~ '%ATHERTON%'::text) OR (upper((entity_city_state_zip)::text) ~~ '%BERKELEY%'::text) (..skipped..) OR (upper((agent_city_state_zip)::text) ~~ '%ATHERTON%'::text) OR (upper((agent_city_state_zip)::text) ~~ '%BERKELEY%'::text) OR (upper((agent_city_state_zip)::text) ~~ '%BURLINGAME%'::text) ))
        Rows Removed by Filter: 310249
Planning time: 2.110 ms
Execution time: 17841.944 ms

我尝试过在entity_city_state_zipagent_city_state_zip 上使用索引,使用各种组合,例如:

CREATE INDEX ON entities_entity (upper(entity_city_state_zip));
CREATE INDEX ON entities_entity (upper(agent_city_state_zip));

或使用varchar_pattern_ops,没有运气。

服务器正在使用这样的东西:

qs = queryset.filter(Q(entity_city_state_zip__icontains = all_city_list) |
                     Q(agent_city_state_zip__icontains = all_city_list))

生成该查询。

我不知道还能尝试什么,

谢谢!

【问题讨论】:

  • LIKE'%...' 开头的查询不会使用任何btree 索引(包括xxx_pattern_ops)。如果模式在开始时匹配,则仅选择这些索引。 (例如col LIKE 'XXX%'col ~ '^XXX')。你可以试试pg_trgm modulewhich provides a suitable index for you。 (您可以使用ilike 代替likelower()/upper() 调用)。
  • @pozs 我不知道!我试试看
  • 我至少想知道Seq Scan 有什么影响,以及是否可以替换索引扫描。看看set enable_seqscan=false 对计划有什么影响。数据库是否在 SSD 上运行?
  • @AndrewRegan 是的,测试是在我的具有 SSD(生产中)的 Mac 上进行的。将 enable_seqscan 设置为 false 产生:Aggregate (cost=175867.33..175867.34 rows=1 width=0) (actual time=20916.498..20916.498 rows=1 loops=1) -&gt; Seq Scan on entities_entity (cost=0.00..175858.95 rows=3351 width=0) (actual time=0.192..20871.984 rows=145075 loops=1)
  • 嗯,好的,这没有效果,这表明规划器没有它可能使用的替代索引。我想这就是我的下一个建议 - 将 random_page_cost 降低到 1.1 左右,以告诉规划者它足够快以随机访问快速磁盘上的潜在索引而不是 seq 扫描 - 冗余。

标签: django performance postgresql


【解决方案1】:

我在 Pluralsight 中观看了一门课程,该课程解决了一个非常相似的问题。该课程是“面向 .NET 开发人员的 Postgres”,位于“简单 SQL 的乐趣”、“全文搜索”部分。

用你的例子总结他们的解决方案:

在您的表中创建一个新列,将您的 entity_city_state_zip 表示为 tsvector:

create table entities_entity (
  date_filed date,
  entity_city_state_zip text,
  csz_search tsvector not null   -- add this column
);

最初您可能必须将其设为可为空,然后填充数据并使其不可为空。

update entities_entity
set csz_search = to_tsvector (entity_city_state_zip);

接下来,创建一个触发器,该触发器将在任何时候添加或修改记录时填充新字段:

create trigger entities_insert_update
before insert or update on entities_entity
for each row execute procedure
tsvector_update_trigger(csz_search,'pg_catalog.english',entity_city_state_zip);

您的搜索查询现在可以查询 tsvector 字段而不是 city/state/zip 字段:

select * from entities_entity
where csz_search @@ to_tsquery('Atherton')

对此感兴趣的一些注意事项:

  • to_tsquery,如果你还没有使用它,它比上面的例子更复杂。它允许和条件、部分匹配等
  • 它也不区分大小写,因此无需执行查询中的upper 函数

作为最后一步,在 tsquery 字段上放置一个GIN 索引:

create index entities_entity_ix1 on entities_entity
using gin(csz_search);

如果我对课程的理解正确,这应该会使您的查询顺利进行,并且它将克服 btree 索引无法处理 like '% 查询的问题。

以下是此类查询的解释计划:

Bitmap Heap Scan on entities_entity  (cost=56.16..1204.78 rows=505 width=81)
  Recheck Cond: (csz_search @@ to_tsquery('Atherton'::text))
  ->  Bitmap Index Scan on entities_entity_ix1  (cost=0.00..56.04 rows=505 width=0)
        Index Cond: (csz_search @@ to_tsquery('Atherton'::text))

【讨论】:

  • 真是太棒了。我对大约 2,000,000 行数据进行了一些快速测试,这种方法大约需要 300 毫秒,而传统查询大约需要 2.4 秒。对于较大数据集的嵌套“或”查询,我敢打赌,差异会更加显着。
【解决方案2】:

我认为 "multiple LIKE" 和 UPPER("entities_entity ...

中存在问题

你可以使用:

WHERE entities_entity.entity_city_state_zip SIMILAR TO '%Atherton%|%Berkeley%'

或者是这样的:

WHERE entities_entity.entity_city_state_zip LIKE ANY(ARRAY['%Atherton%', '%Berkeley%'])


已编辑

关于 Django 中的原始 SQL 查询:

  1. https://docs.djangoproject.com/es/1.9/topics/db/sql/
  2. How do I execute raw SQL in a django migration

问候

【讨论】:

猜你喜欢
  • 2012-10-25
  • 2012-03-31
  • 2012-01-13
  • 1970-01-01
  • 1970-01-01
  • 2011-06-14
  • 1970-01-01
  • 2019-09-27
  • 1970-01-01
相关资源
最近更新 更多