{"id":20619760,"url":"https://github.com/twtrubiks/django_n_add_1_queries_problem_tutorial","last_synced_at":"2026-04-29T09:03:48.581Z","repository":{"id":84518934,"uuid":"597385534","full_name":"twtrubiks/django_N_add_1_queries_problem_tutorial","owner":"twtrubiks","description":"Django N+1 Queries Problem","archived":false,"fork":false,"pushed_at":"2024-07-25T05:47:49.000Z","size":19,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-21T01:40:27.463Z","etag":null,"topics":["django","prefetch","prefetch-related","query","select-related"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/twtrubiks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-04T11:41:45.000Z","updated_at":"2025-05-27T02:27:15.000Z","dependencies_parsed_at":"2024-11-20T11:02:50.913Z","dependency_job_id":null,"html_url":"https://github.com/twtrubiks/django_N_add_1_queries_problem_tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/twtrubiks/django_N_add_1_queries_problem_tutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango_N_add_1_queries_problem_tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango_N_add_1_queries_problem_tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango_N_add_1_queries_problem_tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango_N_add_1_queries_problem_tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twtrubiks","download_url":"https://codeload.github.com/twtrubiks/django_N_add_1_queries_problem_tutorial/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango_N_add_1_queries_problem_tutorial/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32418176,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T06:29:02.080Z","status":"ssl_error","status_checked_at":"2026-04-29T06:29:00.631Z","response_time":110,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django","prefetch","prefetch-related","query","select-related"],"created_at":"2024-11-16T12:12:27.711Z","updated_at":"2026-04-29T09:03:48.542Z","avatar_url":"https://github.com/twtrubiks.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# django_N_add_1_queries_problem_tutorial\n\n透過 django 介紹 N+1 Queries Problem\n\n* [Youtube Tutorial - 透過 django 介紹 N+1 Queries Problem](https://youtu.be/trVzF-jBFTo)\n\n## 環境安裝\n\n```cmd\npip3 install -r requirements.txt\n```\n\n這裡面有使用到 [django-extensions](https://django-extensions.readthedocs.io/en/latest/), 主要是要看 raw SQL 做了什麼事情\n\n## 介紹\n\n[models.py](https://github.com/twtrubiks/django_N_add_1_queries_problem_tutorial/blob/main/books/models.py)\n\n建立一些簡單的資料\n\nAuthor\n\n```sql\npostgres=# SELECT * FROM public.author;\n id |   name   | country_id\n----+----------+------------\n  1 | author_1 |          1\n  2 | author_2 |          2\n  3 | author_3 |          3\n(3 rows)\n```\n\nBook\n\n```sql\npostgres=# SELECT * FROM public.book;\n id |  name  | author_id\n----+--------+-----------\n  1 | book_1 |         1\n  2 | book_2 |         2\n  3 | book_3 |         3\n(3 rows)\n```\n\nCountry\n\n```sql\npostgres=# SELECT * FROM public.country;\n id |   name\n----+-----------\n  1 | country_1\n  2 | country_2\n  3 | country_3\n(3 rows)\n```\n\nUser\n\n```sql\npostgres=# SELECT * FROM public.\"user\";\n id | name\n----+-------\n  1 | user1\n  2 | user2\n  3 | user3\n(3 rows)\n```\n\nTag\n\n```sql\npostgres=# SELECT * FROM public.tag;\n id | name  | created_by_id\n----+-------+---------------\n  1 | tag_1 |             1\n  3 | tag_3 |             3\n  2 | tag_2 |\n(3 rows)\n```\n\nbook_tags\n\n(ManyToManyField 自動產出來的表, 紀錄 Book 和 Tag 的關係)\n\n```sql\npostgres=# SELECT * FROM public.book_tags;\n id | book_id | tag_id\n----+---------+--------\n  1 |       1 |      1\n  2 |       1 |      2\n  3 |       2 |      3\n(3 rows)\n```\n\n先 migrate 後匯入測試資料\n\n```cmd\npython manage.py migrate\npython manage.py loaddata db.json\n```\n\n也可以從後台建立(觀看),\n\n```cmd\npython3 manage.py runserver\n```\n\n進入 [http://0.0.0.0:8000/admin/](http://0.0.0.0:8000/admin/)\n\n```text\n帳號/密碼: admin/admin\n```\n\n進入 shell_plus 操作資料,\n\n```cmd\npython3 manage.py shell_plus --print-sql\n```\n\n### 情境一\n\nN+1 Queries\n\n```cmd\n\u003e\u003e\u003e from books.models import Country, Author, Book\n\u003e\u003e\u003e books = Book.objects.all() # QuerySets are lazy # Doesn't hit the database.\n\u003e\u003e\u003e for book in books:\n...     print(book.name, \"by\", book.author.name)\n...\nSELECT \"book\".\"id\",\n       \"book\".\"name\",\n       \"book\".\"author_id\"\n  FROM \"book\"\nExecution time: 0.000184s [Database: default]\nSELECT \"author\".\"id\",\n       \"author\".\"name\",\n       \"author\".\"country_id\"\n  FROM \"author\"\n WHERE \"author\".\"id\" = 1\n LIMIT 21\nExecution time: 0.000178s [Database: default]\nbook_1 by author_1\nSELECT \"author\".\"id\",\n       \"author\".\"name\",\n       \"author\".\"country_id\"\n  FROM \"author\"\n WHERE \"author\".\"id\" = 2\n LIMIT 21\nExecution time: 0.000191s [Database: default]\nbook_2 by author_2\nSELECT \"author\".\"id\",\n       \"author\".\"name\",\n       \"author\".\"country_id\"\n  FROM \"author\"\n WHERE \"author\".\"id\" = 3\n LIMIT 21\nExecution time: 0.000202s [Database: default]\nbook_3 by author_3\n```\n\nbook table 只有3筆資料, 但總共執行了 4 (N+1) 次 SQL,\n\n1次是撈出全部的book, 剩下的3次, 是透過 author id 去撈作者的 name.\n\n### 情境二\n\n2N+1 Queries\n\n假如今天我們 access 更多的 foreign key\n\n```cmd\n\u003e\u003e\u003e from books.models import Country, Author, Book\n\u003e\u003e\u003e books = Book.objects.all() # QuerySets are lazy # Doesn't hit the database.\n\u003e\u003e\u003e for book in books:\n...     print(book.name, \"by\", book.author.name, \"from\", book.author.country.name)\n...\nSELECT \"book\".\"id\",\n       \"book\".\"name\",\n       \"book\".\"author_id\"\n  FROM \"book\"\nExecution time: 0.000313s [Database: default]\nSELECT \"author\".\"id\",\n       \"author\".\"name\",\n       \"author\".\"country_id\"\n  FROM \"author\"\n WHERE \"author\".\"id\" = 1\n LIMIT 21\nExecution time: 0.000086s [Database: default]\nSELECT \"country\".\"id\",\n       \"country\".\"name\"\n  FROM \"country\"\n WHERE \"country\".\"id\" = 1\n LIMIT 21\nExecution time: 0.000128s [Database: default]\nbook_1 by author_1 from country_1\nSELECT \"author\".\"id\",\n       \"author\".\"name\",\n       \"author\".\"country_id\"\n  FROM \"author\"\n WHERE \"author\".\"id\" = 2\n LIMIT 21\nExecution time: 0.000112s [Database: default]\nSELECT \"country\".\"id\",\n       \"country\".\"name\"\n  FROM \"country\"\n WHERE \"country\".\"id\" = 2\n LIMIT 21\nExecution time: 0.000108s [Database: default]\nbook_2 by author_2 from country_2\nSELECT \"author\".\"id\",\n       \"author\".\"name\",\n       \"author\".\"country_id\"\n  FROM \"author\"\n WHERE \"author\".\"id\" = 3\n LIMIT 21\nExecution time: 0.000254s [Database: default]\nSELECT \"country\".\"id\",\n       \"country\".\"name\"\n  FROM \"country\"\n WHERE \"country\".\"id\" = 3\n LIMIT 21\nExecution time: 0.000117s [Database: default]\nbook_3 by author_3 from country_3\n```\n\nbook table 只有3筆資料, 但總共執行了 7 (2N+1) 次 SQL,\n\n1次是撈出全部的book,\n\n(1次是透過 author id 去撈作者的 name, 1次是透過 country id 去撈居住地點的 name.) * 2 次.\n\n看了情境一(N+1 Queries), 以及情境二 (2N+1 Queries), 肯定會造成效能的影響 :weary:\n\n這樣在 Django ORM 上, 應該怎麼解決呢 :question:\n\n### 情境三\n\n也來看一下 ManyToManyField 的情境\n\n```cmd\nfrom books.models import Country, Author, Book, Tag\n\u003e\u003e\u003e books = Book.objects.all()\n\u003e\u003e\u003e for book in books:\n...     print(book.tags.all())\n...\n\nSELECT \"book\".\"id\",\n       \"book\".\"name\",\n       \"book\".\"author_id\"\n  FROM \"book\"\nExecution time: 0.001057s [Database: default]\nSELECT \"tag\".\"id\",\n       \"tag\".\"name\",\n       \"tag\".\"created_by_id\"\n  FROM \"tag\"\n INNER JOIN \"book_tags\"\n    ON (\"tag\".\"id\" = \"book_tags\".\"tag_id\")\n WHERE \"book_tags\".\"book_id\" = 1\n LIMIT 21\nExecution time: 0.001094s [Database: default]\n\u003cQuerySet [\u003cTag: tag_1\u003e, \u003cTag: tag_2\u003e]\u003e\nSELECT \"tag\".\"id\",\n       \"tag\".\"name\",\n       \"tag\".\"created_by_id\"\n  FROM \"tag\"\n INNER JOIN \"book_tags\"\n    ON (\"tag\".\"id\" = \"book_tags\".\"tag_id\")\n WHERE \"book_tags\".\"book_id\" = 2\n LIMIT 21\nExecution time: 0.000927s [Database: default]\n\u003cQuerySet [\u003cTag: tag_3\u003e]\u003e\nSELECT \"tag\".\"id\",\n       \"tag\".\"name\",\n       \"tag\".\"created_by_id\"\n  FROM \"tag\"\n INNER JOIN \"book_tags\"\n    ON (\"tag\".\"id\" = \"book_tags\".\"tag_id\")\n WHERE \"book_tags\".\"book_id\" = 3\n LIMIT 21\nExecution time: 0.000973s [Database: default]\n\u003cQuerySet []\u003e\n```\n\n一次 SQL 取出全部的 book,\n\n二次 SQL 直接 JOIN book_tags, 然後透過 WHERE 的方式, 將需要的資料拿出來,\n\n因為 book 有三筆資料, 就分別 JOIN 了 3 次 (效能不好).\n\n## 解決方法\n\n### 方法一\n\n`select_related()` [https://docs.djangoproject.com/en/5.0/ref/models/querysets/#select-related](https://docs.djangoproject.com/en/5.0/ref/models/querysets/#select-related)\n\n官網說明如下,\n\n```text\nReturns a QuerySet that will “follow” foreign-key relationships, selecting additional related-object data when it executes its query. This is a performance booster which results in a single more complex query but means later use of foreign-key relationships won’t require database queries.\n```\n\n#### 情境一\n\nN+1 Queries\n\n```cmd\n\u003e\u003e\u003e from books.models import Country, Author, Book\n\u003e\u003e\u003e books = Book.objects.all().select_related(\"author\") # QuerySets are lazy # Doesn't hit the database.\n\u003e\u003e\u003e for book in books:\n...     print(book.name, \"by\", book.author.name)\n...\nSELECT \"book\".\"id\",\n       \"book\".\"name\",\n       \"book\".\"author_id\",\n       \"author\".\"id\",\n       \"author\".\"name\",\n       \"author\".\"country_id\"\n  FROM \"book\"\n INNER JOIN \"author\"\n    ON (\"book\".\"author_id\" = \"author\".\"id\")\nExecution time: 0.000651s [Database: default]\nbook_1 by author_1\nbook_2 by author_2\nbook_3 by author_3\n```\n\n只使用了一次的 Query, 使用 Join 的方式解決了這個問題.\n\n#### 情境二\n\n2N+1 Queries\n\n```cmd\n\u003e\u003e\u003e from books.models import Country, Author, Book\n\u003e\u003e\u003e books = Book.objects.all().select_related(\"author\", \"author__country\") # QuerySets are lazy # Doesn't hit the database.\n\u003e\u003e\u003e for book in books:\n...     print(book.name, \"by\", book.author.name, \"from\", book.author.country.name)\n...\nSELECT \"book\".\"id\",\n       \"book\".\"name\",\n       \"book\".\"author_id\",\n       \"author\".\"id\",\n       \"author\".\"name\",\n       \"author\".\"country_id\",\n       \"country\".\"id\",\n       \"country\".\"name\"\n  FROM \"book\"\n INNER JOIN \"author\"\n    ON (\"book\".\"author_id\" = \"author\".\"id\")\n INNER JOIN \"country\"\n    ON (\"author\".\"country_id\" = \"country\".\"id\")\nExecution time: 0.000541s [Database: default]\nbook_1 by author_1 from country_1\nbook_2 by author_2 from country_2\nbook_3 by author_3 from country_3\n```\n\n只使用了一次的 Query, 使用 Join 兩個 table 的方式解決了這個問題.\n\n### 方法二\n\n`prefetch_related` [https://docs.djangoproject.com/en/5.0/ref/models/querysets/#prefetch-related](https://docs.djangoproject.com/en/5.0/ref/models/querysets/#prefetch-related)\n\n官網說明如下,\n\n```text\nReturns a QuerySet that will automatically retrieve, in a single batch, related objects for each of the specified lookups.\n\nThis has a similar purpose to select_related, in that both are designed to stop the deluge of database queries that is caused by accessing related objects, but the strategy is quite different.\n\nselect_related works by creating an SQL join and including the fields of the related object in the SELECT statement. For this reason, select_related gets the related objects in the same database query. However, to avoid the much larger result set that would result from joining across a ‘many’ relationship, select_related is limited to single-valued relationships - foreign key and one-to-one.\n\nprefetch_related, on the other hand, does a separate lookup for each relationship, and does the ‘joining’ in Python. This allows it to prefetch many-to-many and many-to-one objects, which cannot be done using select_related, in addition to the foreign key and one-to-one relationships that are supported by select_related. It also supports prefetching of GenericRelation and GenericForeignKey, however, it must be restricted to a homogeneous set of results. For example, prefetching objects referenced by a GenericForeignKey is only supported if the query is restricted to one ContentType.\n```\n\n#### 情境一\n\nN+1 Queries\n\n```cmd\n\u003e\u003e\u003e from books.models import Country, Author, Book\n\u003e\u003e\u003e books = Book.objects.all().prefetch_related(\"author\") # QuerySets are lazy # Doesn't hit the database.\n\u003e\u003e\u003e for book in books:\n...     print(book.name, \"by\", book.author.name)\n...\nSELECT \"book\".\"id\",\n       \"book\".\"name\",\n       \"book\".\"author_id\"\n  FROM \"book\"\nExecution time: 0.000486s [Database: default]\nSELECT \"author\".\"id\",\n       \"author\".\"name\",\n       \"author\".\"country_id\"\n  FROM \"author\"\n WHERE \"author\".\"id\" IN (1, 2, 3)\nExecution time: 0.000735s [Database: default]\nbook_1 by author_1\nbook_2 by author_2\nbook_3 by author_3\n```\n\n和 `select_related` 不同的是, 使用了兩次的 Query,\n\n一次找全部的 book,\n\n第二次透過作者 id (ids) 一次找到全部的名稱.\n\n#### 情境二\n\n2N+1 Queries\n\n```cmd\n\u003e\u003e\u003e from books.models import Country, Author, Book\n\u003e\u003e\u003e books = Book.objects.all().prefetch_related(\"author\", \"author__country\") # QuerySets are lazy # Doesn't hit the database.\n\u003e\u003e\u003e for book in books:\n...     print(book.name, \"by\", book.author.name, \"from\", book.author.country.name)\n...\nSELECT \"book\".\"id\",\n       \"book\".\"name\",\n       \"book\".\"author_id\"\n  FROM \"book\"\nExecution time: 0.000108s [Database: default]\nSELECT \"author\".\"id\",\n       \"author\".\"name\",\n       \"author\".\"country_id\"\n  FROM \"author\"\n WHERE \"author\".\"id\" IN (1, 2, 3)\nExecution time: 0.000112s [Database: default]\nSELECT \"country\".\"id\",\n       \"country\".\"name\"\n  FROM \"country\"\nWHERE \"country\".\"id\" IN (1, 2, 3)\n```\n\n和 `select_related` 不同的是, 使用了三次的 Query,\n\n一次找全部的 book,\n\n第二次透過作者 id (ids) 一次找到全部的名稱.\n\n第三次透過居住地 id (ids) 一次找到全部的名稱.\n\n#### 情境三\n\nManyToManyField 的情境\n\n```cmd\n\u003e\u003e\u003e from books.models import Country, Author, Book, Tag\n\u003e\u003e\u003e books = Book.objects.all().prefetch_related(\"tags\")\n\u003e\u003e\u003e for book in books:\n...     print(book.tags.all())\n...\n\nSELECT \"book\".\"id\",\n       \"book\".\"name\",\n       \"book\".\"author_id\"\n  FROM \"book\"\nExecution time: 0.001068s [Database: default]\nSELECT (\"book_tags\".\"book_id\") AS \"_prefetch_related_val_book_id\",\n       \"tag\".\"id\",\n       \"tag\".\"name\",\n       \"tag\".\"created_by_id\"\n  FROM \"tag\"\n INNER JOIN \"book_tags\"\n    ON (\"tag\".\"id\" = \"book_tags\".\"tag_id\")\n WHERE \"book_tags\".\"book_id\" IN (1, 2, 3)\nExecution time: 0.000886s [Database: default]\n\u003cQuerySet [\u003cTag: tag_1\u003e, \u003cTag: tag_2\u003e]\u003e\n\u003cQuerySet [\u003cTag: tag_3\u003e]\u003e\n\u003cQuerySet []\u003e\n```\n\n一次 SQL 取出全部的 book,\n\n二次 SQL 直接 JOIN book_tags, 然後透過 WHERE IN 的方式, 一口氣將需要的資料拿出來,\n\n當在 loop 的時候, 其實也都是去快取(cache) 裡面取資料而已(不會 access db).\n\n#### 更複雜的情境\n\n假設今天我們的 ManyToManyField tags 裡面還有一個 ForeignKey created_by,\n\n這樣應該如何一起把它 JOIN 起來呢 ❓ 這時候就必須透過 [Prefetch](https://docs.djangoproject.com/en/5.0/ref/models/querysets/#django.db.models.Prefetch)\n\n以下範例是只撈出 Tag 裡面有 created_by 值得內容,\n\n```cmd\n\u003e\u003e\u003e from books.models import Country, Author, Book, Tag, User\n\u003e\u003e\u003e from django.db.models import Prefetch\n\u003e\u003e\u003e queryset = Tag.objects.select_related(\"created_by\").filter(created_by__isnull=False)\n\u003e\u003e\u003e books = Book.objects.prefetch_related(Prefetch(\"tags\", queryset=queryset, to_attr=\"tag_has_created_by\"))\n\u003e\u003e\u003e for book in books:\n...     for tag in book.tag_has_created_by:\n...         print(tag.created_by)\n...\n\nSELECT \"book\".\"id\",\n       \"book\".\"name\",\n       \"book\".\"author_id\"\n  FROM \"book\"\nExecution time: 0.001034s [Database: default]\nSELECT (\"book_tags\".\"book_id\") AS \"_prefetch_related_val_book_id\",\n       \"tag\".\"id\",\n       \"tag\".\"name\",\n       \"tag\".\"created_by_id\",\n       \"user\".\"id\",\n       \"user\".\"name\"\n  FROM \"tag\"\n INNER JOIN \"user\"\n    ON (\"tag\".\"created_by_id\" = \"user\".\"id\")\n INNER JOIN \"book_tags\"\n    ON (\"tag\".\"id\" = \"book_tags\".\"tag_id\")\n WHERE (\"tag\".\"created_by_id\" IS NOT NULL AND \"book_tags\".\"book_id\" IN (1, 2, 3))\nExecution time: 0.001113s [Database: default]\nuser1\nuser3\n```\n\n你會發現 JOIN 了兩次, 一次是針對 book_tags, 另一次是針對 created_by (user).\n\n#### prefetch_related_objects\n\n如果今天你使用了 `model.all()`, 它不會有快取,\n\n因為對 django 來說, 他是一份新的.\n\n所以這時候可以透過 [prefetch_related_objects](https://docs.djangoproject.com/en/5.0/ref/models/querysets/#prefetch-related-objects) 快取起來.\n\n`prefetch_related_objects(model_instances, *related_lookups)`\n\n```cmd\n\u003e\u003e\u003e from books.models import Country, Author, Book, Tag, User\n\u003e\u003e\u003e book = Book.objects.first()\nSELECT \"book\".\"id\",\n       \"book\".\"name\",\n       \"book\".\"author_id\"\n  FROM \"book\"\n ORDER BY \"book\".\"id\" ASC\n LIMIT 1\nExecution time: 0.000815s [Database: default]\n\u003e\u003e\u003e book.tags.all() # query db\nSELECT \"tag\".\"id\",\n       \"tag\".\"name\",\n       \"tag\".\"created_by_id\"\n  FROM \"tag\"\n INNER JOIN \"book_tags\"\n    ON (\"tag\".\"id\" = \"book_tags\".\"tag_id\")\n WHERE \"book_tags\".\"book_id\" = 1\n LIMIT 21\nExecution time: 0.001145s [Database: default]\n\u003cQuerySet [\u003cTag: tag_1\u003e, \u003cTag: tag_2\u003e]\u003e\n\u003e\u003e\u003e book.tags.all() # query db\nSELECT \"tag\".\"id\",\n       \"tag\".\"name\",\n       \"tag\".\"created_by_id\"\n  FROM \"tag\"\n INNER JOIN \"book_tags\"\n    ON (\"tag\".\"id\" = \"book_tags\".\"tag_id\")\n WHERE \"book_tags\".\"book_id\" = 1\n LIMIT 21\nExecution time: 0.001164s [Database: default]\n\u003cQuerySet [\u003cTag: tag_1\u003e, \u003cTag: tag_2\u003e]\u003e\n\n\u003e\u003e\u003e from django.db.models import prefetch_related_objects\n\u003e\u003e\u003e prefetch_related_objects([book], \"tags\")\nSELECT (\"book_tags\".\"book_id\") AS \"_prefetch_related_val_book_id\",\n       \"tag\".\"id\",\n       \"tag\".\"name\",\n       \"tag\".\"created_by_id\"\n  FROM \"tag\"\n INNER JOIN \"book_tags\"\n    ON (\"tag\".\"id\" = \"book_tags\".\"tag_id\")\n WHERE \"book_tags\".\"book_id\" IN (1)\nExecution time: 0.001192s [Database: default]\n\u003e\u003e\u003e book.tags.all() # use cache\n\u003cQuerySet [\u003cTag: tag_1\u003e, \u003cTag: tag_2\u003e]\u003e\n\u003e\u003e\u003e book.tags.all() # use cache\n\u003cQuerySet [\u003cTag: tag_1\u003e, \u003cTag: tag_2\u003e]\u003e\n\u003e\u003e\u003e book.tags.all() # use cache\n\u003cQuerySet [\u003cTag: tag_1\u003e, \u003cTag: tag_2\u003e]\u003e\n\u003e\u003e\u003e book.tags.all() # use cache\n\u003cQuerySet [\u003cTag: tag_1\u003e, \u003cTag: tag_2\u003e]\u003e\n```\n\n可以發現當我們使用了 prefetch_related_objects, 就會直接從快取拿資料.\n\n## 結論\n\n`select_related` 可以只 Query 一次, 但 JOIN 還是需要一點成本.\n\n通常使用在 OneToOne(一對一) 或 ForeignKey(多對一), 透過 join 降低查詢次數.\n\n```text\nselect_related is limited to single-valued relationships - foreign key and one-to-one.\n```\n\n`prefetch_related` Query 多次(依照你要取的 ForeignKey 數量), 但執行比較不需要成本的 Query.\n\n通常使用在 OneToMany(一對多) 或 ManyToMany(多對多), 避免載入大量資料.\n\n## 執行環境\n\n* Python 3.11\n\n## Reference\n\n* [Django](https://www.djangoproject.com/)\n\n* [Django and the N+1 Queries Problem](https://scoutapm.com/blog/django-and-the-n1-queries-problem)\n\n## Donation\n\n文章都是我自己研究內化後原創，如果有幫助到您，也想鼓勵我的話，歡迎請我喝一杯咖啡:laughing:\n\n![alt tag](https://i.imgur.com/LRct9xa.png)\n\n[贊助者付款](https://payment.opay.tw/Broadcaster/Donate/9E47FDEF85ABE383A0F5FC6A218606F8)\n\n## License\n\nMIT license","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2Fdjango_n_add_1_queries_problem_tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwtrubiks%2Fdjango_n_add_1_queries_problem_tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2Fdjango_n_add_1_queries_problem_tutorial/lists"}