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

Tit*_*ter 2 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'))
Run Code Online (Sandbox Code Playgroud)

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

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()
Run Code Online (Sandbox Code Playgroud)

结果:

“对 profile_user 进行顺序扫描(成本=0.00..81.75 行=1 宽度=316)\n 过滤器:(upper((to_tsvector(COALESCE((name)::text, ''::text)))::text) ~~'%someuser%'::text)"

如何使索引工作?

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

Pao*_*rre 7

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

  • 更新搜索向量字段而不过滤您的对象
  • SearchVector使用 an 来执行带注释的搜索查询,icontains而不是使用 theSearchVectorField和您的GinIndex

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

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"))
Run Code Online (Sandbox Code Playgroud)

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

在这里您可以看到我在 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
Run Code Online (Sandbox Code Playgroud)

下面是我在 python shell 中执行的代码,ProfileUser以使用SearchVectorField模型GINindex进行搜索。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))"
Run Code Online (Sandbox Code Playgroud)

如果您想了解有关Django 和 PostgreSQL 全文搜索的更多信息,您可以阅读有关全文搜索的官方文档。

如果您对这方面的外部文章感兴趣,这是我写的一篇: Full-Text Search in Django with PostgreSQL