我使用的是 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
上执行搜索查询,而不是将SearchVectorField
与Gindex
一起使用
我更新了一些你的模型代码:
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/