Django Performance 效能調校全方位指南 | Django 教學

2026/06/28 2026/05/25
Django Performance 效能調校全方位指南 | Django 教學

效能調校是 Django 應用從開發階段走向生產環境的關鍵環節。一個原則貫穿始終:量測先於優化(Measure Before Optimize)。盲目優化不僅浪費時間,還可能引入新的問題。本篇將從效能調校的正確思維出發,介紹 Django Debug Toolbar 效能診斷工具、資料庫索引(Database Index) 策略、快取(Cache) 機制回顧、靜態檔案優化WhiteNoiseCDN)、模板效能技巧、Middleware 精簡、連線持久化(Connection Persistence) 以及 Gzip 壓縮 等全方位的效能調校手段,幫助你系統性地提升 Django 應用的回應速度與吞吐量。

效能調校思維:量測先於優化

在動手優化之前,最重要的一件事是:找到真正的瓶頸。Donald Knuth 曾說過:「過早優化是萬惡之源。」效能調校的正確流程是:

  1. 量測:使用工具找出效能瓶頸所在
  2. 分析:確認瓶頸的根本原因
  3. 優化:針對性地解決問題
  4. 驗證:量測優化後的效果,確認改善

常見的效能瓶頸通常出現在以下幾個層面:

層面常見問題診斷工具
資料庫N+1 查詢、缺少索引、慢查詢Django Debug Toolbar、EXPLAIN
應用邏輯不必要的計算、重複查詢django-silk、cProfile
網路靜態檔案未壓縮、未快取瀏覽器 DevTools、Lighthouse
模板複雜模板邏輯、重複渲染Django Debug Toolbar Template 面板

Django Debug Toolbar 安裝與使用

Django Debug Toolbar 是 Django 開發者的必備效能診斷工具,它會在頁面側邊顯示一個可展開的工具面板,提供 SQL 查詢、模板渲染、快取命中率等詳細資訊。

安裝與設定

pip install django-debug-toolbar
# settings.py(僅開發環境)

INSTALLED_APPS += ['debug_toolbar']

# 必須放在 Middleware 列表的最前面
MIDDLEWARE = [
    'debug_toolbar.middleware.DebugToolbarMiddleware',
] + MIDDLEWARE

# 只對本機 IP 顯示 Debug Toolbar
INTERNAL_IPS = ['127.0.0.1']
# urls.py
from django.conf import settings

if settings.DEBUG:
    import debug_toolbar
    urlpatterns = [
        path('__debug__/', include(debug_toolbar.urls)),
    ] + urlpatterns

關鍵面板解讀

面板功能重點關注
SQL列出所有 SQL 查詢查詢數量、重複查詢(紅色背景)、執行時間
Timer頁面總載入時間各階段耗時分佈
Template模板渲染資訊使用的模板數量、Context 變數
Cache快取命中/未命中命中率、快取操作次數
SignalDjango Signal 觸發Signal 的執行時間

SQL 面板是最常用的,它會用紅色背景標記相似查詢超過 2 次的情況,這通常是 N+1 問題的徵兆。

資料庫索引策略

索引(Index) 是提升資料庫查詢效能最直接的手段。沒有索引的查詢需要逐筆掃描整張表(Full Table Scan,全表掃描),而有索引的查詢可以直接定位到目標資料。

單欄索引

class Article(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)   # unique=True 自動建立索引
    status = models.CharField(max_length=20, db_index=True)  # db_index 建立單欄索引
    created_at = models.DateTimeField(auto_now_add=True)

Meta.indexes 複合索引

當查詢條件經常同時包含多個欄位時,複合索引(Composite Index) 比多個單欄索引更有效率:

class Article(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    status = models.CharField(max_length=20)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        indexes = [
            # 複合索引:用於 filter(author=x, status=y) 查詢
            models.Index(
                fields=['author', 'status'],
                name='article_author_status_idx',
            ),
            # 降序索引:用於 order_by('-created_at') 查詢
            models.Index(
                fields=['-created_at'],
                name='article_created_desc_idx',
            ),
            # 條件索引(Django 4.0+,僅 PostgreSQL)
            models.Index(
                fields=['created_at'],
                name='article_published_idx',
                condition=models.Q(status='published'),
            ),
        ]

索引使用原則

  1. 高選擇性欄位優先:狀態(status)的值種類少,選擇性低;使用者 ID 的值種類多,選擇性高
  2. 頻繁查詢的 WHERE 和 ORDER BY 欄位:根據實際查詢模式建立索引
  3. 避免過度索引:每個索引都會增加寫入成本(INSERT、UPDATE、DELETE 變慢)
  4. 使用 EXPLAIN 驗證:確認索引是否被實際使用
# 在 Django Shell 中查看 SQL 和 EXPLAIN
from django.db import connection

qs = Article.objects.filter(author_id=1, status='published')
print(qs.query)  # 查看生成的 SQL

with connection.cursor() as cursor:
    cursor.execute("EXPLAIN ANALYZE " + str(qs.query))
    for row in cursor.fetchall():
        print(row[0])
# Index Scan using article_author_status_idx → 索引生效
# Seq Scan on article → 全表掃描,需要建立索引!

快取策略回顧

快取是效能優化最有效的手段之一。如果你尚未閱讀快取相關的章節,建議先回顧第 32 篇的快取機制詳解。這裡快速回顧幾個關鍵策略:

快取層級一覽

層級方式適用場景
全站快取UpdateCacheMiddleware + FetchFromCacheMiddleware內容不常變動的靜態網站
View 快取@cache_page(60 * 15)特定頁面快取 15 分鐘
模板片段快取{% cache 300 sidebar %}頁面中局部區塊的快取
低階 APIcache.get() / cache.set()自訂快取邏輯
# View 快取範例
from django.views.decorators.cache import cache_page

@cache_page(60 * 15)  # 快取 15 分鐘
def article_list(request):
    articles = Article.objects.filter(status='published')
    return render(request, 'articles/list.html', {'articles': articles})
# 低階快取 API 範例
from django.core.cache import cache

def get_popular_articles():
    cache_key = 'popular_articles'
    articles = cache.get(cache_key)
    if articles is None:
        articles = list(
            Article.objects.filter(status='published')
            .order_by('-view_count')[:10]
            .values('id', 'title', 'view_count')
        )
        cache.set(cache_key, articles, timeout=60 * 30)  # 快取 30 分鐘
    return articles

靜態檔案優化

靜態檔案(CSS、JavaScript、圖片)的載入速度直接影響使用者體驗。以下介紹兩種主要的優化策略。

WhiteNoise:簡化靜態檔案服務

WhiteNoise 讓 Django/Gunicorn 可以直接服務靜態檔案,無需額外的 Nginx 設定,並自動支援 Gzip 壓縮和檔名 hash 長期快取:

pip install whitenoise
# settings.py

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',  # 緊接在 SecurityMiddleware 之後
    # ... 其他 Middleware
]

# 開啟壓縮 + Manifest(檔名加 hash,支援長期快取)
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

WhiteNoise 的工作原理:

  1. collectstatic 收集所有靜態檔案到 STATIC_ROOT
  2. WhiteNoise 在啟動時掃描所有檔案,產生 Gzip/Brotli 壓縮版本
  3. 為每個檔案產生帶 hash 的檔名(如 style.abc123.css),可設定超長快取時間
  4. 請求靜態檔案時,直接從記憶體中回應

CDN 加速

對於高流量的生產環境,建議搭配 CDN(Content Delivery Network,內容分發網路) 將靜態檔案分發到全球節點:

# settings.py - 搭配 AWS S3 + CloudFront
STATIC_URL = 'https://d1234567.cloudfront.net/static/'

# 或使用 django-storages 自動上傳至 S3
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3StaticStorage'
AWS_STORAGE_BUCKET_NAME = 'my-static-bucket'
AWS_S3_CUSTOM_DOMAIN = 'd1234567.cloudfront.net'

模板效能優化

Django 模板引擎的效能通常不是主要瓶頸,但在高流量場景下,一些優化技巧可以帶來顯著改善。

{% cache %} 模板片段快取

將不常變動的模板區塊包裹在 {% cache %} 標籤中,避免重複渲染:

{% load cache %}

<!-- 快取側邊欄 300 秒,快取 key 為 "sidebar" -->
{% cache 300 sidebar %}
    <div class="sidebar">
        {% for category in categories %}
            <a href="{{ category.get_absolute_url }}">
                {{ category.name }} ({{ category.article_count }})
            </a>
        {% endfor %}
    </div>
{% endcache %}

<!-- 依據使用者 ID 產生不同的快取版本 -->
{% cache 600 user_dashboard request.user.id %}
    <div class="dashboard">
        <!-- 使用者專屬的儀表板內容 -->
    </div>
{% endcache %}

with 標籤減少重複求值

{% with %} 標籤可以將複雜表達式的結果暫存為變數,避免在模板中重複計算:

<!-- 不佳:expensive_method() 被呼叫多次 -->
{% if article.get_comment_count > 0 %}
    <p>{{ article.get_comment_count }} 則留言</p>
{% endif %}

<!-- 改善:只呼叫一次 -->
{% with comment_count=article.get_comment_count %}
    {% if comment_count > 0 %}
        <p>{{ comment_count }} 則留言</p>
    {% endif %}
{% endwith %}

Middleware 效能影響

每個 Middleware 都會在每次請求/回應周期中被執行,因此過多或不必要的 Middleware 會拖慢效能。

精簡 Middleware 原則

# settings.py - 審視每個 Middleware 是否必要

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',          # 必要:安全
    'whitenoise.middleware.WhiteNoiseMiddleware',             # 必要:靜態檔案
    'django.contrib.sessions.middleware.SessionMiddleware',   # 必要:Session
    'django.middleware.common.CommonMiddleware',              # 必要:URL 正規化
    'django.middleware.csrf.CsrfViewMiddleware',              # 必要:CSRF 防護
    'django.contrib.auth.middleware.AuthenticationMiddleware', # 必要:認證
    'django.contrib.messages.middleware.MessageMiddleware',   # 必要:訊息框架
    'django.middleware.clickjacking.XFrameOptionsMiddleware', # 必要:Clickjacking 防護
    # 'django.middleware.locale.LocaleMiddleware',            # 未使用多語系可移除
    # 'debug_toolbar.middleware.DebugToolbarMiddleware',      # 僅開發環境
]

自訂 Middleware 的效能考量

如果你撰寫了自訂 Middleware,要注意避免在其中執行耗時操作:

# 不佳:每次請求都查詢資料庫
class SlowMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # 每次請求都執行 DB 查詢 — 效能殺手!
        request.site_config = SiteConfig.objects.first()
        return self.get_response(request)

# 改善:搭配快取
class CachedMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        from django.core.cache import cache
        config = cache.get('site_config')
        if config is None:
            config = SiteConfig.objects.first()
            cache.set('site_config', config, timeout=300)  # 快取 5 分鐘
        request.site_config = config
        return self.get_response(request)

連線持久化(CONN_MAX_AGE)

預設情況下,Django 在每次請求結束後會關閉資料庫連線,下次請求再重新建立。這在高流量場景下會造成大量的連線建立/銷毀開銷。CONN_MAX_AGE 設定可以讓 Django 在指定時間內重複使用同一條連線:

# settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydb',
        'USER': 'myuser',
        'PASSWORD': 'mypassword',
        'HOST': 'localhost',
        'PORT': '5432',
        'CONN_MAX_AGE': 60,   # 連線保持 60 秒
        # CONN_MAX_AGE = 0    # 預設:每次請求後關閉
        # CONN_MAX_AGE = None # 永久保持(需注意資料庫連線上限)
    }
}

CONN_MAX_AGE 注意事項

設定值行為適用場景
0(預設)每次請求結束後關閉連線開發環境
60 ~ 600連線保持 1~10 分鐘大多數生產環境
None永久保持連線搭配連線池使用

搭配 Gunicorn 多 Worker 模式時,每個 Worker 都會持有自己的連線,因此總連線數 = Workers 數量 x 每個 Worker 的連線數。要確保不超過資料庫的最大連線數限制。

對於更進階的連線管理,可以使用外部連線池如 PgBouncer

# 使用 django-db-connection-pool
# pip install django-db-connection-pool

DATABASES = {
    'default': {
        'ENGINE': 'dj_db_conn_pool.backends.postgresql',
        'NAME': 'mydb',
        'POOL_OPTIONS': {
            'POOL_SIZE': 10,       # 連線池基本大小
            'MAX_OVERFLOW': 10,    # 允許超出的連線數
            'RECYCLE': 24 * 60 * 60,  # 連線回收週期(1 天)
        }
    }
}

Gzip 壓縮

Gzip 壓縮 可以大幅減少 HTTP 回應的大小,加快傳輸速度。Django 提供了內建的 Gzip Middleware:

# settings.py

MIDDLEWARE = [
    'django.middleware.gzip.GZipMiddleware',  # 放在最前面
    'django.middleware.security.SecurityMiddleware',
    # ... 其他 Middleware
]

不過,更推薦在反向代理層(Nginx)處理 Gzip 壓縮,因為 Nginx 的壓縮效率更高,且不會佔用 Django Worker 的資源:

# nginx.conf
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1000;    # 小於 1KB 的回應不壓縮
gzip_comp_level 6;       # 壓縮等級(1-9,6 是效能與壓縮率的平衡點)
gzip_vary on;            # 加入 Vary: Accept-Encoding 標頭

效能調校檢查清單

以下是一份實用的效能調校檢查清單,建議在專案上線前逐項確認:

類別項目狀態
資料庫使用 select_related / prefetch_related 消除 N+1
資料庫為常用查詢欄位建立索引
資料庫啟用 CONN_MAX_AGE 連線持久化
快取為高頻讀取的資料設定快取
快取使用模板片段快取({% cache %}
靜態檔案啟用 WhiteNoise 或 Nginx 靜態檔案服務
靜態檔案開啟 Gzip 壓縮
靜態檔案設定合理的快取標頭(Cache-Control、ETag)
應用精簡不必要的 Middleware
應用使用 values() / only() 減少不必要的欄位載入
監控開發環境安裝 Django Debug Toolbar
監控使用 assertNumQueries 防止查詢數回歸

總結

Django 效能調校的核心原則是 量測先於優化。透過 Django Debug Toolbar 找出效能瓶頸後,再有針對性地進行優化。資料庫層面,善用索引策略、連線持久化(CONN_MAX_AGE)和連線池能有效降低查詢延遲;快取層面,從全站快取到模板片段快取,根據資料更新頻率選擇合適的快取策略;靜態檔案層面,WhiteNoise 提供了簡單高效的解決方案,搭配 CDN 則能進一步加速全球存取;應用層面,精簡 Middleware、使用模板 {% cache %}{% with %} 標籤都能帶來可觀的效能提升。下一篇我們將深入探討 Django 最常見的效能問題 — N+1 查詢,學習如何用 select_relatedprefetch_related 徹底解決它。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。