Django 進階資料庫:連線池、Raw SQL 與交易管理 | Django 教學

2026/07/02 2026/05/25
Django 進階資料庫:連線池、Raw SQL 與交易管理 | Django 教學

當 Django 專案從開發階段邁向生產環境,資料庫操作的效能與可靠性就成為核心關注點。 連線池(Connection Pool) 減少重複建立連線的開銷, Raw SQL 讓你在 ORM 力有未逮時直接操作資料庫, 交易管理(Transaction Management) 確保資料的一致性與完整性。本篇將深入探討這些進階資料庫技術,幫助你打造高效能且穩定的 Django 後端。

連線池設定

每次 HTTP 請求建立新的資料庫連線都需要經歷 TCP 握手與認證,在高並發環境下這個開銷非常可觀。 連線池(Connection Pool) 透過重複使用現有連線來解決這個問題。

CONN_MAX_AGE:Django 內建連線持久化

Django 內建最簡單的連線持久化機制——CONN_MAX_AGE 參數:

# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydb',
        'USER': 'myuser',
        'PASSWORD': 'mypassword',
        'HOST': 'localhost',
        'PORT': '5432',
        'CONN_MAX_AGE': 600,  # 連線存活 600 秒(10 分鐘)
    }
}
設定值行為
0(預設)每次請求結束後關閉連線
600連線存活 10 分鐘,期間可重複使用
None連線永不過期(需注意資料庫端的逾時設定)

需要注意的是,CONN_MAX_AGE每個執行緒一個連線 的持久化機制,並非真正的連線池。如果使用 Gunicorn 搭配 4 Workers 和 2 threads,最多會有 8 個持久連線。

django-db-connection-pool:真正的連線池

如果需要更精細的連線池管理,可以使用第三方套件 django-db-connection-pool,它基於 SQLAlchemy 的連線池實作:

pip install django-db-connection-pool
# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'dj_db_conn_pool.backends.postgresql',
        'NAME': 'mydb',
        'USER': 'myuser',
        'PASSWORD': 'mypassword',
        'HOST': 'localhost',
        'PORT': '5432',
        'POOL_OPTIONS': {
            'POOL_SIZE': 10,        # 連線池大小
            'MAX_OVERFLOW': 10,     # 超出 POOL_SIZE 時的最大額外連線數
            'RECYCLE': 300,         # 連線回收時間(秒)
        },
    }
}

pgBouncer:外部連線池

pgBouncer 是 PostgreSQL 專用的輕量級連線池代理,獨立於 Django 運行,適合大規模生產環境:

# /etc/pgbouncer/pgbouncer.ini
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb

[pgbouncer]
listen_port = 6432
listen_addr = 127.0.0.1
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction        # 以交易為單位分配連線
max_client_conn = 1000         # 最大客戶端連線數
default_pool_size = 20         # 每個資料庫的預設連線池大小
# settings.py(Django 連線到 pgBouncer)
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydb',
        'HOST': '127.0.0.1',
        'PORT': '6432',          # pgBouncer 的端口,非 PostgreSQL 的 5432
        'CONN_MAX_AGE': 0,       # 搭配 pgBouncer 時設為 0,避免兩層池衝突
    }
}

Raw SQL 查詢

雖然 Django ORM 能處理大部分查詢需求,但某些複雜場景仍需要直接撰寫 SQL。Django 提供兩種方式執行 Raw SQL。

Manager.raw():回傳 Model 實例

Manager.raw() 適合查詢結果能對應到 Model 欄位的情境:

# 基本用法——結果映射為 Model 實例
users = User.objects.raw('SELECT * FROM auth_user WHERE is_active = %s', [True])
for user in users:
    print(user.username)  # 可直接存取 Model 屬性

# 搭配 JOIN 查詢
articles = Article.objects.raw('''
    SELECT a.*, u.username AS author_name
    FROM blog_article a
    JOIN auth_user u ON a.author_id = u.id
    WHERE a.status = %s
    ORDER BY a.created_at DESC
''', ['published'])

for article in articles:
    print(f"{article.title} by {article.author_name}")

connection.cursor():完全控制 SQL

connection.cursor() 提供最大的彈性,適合回傳非 Model 格式的資料:

from django.db import connection
from django.utils import timezone
from datetime import timedelta

def get_monthly_stats():
    """查詢月度統計資料"""
    with connection.cursor() as cursor:
        cursor.execute("""
            SELECT
                DATE_TRUNC('month', created_at) AS month,
                COUNT(*) AS order_count,
                SUM(amount) AS total_revenue
            FROM orders_order
            WHERE created_at >= %s
            GROUP BY 1
            ORDER BY 1 DESC
        """, [timezone.now() - timedelta(days=365)])

        # 將結果轉為 dict 列表
        columns = [col[0] for col in cursor.description]
        return [dict(zip(columns, row)) for row in cursor.fetchall()]

SQL 注入防護

使用 Raw SQL 時,永遠不要使用字串格式化拼接 SQL,必須使用參數化查詢:

from django.db import connection

user_id = 42

# 危險!SQL 注入漏洞
cursor.execute(f"SELECT * FROM auth_user WHERE id = {user_id}")

# 危險!同樣有漏洞
cursor.execute("SELECT * FROM auth_user WHERE id = %s" % user_id)

# 安全!參數化查詢
cursor.execute("SELECT * FROM auth_user WHERE id = %s", [user_id])

Django 會自動處理參數的跳脫與型別轉換,確保惡意輸入無法改變 SQL 語句的結構。


資料庫函式

Django ORM 提供豐富的 資料庫函式(Database Functions) ,讓你在不寫 Raw SQL 的情況下使用資料庫層級的運算。

常用函式一覽

from django.db.models.functions import (
    Concat, Coalesce, Now, ExtractYear, ExtractMonth,
    Upper, Lower, Length, Trim, Left, Right,
    Cast, Round, Abs,
)
from django.db.models import Value, CharField, FloatField

# Concat:字串串接
from django.db.models.functions import Concat
users = User.objects.annotate(
    full_name=Concat('first_name', Value(' '), 'last_name')
)

# Coalesce:取第一個非 NULL 值(類似 SQL 的 COALESCE)
articles = Article.objects.annotate(
    display_name=Coalesce('nickname', 'username', Value('匿名'))
)

# Now:取得資料庫當前時間
from django.db.models.functions import Now
expired_items = Item.objects.filter(expires_at__lt=Now())

# ExtractYear / ExtractMonth:提取日期部分
articles_2026 = Article.objects.annotate(
    year=ExtractYear('created_at')
).filter(year=2026)

monthly_stats = Article.objects.annotate(
    month=ExtractMonth('created_at')
).values('month').annotate(count=Count('id'))

自訂資料庫函式

from django.db.models import Func

class ArrayLength(Func):
    """PostgreSQL array_length 函式"""
    function = 'array_length'
    template = '%(function)s(%(expressions)s, 1)'

# 使用自訂函式
products = Product.objects.annotate(
    tag_count=ArrayLength('tags')
).filter(tag_count__gte=3)

交易管理(Transaction Management)

交易(Transaction) 確保一組資料庫操作要麼全部成功,要麼全部回滾,是維護資料一致性的關鍵機制。Django 透過 transaction.atomic() 提供簡潔的交易管理 API。

transaction.atomic() 基本用法

from django.db import transaction

# 方法一:with 語句(推薦)
def transfer_money(from_account_id, to_account_id, amount):
    with transaction.atomic():
        # select_for_update() 鎖定資料列,防止並發衝突
        from_acc = Account.objects.select_for_update().get(pk=from_account_id)
        to_acc = Account.objects.select_for_update().get(pk=to_account_id)

        if from_acc.balance < amount:
            raise ValueError("餘額不足")

        from_acc.balance -= amount
        to_acc.balance += amount
        from_acc.save()
        to_acc.save()
        # 發生例外時,整個 block 自動回滾

# 方法二:裝飾器
@transaction.atomic
def create_order_with_items(order_data, items_data):
    order = Order.objects.create(**order_data)
    for item in items_data:
        OrderItem.objects.create(order=order, **item)
    return order

手動 Savepoint:巢狀交易

transaction.atomic() 支援巢狀使用,內層會建立 Savepoint(儲存點)

from django.db import transaction

def complex_operation():
    with transaction.atomic():                   # 外層:建立真正的交易
        obj1 = Model1.objects.create(name='obj1')

        try:
            with transaction.atomic():           # 內層:建立 Savepoint
                obj2 = Model2.objects.create(name='obj2')
                raise ValueError("內部操作失敗")  # 僅回滾此 Savepoint
        except ValueError:
            pass  # obj2 被回滾,但 obj1 保留

        obj3 = Model3.objects.create(name='obj3')  # 繼續正常執行
    # 最終結果:obj1 和 obj3 被提交,obj2 被回滾

on_commit:交易提交後執行

transaction.on_commit() 確保回呼函式只在交易成功提交後才執行,適合發送通知、觸發非同步任務等場景:

from django.db import transaction

def create_order(request):
    with transaction.atomic():
        order = Order.objects.create(
            customer=request.user,
            total=calculate_total(request)
        )
        # 確保 commit 後才發送通知(避免通知成功但 DB 回滾)
        transaction.on_commit(
            lambda: send_confirmation_email.delay(order.id)
        )
    return order

select_for_update() 悲觀鎖定

from django.db import transaction, DatabaseError

# NOWAIT:如果資料列已被鎖定,立即拋出例外
with transaction.atomic():
    try:
        product = Product.objects.select_for_update(nowait=True).get(pk=1)
        product.stock -= 1
        product.save()
    except DatabaseError:
        raise ValueError("庫存更新衝突,請重試")

# SKIP LOCKED:跳過已鎖定的資料列(適合任務佇列模式)
with transaction.atomic():
    tasks = Task.objects.select_for_update(skip_locked=True).filter(
        status='pending'
    )[:10]
    for task in tasks:
        process_task(task)

多資料庫設定

Django 支援同時連接多個資料庫,透過 DATABASE_ROUTERS 自動決定讀寫路由。

DATABASES 多個 key

# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'primary_db',
        'HOST': 'primary.db.internal',
    },
    'replica': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'primary_db',
        'HOST': 'replica.db.internal',
        'TEST': {'MIRROR': 'default'},  # 測試時鏡像主庫
    },
    'analytics': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'analytics_db',
        'HOST': 'analytics.db.internal',
    },
}

DATABASE_ROUTERS = ['myapp.routers.ReadWriteRouter', 'myapp.routers.AnalyticsRouter']

自訂 Router:讀寫分離

# myapp/routers.py

class ReadWriteRouter:
    """讀寫分離路由:寫入導向 Primary,讀取導向 Replica"""

    def db_for_read(self, model, **hints):
        if model._meta.app_label not in ('analytics',):
            return 'replica'
        return None

    def db_for_write(self, model, **hints):
        return 'default'

    def allow_relation(self, obj1, obj2, **hints):
        db_set = {'default', 'replica'}
        if obj1._state.db in db_set and obj2._state.db in db_set:
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        return db == 'default'


class AnalyticsRouter:
    """Analytics App 路由至獨立資料庫"""

    def db_for_read(self, model, **hints):
        if model._meta.app_label == 'analytics':
            return 'analytics'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == 'analytics':
            return 'analytics'
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label == 'analytics':
            return db == 'analytics'
        return None

手動指定資料庫

# 使用 .using() 手動指定資料庫(繞過 Router)
User.objects.using('replica').filter(is_active=True)
Analytics.objects.using('analytics').create(event='page_view')

PostgreSQL 特定功能

PostgreSQL 是 Django 生產環境的首選資料庫,Django 為其提供了許多專屬功能。

特殊欄位類型

from django.contrib.postgres.fields import ArrayField, HStoreField
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=200)

    # ArrayField:儲存陣列資料
    tags = ArrayField(
        models.CharField(max_length=50),
        default=list,
        blank=True
    )

    # JSONField:儲存 JSON 資料(Django 3.1+ 內建,不限 PostgreSQL)
    metadata = models.JSONField(default=dict, blank=True)

    # HStoreField:儲存鍵值對(需啟用 hstore 擴充)
    attributes = HStoreField(default=dict, blank=True)
# ArrayField 查詢
Product.objects.filter(tags__contains=['django'])     # 包含 'django'
Product.objects.filter(tags__overlap=['django', 'python'])  # 重疊
Product.objects.filter(tags__len=3)                   # 陣列長度為 3

# JSONField 查詢
Product.objects.filter(metadata__brand='Apple')       # JSON 欄位查詢
Product.objects.filter(metadata__specs__ram__gte=16)   # 巢狀欄位查詢

PostgreSQL 的全文搜尋功能強大,Django 透過 SearchVectorSearchQuery 提供完整支援:

from django.contrib.postgres.search import (
    SearchVector, SearchQuery, SearchRank, TrigramSimilarity
)

# 基本全文搜尋
articles = Article.objects.annotate(
    search=SearchVector('title', 'content')
).filter(search=SearchQuery('Django 部署'))

# 搜尋排名
vector = SearchVector('title', weight='A') + SearchVector('content', weight='B')
query = SearchQuery('Django 部署')
articles = Article.objects.annotate(
    rank=SearchRank(vector, query)
).filter(rank__gte=0.1).order_by('-rank')

# 模糊搜尋(Trigram Similarity,需啟用 pg_trgm 擴充)
articles = Article.objects.annotate(
    similarity=TrigramSimilarity('title', 'Djnago')  # 拼錯也能搜到
).filter(similarity__gt=0.3).order_by('-similarity')

資料庫效能監控

在生產環境中,持續監控資料庫效能是預防問題的關鍵。

Django Debug Toolbar(開發環境)

# settings.py(僅開發環境)
if DEBUG:
    INSTALLED_APPS += ['debug_toolbar']
    MIDDLEWARE = ['debug_toolbar.middleware.DebugToolbarMiddleware'] + MIDDLEWARE
    INTERNAL_IPS = ['127.0.0.1']

django-debug-toolbar 可以在每個頁面顯示執行的 SQL 查詢數量、耗時與重複查詢,是開發階段最實用的除錯工具。

django-silk(開發與測試環境)

# settings.py
INSTALLED_APPS += ['silk']
MIDDLEWARE = ['silk.middleware.SilkyMiddleware'] + MIDDLEWARE

django-silk 提供更詳細的效能分析,包含每個請求的 SQL 查詢、執行時間分佈與 Python 程式碼的效能剖析。

自訂 SQL 日誌(生產環境)

# settings.py
LOGGING = {
    'version': 1,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'level': 'DEBUG',       # 記錄所有 SQL 查詢
            'propagate': False,
        },
    },
}

查詢效能監控指標

監控項目建議閾值工具
單次請求 SQL 查詢數< 20django-debug-toolbar
單次查詢執行時間< 100msdjango-silk、EXPLAIN ANALYZE
連線池使用率< 80%pgBouncer SHOW POOLS
慢查詢> 1sPostgreSQL pg_stat_statements

總結

進階資料庫操作是 Django 應用邁向生產環境的必備知識,正確的設定與使用能大幅提升效能與可靠性。以下整理本篇的重點:

  1. 連線池 減少重複建立連線的開銷——CONN_MAX_AGE 提供最簡單的連線持久化,django-db-connection-pool 實現真正的連線池,pgBouncer 適合大規模生產環境
  2. Raw SQL 讓你在 ORM 無法表達的場景直接操作資料庫——Manager.raw() 回傳 Model 實例,connection.cursor() 提供完全控制,但必須使用參數化查詢防止 SQL 注入
  3. 資料庫函式 讓你在不寫 Raw SQL 的情況下使用資料庫層級運算——ConcatCoalesceNowExtractYear 等函式涵蓋常見需求
  4. 交易管理 確保資料一致性——transaction.atomic() 支援巢狀 Savepoint,on_commit() 確保提交後才執行副作用,select_for_update() 實現悲觀鎖定
  5. 多資料庫設定 實現讀寫分離——DATABASE_ROUTERS 自動路由讀寫請求到不同資料庫
  6. PostgreSQL 特定功能 提供 ArrayFieldJSONField、全文搜尋等強大功能,是 Django 生產環境的首選資料庫
  7. 資料庫效能監控 應從開發階段就開始——django-debug-toolbardjango-silk 幫助你即時發現效能瓶頸

下一篇我們將介紹 Django 的 套件生態系 ,盤點各類必裝與推薦的第三方套件,幫助你快速建立功能完整的 Django 專案。

BenZ Software Developer

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