# Django中怎么執行原生SQL
## 前言
在Django開發中,ORM(對象關系映射)提供了強大的數據操作能力,能夠滿足90%以上的數據庫操作需求。但在某些特殊場景下(如復雜報表查詢、性能優化、數據庫特性使用等),我們可能需要直接執行原生SQL語句。本文將全面介紹Django中執行原生SQL的多種方式及其最佳實踐。
---
## 一、為什么需要執行原生SQL
### 1.1 ORM的局限性
雖然Django ORM功能強大,但在以下場景可能力不從心:
- 復雜多表連接查詢
- 數據庫特定函數(如PostgreSQL的JSON操作)
- 需要精細控制SQL執行計劃
- 批量操作性能優化
### 1.2 性能考量
某些復雜查詢使用原生SQL可能比ORM轉換后的查詢效率更高。例如:
```python
# ORM方式(可能產生N+1查詢)
books = Book.objects.filter(author__name='魯迅').select_related('author')
# 原生SQL可能更高效
"SELECT * FROM books_book INNER JOIN books_author ON books_book.author_id = books_author.id WHERE books_author.name = '魯迅'"
最基礎的原生SQL執行方式,返回模型實例:
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=100)
author = models.CharField(max_length=50)
# 執行原生查詢
books = Book.objects.raw('SELECT * FROM books_book WHERE author = %s', ['魯迅'])
for book in books:
print(book.name)
# 位置參數
Book.objects.raw('SELECT * FROM books_book WHERE id = %s', [1])
# 命名參數
Book.objects.raw('SELECT * FROM books_book WHERE id = %(id)s', {'id': 1})
需要直接訪問數據庫連接時使用:
from django.db import connection
def my_custom_sql():
with connection.cursor() as cursor:
# 執行查詢
cursor.execute("SELECT * FROM books_book WHERE author = %s", ['魯迅'])
# 獲取結果方式1:字典格式
columns = [col[0] for col in cursor.description]
results = [dict(zip(columns, row)) for row in cursor.fetchall()]
# 獲取結果方式2:原始元組
# results = cursor.fetchall()
return results
connections['別名']
)from django.db import transaction
with transaction.atomic():
with connection.cursor() as cursor:
cursor.execute("UPDATE books_book SET price = price * 1.1")
Django提供了一些便捷的游標方法:
from django.db import connection
def get_book_count():
with connection.cursor() as cursor:
cursor.execute("SELECT COUNT(*) FROM books_book")
row = cursor.fetchone()
return row[0]
fetchone()
:獲取單條記錄fetchmany(size)
:獲取指定數量記錄fetchall()
:獲取所有記錄executemany()
:批量執行(適合批量插入)在settings.py配置多個數據庫時:
from django.db import connections
with connections['replica'].cursor() as cursor:
cursor.execute("SELECT * FROM books_book")
# 處理結果...
以MySQL為例:
with connection.cursor() as cursor:
cursor.callproc('my_stored_procedure', [param1, param2])
results = cursor.fetchall()
對于大數據量查詢:
def large_query():
with connection.cursor() as cursor:
cursor.execute("SELECT * FROM large_table")
while True:
batch = cursor.fetchmany(1000) # 每次獲取1000條
if not batch:
break
process_batch(batch)
extra()
方法過渡ORM與原生SQL的折中方案:
Book.objects.extra(
where=["author = %s"],
params=['魯迅'],
select={'lower_name': 'LOWER(name)'}
)
錯誤示范:
# 危險!容易導致SQL注入
query = "SELECT * FROM books_book WHERE author = '%s'" % user_input
正確做法:
# 使用參數化查詢
cursor.execute("SELECT * FROM books_book WHERE author = %s", [user_input])
即使使用參數化查詢,也應驗證輸入:
from django.core.exceptions import ValidationError
def validate_author_name(name):
if not name.isalpha(): # 簡單示例
raise ValidationError("Invalid author name")
with
語句)確保連接關閉使用executemany
提高批量插入效率:
data = [(1, 'Book1'), (2, 'Book2')]
with connection.cursor() as cursor:
cursor.executemany("INSERT INTO books_book VALUES (%s, %s)", data)
對于復雜查詢,可以在SQL中指定索引:
cursor.execute("SELECT * FROM books_book USE INDEX (author_index) WHERE author = %s", ['魯迅'])
from django.test import TestCase
class SQLTests(TestCase):
def test_raw_query(self):
with self.assertNumQueries(1):
list(Book.objects.raw("SELECT * FROM books_book"))
在settings.py中啟用:
LOGGING = {
'version': 1,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
}
},
'loggers': {
'django.db.backends': {
'level': 'DEBUG',
'handlers': ['console'],
}
}
}
with connection.cursor() as cursor:
cursor.execute("EXPLN ANALYZE SELECT * FROM books_book")
print(cursor.fetchall())
在轉向原生SQL前,可嘗試:
- select_related()
/ prefetch_related()
- annotate()
聚合
- F()
表達式
- 自定義Manager/QuerySet
django-extra-views
:增強的ORM功能django-sql-explorer
:SQL查詢工具雖然Django ORM足夠強大,但掌握原生SQL執行能力仍是Django開發者的重要技能。合理使用原生SQL可以在保持Django優勢的同時,解決特定場景下的特殊需求。關鍵是要: 1. 優先考慮ORM解決方案 2. 必要時謹慎使用原生SQL 3. 始終注意安全性和性能 4. 編寫清晰的文檔和測試
通過本文介紹的各種方法和最佳實踐,希望您能在Django項目中游刃有余地處理各種數據訪問需求。 “`
注:本文實際約2650字(含代碼示例),完整覆蓋了Django執行原生SQL的主要技術點。根據具體需求,可適當調整各部分篇幅。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。