建表时必须同时设 NOT NULL 和默认值以确保语义一致;外键字段名应反映业务角色而非模型关系;JSONField 需按数据库能力谨慎使用;时间字段统一存 UTC,时区转换延后至展示层。
建表前必须确认 NOT NULL 和默认值的语义是否一致
很多团队在 django 或 sqlalchemy 里写 models.py 时,习惯给字段加 default=0 或 default="",但没同步设 null=false。结果迁移生成的 sql 里字段是 null 允许的,数据库层根本拦不住空值写入——应用层的 default 只在 orm 创建实例时生效,绕过 orm 直接 insert 就失效。
实操建议:
- 所有业务上“不允许为空”的字段,必须同时声明
null=False(Django)或nullable=False(SQLAlchemy),不能只靠 default -
DateTimeField(auto_now_add=True)自动设值,但仍是null=True默认,得显式写null=False - PostgreSQL 对
NOT NULL约束更严格,MySQL 8.0+ 才默认拒绝空字符串插入到NOT NULL VARCHAR字段,老版本容易漏检
外键字段名别硬套 xxx_id,先看 JOIN 场景和索引需求
生成迁移时自动加 _id 后缀看似省事,但实际查表时经常要 JOIN user ON order.user_id = user.id。如果字段名就是 user_id,那 SELECT * FROM order JOIN user ON order.user_id = user.id 还算清晰;可一旦模型里写成 creator = ForeignKey(User),ORM 生成的字段名可能是 creator_id,而业务语义上它就是“用户”,不是“创建者 ID”。命名混淆会拖慢 SQL 审查和 DBA 协作。
实操建议:
- 字段名优先反映业务角色(如
owner_id、tenant_id),而非模型关系名(user_id可能是 owner、tenant、assignee 中任意一个) -
ForeignKey字段必须配db_constraint=True(Django 默认),否则迁移不建外键约束,级联删除/更新全失效 - 复合查询高频的外键,比如
(status, tenant_id),要手动加联合索引,ORM 不会自动推导
JSONField 别当万金油,先问清楚查得多不多、要不要校验、是否跨数据库
PostgreSQL 的 JSONB 支持路径查询和 GIN 索引,MySQL 5.7+ 的 JSON 类型也支持 -> 操作符,但 SQLite 的 JSON1 扩展默认不启用,Django 测试用 SQLite 时 JSONField 实际存成 TEXT——看着能跑,上线 PG 就发现索引没生效、模糊查询变全表扫。
实操建议:
- 仅存储、不查询内部字段?用
TextField+ 应用层序列化更稳 - 需要按 key 查(如
data->>'status')?确认目标数据库版本支持原生 JSON 索引,且 ORM 迁移真生成了USING GIN(PG)或GENERATED COLUMN + INDEX(MySQL) - Django
JSONField在filter(data__key=value)场景下,不同后端生成的 SQL 差异极大,务必在真实 DB 上print(str(qs.query))看一眼
时间字段统一用 UTC 存储,时区转换交给应用层或视图层
把 DateTimeField(timezone=True) 当保险丝用很危险:Django 默认用 pytz 处理时区,但 PostgreSQL 的 TIMESTAMP WITH TIME ZONE 实际存的是 UTC 时间戳,显示时按 session timezone 转换。如果前端传来带时区的 ISO 时间(如 "2024-06-15T14:30:00+08:00"),Django 会转成 UTC 存,但若中间有 Nginx 或负载均衡改了 Accept-Language 或 header,或者用了错误的 TIME_ZONE 配置,读出来的时间就偏了 8 小时。
实操建议:
- 所有
DateTimeField设timezone=True,但settings.TIME_ZONE固定为"UTC",禁止设成"Asia/Shanghai"类本地时区 - 前端传时间一律要求 ISO 8601 UTC 格式(
"2024-06-15T06:30:00Z"),后端不做任何astimezone()转换 - 报表类场景需本地时区展示?在 DRF Serializer 或模板里用
datetime.astimezone(pytz.timezone("Asia/Shanghai")),不在模型层做
最麻烦的不是语法对不对,是开发、测试、生产三套环境的时区配置不一致,连日志里打印的时间都对不上——这种问题没法靠单元测试覆盖,只能靠部署检查清单卡住。










