具体化されたビューによる高速読み込み
28112 ワード
このブログは、PostgreSQLのビューのGISTとDJangoでの統合方法を提供します.
PostgreSQLのビューは非常に便利で、ビューを複雑なクエリをラッピング(抽象化)し、名前を割り当てることを考えます.
それらは、ビューを使用するいくつかの便利です ビューは複雑さを隠す 複数のテーブルを集約したり結合したりする複雑なロジックを必要とするクエリがある場合は、そのロジックをすべてビューに変換できます
そして、それが通常のテーブルであるかのように、それからデータをretreiveします. ビューはセキュリティに使用できます あなたが特定の列/行の機密データを持つテーブルを持っていると想像してください、そして、あなたはそのデータを公開することなくユーザーにアクセスをしたいです、見解は我々に必要なデータだけをラップさせて、下にあるテーブルの代わりにその見方に許可を与えさせます.
すべてのSQLデータベースPostgresと同様に2種類のビューがあります 見解 実体化された見解 見解と具体化された見解のわずかな違いは、ここにあります
見解
実体化された見解
ビューはディスクに保存されません.
材料はディスクに保存されます.
ビューからデータを読み込むたびに、基になるテーブルからデータが読み込まれます.
具体化されたビューは独自のテーブルを持っているので、具体化されたビューからの読みは直接テーブルから読みますが、基になるテーブルから読みます.
具体化された見解を実行しようとしましょう.ブログサイトの次のシナリオを考えてください.典型的なブログサイトモデルが含まれます ブログ表 カテゴリー表 タグテーブル それらの間の関係が非常に簡単であるところでは、ブログはカテゴリーに外部キーを持っていて、タグとの多くの多くの関係を持ちます.等価なDjangoモデルはこのようになります.
我々が具体化された見解をどのようにつくることができるか見ましょう
すべてのコードはこちらhttps://github.com/sreevardhanreddi/django-materialized-views
SQLが行われた今、私たちはDjangoとの統合を通して行きましょう
このコマンドを使用して空の移行ファイルを作成します
このモデルの後、具体化されたビューを反映するモデル
これが完了すると、それはQuery
これはもともとはこちらhttps://sreevardhanreddi.github.io/
PostgreSQLのビューは非常に便利で、ビューを複雑なクエリをラッピング(抽象化)し、名前を割り当てることを考えます.
それらは、ビューを使用するいくつかの便利です
そして、それが通常のテーブルであるかのように、それからデータをretreiveします.
すべてのSQLデータベースPostgresと同様に2種類のビューがあります
見解
実体化された見解
ビューはディスクに保存されません.
材料はディスクに保存されます.
ビューからデータを読み込むたびに、基になるテーブルからデータが読み込まれます.
具体化されたビューは独自のテーブルを持っているので、具体化されたビューからの読みは直接テーブルから読みますが、基になるテーブルから読みます.
具体化された見解を実行しようとしましょう.ブログサイトの次のシナリオを考えてください.典型的なブログサイトモデルが含まれます
class Tags(models.Model):
name = models.CharField(max_length=200, unique=True)
class Meta:
verbose_name = "Tag"
verbose_name_plural = "Tags"
db_table = "tags"
def __str__(self):
return self.name
class Category(models.Model):
name = models.CharField(max_length=200, unique=True)
class Meta:
verbose_name = "Category"
verbose_name_plural = "Categories"
db_table = "categories"
def __str__(self):
return self.name
class Blog(models.Model):
title = models.CharField(max_length=200, unique=True)
content = models.TextField()
created_at = models.DateTimeField(auto_now=True)
is_published = models.BooleanField(default=True)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
tags = models.ManyToManyField(Tags)
class Meta:
verbose_name = "Blog"
verbose_name_plural = "Blogs"
db_table = "blogs"
def __str__(self):
return self.title
すべての3つのテーブルをクエリし、すべての3つのテーブルからデータを取得する必要がある状況を仮定します.これを実現するために、すべてのテーブルに参加する必要があります
queryset = Blog.objects.select_related("category").prefetch_related("tags").all()
# the resulting SQL query generated by Django ORM is
SELECT "blogs"."id",
"blogs"."title",
"blogs"."content",
"blogs"."created_at",
"blogs"."is_published",
"blogs"."category_id",
"categories"."id",
"categories"."name"
FROM "blogs"
LEFT OUTER JOIN "categories"
ON ("blogs"."category_id" = "categories"."id")
LIMIT 21
SELECT ("blogs_tags"."blog_id") AS "_prefetch_related_val_blog_id",
"tags"."id",
"tags"."name"
FROM "tags"
INNER JOIN "blogs_tags"
ON ("tags"."id" = "blogs_tags"."tags_id")
WHERE "blogs_tags"."blog_id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21)
ここで2つのクエリを実際に実行していることに注意してください.
SELECT b.id,
b.title AS blog_title,
b.created_at AS blog_created_at,
b.is_published AS blog_is_published,
b.category_id AS blog_category_id,
b.content AS blog_content,
c.id AS category_id,
c.name AS category_name,
bt.blog_id AS blog_tag_blog_id,
count(t.id) AS tag_count,
jsonb_agg(jsonb_build_object('tag_id', t.id, 'tag_name', t.name)) AS tag_json
FROM blogs b
LEFT JOIN categories c ON b.category_id = c.id
LEFT JOIN blogs_tags bt ON b.id = bt.blog_id
LEFT JOIN tags t ON bt.tags_id = t.id
GROUP BY b.id,
c.id,
bt.blog_id
ORDER BY b.id;
クエリが非常に複雑であることがわかりますので、データにアクセスする必要があるたびにクエリを実行する必要があります.ありがたいことに、簡単な方法でそれを簡単にすることができます.我々が具体化された見解をどのようにつくることができるか見ましょう
CREATE materialized VIEW mv_blogs_with_categories_and_tags_combined AS
SELECT b.id,
b.title AS blog_title,
b.content AS blog_content,
b.created_at AS blog_created_at,
b.is_published AS blog_is_published,
c.id AS category_id,
c.name AS category_name,
count(t.id) AS tag_count,
jsonb_agg(jsonb_build_object('tag_id', t.id, 'tag_name', t.name)) AS tags
FROM blogs b
LEFT JOIN categories c ON b.category_id = c.id
LEFT JOIN blogs_tags bt ON b.id = bt.blog_id
LEFT JOIN tags t ON bt.tags_id = t.id
GROUP BY b.id,
c.id,
bt.blog_id
ORDER BY b.id WITH NO DATA;
--- create a unique index, this is needed when we try to refresh the materialized view concurrently
CREATE UNIQUE INDEX ON mv_blogs_with_categories_and_tags_combined (id);
--- refresh the materialized view, to populate the data into it.
REFRESH MATERIALIZED VIEW mv_blogs_with_categories_and_tags_combined;
-------- refresh the materialized view concurrently, without locking the table
-------- when we refresh the view without concurrently , a lock is acquired on the table
-------- to prevent this we use concurrently
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_blogs_with_categories_and_tags_combined;
以下のスクリーンショットでは、実行時間を見ることができます.複雑化されたクエリテーブルを実行するときに、表示されたビューテーブルVSに問い合わせます.Execution Time: 25.874 ms
Execution Time: 0.155 ms
スピードアップ
すべてのコードはこちらhttps://github.com/sreevardhanreddi/django-materialized-views
Djangoとの統合
SQLが行われた今、私たちはDjangoとの統合を通して行きましょう
このコマンドを使用して空の移行ファイルを作成します
python manage.py makemigrations --name materialized_blogs pg_app --empty
生成されるこのファイルは、Djangoのmigrateコマンドを使用して移動を作成するために使用されます
# Generated by Django 3.2.6 on 2021-08-12 06:59
from django.db import migrations
from django.db.migrations.operations.special import RunSQL
class Migration(migrations.Migration):
dependencies = [
("pg_app", "0001_initial"),
]
operations = [
migrations.RunSQL(
"""
CREATE materialized VIEW mv_blogs_with_categories_and_tags_combined AS
SELECT b.id,
b.title AS blog_title,
b.content AS blog_content,
b.created_at AS blog_created_at,
b.is_published AS blog_is_published,
c.id AS category_id,
c.name AS category_name,
count(t.id) AS tag_count,
jsonb_agg(jsonb_build_object('tag_id', t.id, 'tag_name', t.name)) AS tags
FROM blogs b
LEFT JOIN categories c ON b.category_id = c.id
LEFT JOIN blogs_tags bt ON b.id = bt.blog_id
LEFT JOIN tags t ON bt.tags_id = t.id
GROUP BY b.id,
c.id,
bt.blog_id
ORDER BY b.id WITH NO DATA;
""",
"""
DROP MATERIALIZED VIEW IF EXISTS mv_blogs_with_categories_and_tags_combined;
""",
),
migrations.RunSQL(
"""
REFRESH MATERIALIZED VIEW mv_blogs_with_categories_and_tags_combined;
""",
"""
REFRESH MATERIALIZED VIEW mv_blogs_with_categories_and_tags_combined;
""",
),
migrations.RunSQL(
"""
CREATE UNIQUE INDEX idx_mv_blogs_with_categories_and_tags_combined_id ON mv_blogs_with_categories_and_tags_combined (id);
""",
"""
DROP INDEX IF EXISTS idx_mv_blogs_with_categories_and_tags_combined_id
""",
),
]
# RunSQL allows you to write code for migrating forwards and backwards
# i.e, applying migrations and unapplying them. here
# the first string in RunSQL is the forward SQL, the second is the reverse SQL
SQLコマンドを追加したらpython manage.py migrate
これはデータベースへの移行を適用します.このモデルの後、具体化されたビューを反映するモデル
class BlogsWithCategoriesAndTagsCombined(models.Model):
id = models.IntegerField(primary_key=True)
blog_title = models.CharField(max_length=200, unique=True)
blog_content = models.TextField()
blog_created_at = models.DateTimeField()
blog_is_published = models.BooleanField()
category_id = models.IntegerField()
category_name = models.CharField(max_length=200)
tag_count = models.IntegerField()
tags = models.JSONField()
class Meta:
managed = False
db_table = "mv_blogs_with_categories_and_tags_combined"
def __str__(self):
return self.blog_title
ここでメタクラスに注意してくださいmanaged = False
, これは、データベースでこのモデルのテーブルを作成しないようにDjangoに指示します.これが完了すると、それはQuery
queryset = BlogsWithCategoriesAndTagsCombined.objects.all()
具体化されたビューはデータのテーブルに依存しているので、テーブルが更新されると、手動でビュー化されたビューを手動で更新する必要があります.これはDjango crontabまたはセロリを使用してcronジョブ内で行うことができます.これはもともとはこちらhttps://sreevardhanreddi.github.io/
Reference
この問題について(具体化されたビューによる高速読み込み), 我々は、より多くの情報をここで見つけました https://dev.to/sreevardhanreddi/faster-reads-with-materialized-views-504bテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol