1. Django ORM 单表操作基础概念
Django ORM(对象关系映射)是Django框架中最强大的特性之一,它允许开发者使用Python代码而不是SQL来操作数据库。单表操作是ORM中最基础但最重要的部分,掌握好单表操作是理解复杂查询和多表关系的前提。
1.1 什么是单表实例
单表实例指的是只涉及一个数据库表的操作,不包含外键关联、多对多关系等复杂场景。在实际开发中,约60%的数据库操作都是单表操作,包括:
- 创建新记录
- 查询现有记录
- 更新记录
- 删除记录
- 简单过滤和排序
1.2 ORM与原生SQL的对比
使用ORM而不是原生SQL有以下几个优势:
- 开发效率高:用Python语法代替SQL,减少上下文切换
- 安全性好:自动处理SQL注入等安全问题
- 可移植性强:同一套代码可适配不同数据库后端
- 维护方便:模型变更只需修改Python代码,不用到处改SQL
但ORM也有其局限性,在复杂查询时性能可能不如优化过的原生SQL。因此在实际项目中,我们通常采用80% ORM + 20% 原生SQL的混合模式。
2. 定义模型与数据库迁移
2.1 创建基础模型
假设我们要创建一个博客系统,首先定义文章模型:
python复制from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200, verbose_name="标题")
content = models.TextField(verbose_name="内容")
pub_date = models.DateTimeField(auto_now_add=True, verbose_name="发布时间")
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
is_published = models.BooleanField(default=False, verbose_name="是否发布")
view_count = models.PositiveIntegerField(default=0, verbose_name="浏览量")
class Meta:
db_table = "blog_articles" # 自定义表名
ordering = ["-pub_date"] # 默认排序
verbose_name = "文章" # 单数名称
verbose_name_plural = "文章" # 复数名称
def __str__(self):
return self.title
2.2 字段类型详解
Django提供了丰富的字段类型,常用的有:
- CharField:短文本,必须指定max_length
- TextField:长文本,不限长度
- IntegerField/PositiveIntegerField:整数
- BooleanField:布尔值
- DateTimeField/DateField:日期时间
- EmailField/URLField:特殊格式文本
- FileField/ImageField:文件上传
每个字段都可以设置参数:
null:数据库层面是否允许NULLblank:表单验证是否允许空值default:默认值unique:是否唯一db_index:是否创建索引
2.3 数据库迁移
定义模型后,需要生成并应用迁移:
bash复制# 生成迁移文件
python manage.py makemigrations
# 查看生成的SQL(可选)
python manage.py sqlmigrate blog 0001
# 应用迁移
python manage.py migrate
提示:开发过程中应频繁进行迁移,每次模型变更后都应生成新的迁移文件。迁移文件应该纳入版本控制。
3. 基础CRUD操作
3.1 创建记录
创建新记录有几种方式:
python复制# 方法1:create()快捷方式
article = Article.objects.create(
title="Django ORM指南",
content="这是一篇关于Django ORM的详细指南...",
is_published=True
)
# 方法2:先实例化后保存
article = Article(
title="Python高级技巧",
content="分享一些Python的高级用法..."
)
article.save() # 此时才会真正写入数据库
# 方法3:批量创建
articles = [
Article(title=f"文章{i}", content=f"内容{i}")
for i in range(10)
]
Article.objects.bulk_create(articles) # 一次SQL插入多条记录
3.2 查询记录
基本查询方法
python复制# 获取所有记录
articles = Article.objects.all()
# 获取单条记录(主键查询)
article = Article.objects.get(pk=1)
# 获取第一条记录
first_article = Article.objects.first()
# 获取最后一条记录
last_article = Article.objects.last()
# 记录计数
count = Article.objects.count()
常用查询条件
python复制# 精确匹配
Article.objects.filter(title="Django ORM指南")
# 包含查询
Article.objects.filter(title__contains="Django")
# 开头/结尾查询
Article.objects.filter(title__startswith="Django")
Article.objects.filter(title__endswith="指南")
# 空值查询
Article.objects.filter(content__isnull=True)
# 范围查询
Article.objects.filter(view_count__range=(100, 500))
# 日期查询
from datetime import datetime, timedelta
Article.objects.filter(pub_date__gte=datetime.now()-timedelta(days=7))
3.3 更新记录
python复制# 方法1:先查询后修改
article = Article.objects.get(pk=1)
article.title = "修改后的标题"
article.save()
# 方法2:直接批量更新
Article.objects.filter(is_published=False).update(is_published=True)
# 方法3:F()表达式避免竞态条件
from django.db.models import F
Article.objects.filter(pk=1).update(view_count=F('view_count') + 1)
3.4 删除记录
python复制# 删除单条记录
article = Article.objects.get(pk=1)
article.delete()
# 批量删除
Article.objects.filter(is_published=False).delete()
4. 高级查询技巧
4.1 链式查询
Django ORM的查询是惰性的,可以链式调用:
python复制articles = Article.objects.filter(
is_published=True
).exclude(
title__contains="测试"
).order_by(
"-pub_date"
)[:10] # 只在实际使用时才会执行查询
4.2 Q对象复杂查询
python复制from django.db.models import Q
# OR条件查询
Article.objects.filter(
Q(title__contains="Django") | Q(content__contains="ORM")
)
# AND条件查询
Article.objects.filter(
Q(is_published=True) & Q(view_count__gte=100)
)
# 复杂组合
Article.objects.filter(
(Q(pub_date__year=2023) | Q(pub_date__year=2022)) &
~Q(title__startswith="旧")
)
4.3 聚合与分组
python复制from django.db.models import Count, Sum, Avg, Max, Min
# 简单聚合
Article.objects.aggregate(
total_views=Sum('view_count'),
avg_views=Avg('view_count'),
max_views=Max('view_count')
)
# 分组统计
Article.objects.values('is_published').annotate(
count=Count('id'),
avg_views=Avg('view_count')
)
4.4 性能优化技巧
-
select_related:外键提前加载(一对一、多对一)
python复制# 假设Article有author外键 Article.objects.select_related('author').all() -
prefetch_related:多对多、一对多预加载
python复制# 假设Article有tags多对多字段 Article.objects.prefetch_related('tags').all() -
only/defer:延迟加载字段
python复制# 只加载指定字段 Article.objects.only('title', 'pub_date') # 排除指定字段 Article.objects.defer('content') -
iterator:大数据集流式处理
python复制for article in Article.objects.iterator(): # 处理article,内存友好
5. 实际应用中的经验分享
5.1 常见问题与解决方案
问题1:N+1查询问题
这是ORM中最常见的性能问题。例如:
python复制# 错误方式:会导致N+1次查询
for article in Article.objects.all():
print(article.author.name) # 每次循环都查询一次author
# 正确方式:使用select_related
for article in Article.objects.select_related('author').all():
print(article.author.name) # 只查询一次
问题2:批量操作内存溢出
处理大量数据时:
python复制# 错误方式:一次性加载所有数据
articles = list(Article.objects.all()) # 可能内存不足
# 正确方式1:使用iterator()
for article in Article.objects.iterator():
process(article)
# 正确方式2:分页处理
page_size = 1000
for i in range(0, Article.objects.count(), page_size):
articles = Article.objects.all()[i:i+page_size]
bulk_process(articles)
5.2 最佳实践建议
- 始终指定ordering:避免查询结果顺序不确定
- 谨慎使用get():可能抛出DoesNotExist或MultipleObjectsReturned异常
- 批量操作优先:bulk_create、bulk_update等
- 合理使用索引:高频查询字段添加db_index=True
- 定期清理迁移:项目稳定后可以squashmigrations
5.3 调试技巧
-
查看生成的SQL:
python复制print(Article.objects.filter(title__contains="Django").query) -
使用django-debug-toolbar分析查询
-
配置数据库日志:
python复制LOGGING = { 'version': 1, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'level': 'DEBUG', 'handlers': ['console'], }, }, }
6. 单表操作实战案例
6.1 文章发布工作流
python复制def publish_article(article_id):
try:
article = Article.objects.get(pk=article_id)
if not article.is_published:
article.is_published = True
article.pub_date = timezone.now()
article.save()
return True
return False
except Article.DoesNotExist:
logger.error(f"文章{article_id}不存在")
return False
6.2 热门文章排行榜
python复制def get_top_articles(days=30, limit=10):
cutoff = timezone.now() - timedelta(days=days)
return Article.objects.filter(
is_published=True,
pub_date__gte=cutoff
).order_by('-view_count')[:limit]
6.3 文章搜索功能
python复制def search_articles(keyword):
return Article.objects.filter(
Q(title__icontains=keyword) |
Q(content__icontains=keyword),
is_published=True
).select_related('author').order_by('-pub_date')
6.4 定时统计任务
python复制from django.db import connection
def generate_article_stats():
with connection.cursor() as cursor:
cursor.execute("""
SELECT
DATE(pub_date) as day,
COUNT(*) as count,
SUM(view_count) as total_views
FROM blog_articles
WHERE is_published = TRUE
GROUP BY DATE(pub_date)
ORDER BY day DESC
LIMIT 30
""")
return dictfetchall(cursor) # 自定义函数将结果转为字典
