Django:JSONField + 全文搜索 + 索引 -> 序列扫描。如何配置索引工作?

标签 django postgresql indexing full-text-search

我使用的是 Django 2.2 和 PostgreSQL 12。

这是我的模型:

from django.contrib.postgres.search import SearchVectorField, SearchVector
from django.contrib.postgres.fields import JSONField

class ProfileUser(models.Model):
    name = JSONField()

    search_vector = SearchVectorField(null=True)

    class Meta:
        indexes = [
            GinIndex(fields=['search_vector'], name='user_full_name_gin_idx')
        ]

    def save(self, *args, **kwargs):
        super(ProfileUser, self).save(*args, **kwargs)
        ProfileUser.objects.update(search_vector=SearchVector('name'))

我在这里创建一个新用户并试图找到它:

from apps.profiles.models import ProfileUser
from django.contrib.postgres.search import SearchVector

ProfileUser.objects.create(name=[{'name': 'SomeUser', 'lang': 'en'}])
ProfileUser.objects.annotate(search=SearchVector('name')).filter(search__icontains='someuser').explain()

结果:

"Seq Scan on profiles_user (cost=0.00..81.75 rows=1 width=316)\n Filter: (upper((to_tsvector(COALESCE((name)::text, ''::text)))::text) ~~ '%someuser%'::text)"

如何使索引工作?

编辑: 作为对@ivissani 评论的回应,我添加了 5000 个用户并尝试了 .filter(search__icontains='someuser').filter(search_vector__icontains='someuser') - 同样的故事-> 序列扫描

最佳答案

我认为您没有完全使用全文搜索 Django 模块。 我在您的代码中看到的主要问题是:

  • 在不过滤对象的情况下更新搜索向量场
  • 使用 icontains 在带注释的 SearchVector 上执行搜索查询,而不是将 SearchVectorFieldGindex 一起使用

我更新了一些你的模型代码:

from django.contrib.postgres.fields import JSONField
from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import SearchVectorField, SearchVector
from django.db import models
from django.db.models import F


class ProfileUser(models.Model):
    name = JSONField()
    search_vector = SearchVectorField(null=True)

    class Meta:
        indexes = [GinIndex(fields=["search_vector"], name="user_full_name_gin_idx")]

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        ProfileUser.objects.annotate(search_vector_name=SearchVector("name")).filter(
            id=self.id
        ).update(search_vector=F("search_vector_name"))

如您所见,我在 save 方法中添加了注释和过滤器,以仅更新模型的搜索向量场(您可以在我的 another answer 中找到此用法的另一个示例)

在这里您可以看到我在 python shell 中用来创建新的 ProfileUser 的代码。 你可以看到在save 方法中执行的两个 SQL 查询:

>>> from users.models import ProfileUser
>>> ProfileUser.objects.create(name=[{'name': 'SomeUser', 'lang': 'en'}])

INSERT INTO "users_profileuser" ("name", "search_vector")
VALUES ('[{"name": "SomeUser", "lang": "en"}]', NULL) RETURNING "users_profileuser"."id"

UPDATE "users_profileuser"
SET "search_vector" = to_tsvector(COALESCE(("users_profileuser"."name")::text, ''))
WHERE "users_profileuser"."id" = 1

下面是我在 python shell 中执行的代码,用于使用模型的 GINindex 使用 SearchVectorField 搜索 ProfileUser。 可以在索引上看到Index Scan:

>>> from django.contrib.postgres.search import SearchQuery
>>> ProfileUser.objects.filter(search_vector=SearchQuery('someuser')).explain()

EXPLAIN
SELECT "users_profileuser"."id",
    "users_profileuser"."name",
    "users_profileuser"."search_vector"
FROM "users_profileuser"
WHERE "users_profileuser"."search_vector" @@ (plainto_tsquery('someuser')) = true

"Bitmap Heap Scan on users_profileuser  (cost=12.28..21.74 rows=4 width=68)
    Recheck Cond: (search_vector @@ plainto_tsquery('someuser'::text))
    ->  Bitmap Index Scan on user_full_name_gin_idx  (cost=0.00..12.28 rows=4 width=0)
            Index Cond: (search_vector @@ plainto_tsquery('someuser'::text))"

如果您想了解更多Full-text Search with Django and PostgreSQL您可以阅读有关 full-text search 的官方文档.

如果您对关于此的外部文章感兴趣,那就是我写的一篇: Full-Text Search in Django with PostgreSQL

关于Django:JSONField + 全文搜索 + 索引 -> 序列扫描。如何配置索引工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59439375/

相关文章:

mysql - 使用索引语义确定 MySql 索引列中的字段是否为数字

python - 如何在 Django 查询集过滤中执行不等于?

Django:如何在外键模型字段上定义动态初始值

python - 无法为 Django 和 Mysql 启动 docker 构建

python - 如何测试 POST 请求的 URL 参数?

sql - Postgres GROUP BY,然后排序

mysql - SQL 挑战 - 显示具有特定列值的 N(1、X 或全部)行

sql-server - 主键在sql server中总是有索引吗?

macos - psql : could not connect to server: No such file or directory (Mac OS X)

MySQL:外键