因为我的机器使用的是mysql容器,所以可以打印环境环境获取登录账密
bash
docker exec -it db printenv | grep MYSQL
下面用「Django + MySQL」的视角,把 开发/上线 99% 会踩的 MySQL 典型错误 汇总成一张"踩坑地图"。
每个错误都给出:
- 异常堆栈(Django 真实报错)
- 根因一句话
- 复现代码(最小 Django 示例)
- 修复方案(含 ORM 写法或配置)
复制即可复现,跟着敲一遍,以后看到同样日志秒定位。
0. 环境统一
- Django 4.2 + mysqlclient 2.2 + MySQL 8.0
- 默认配置(不改 SQL_MODE)以便暴露问题
python
# settings.py
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": "django_err",
"USER": "root",
"PASSWORD": "password",
"HOST": "127.0.0.1",
"PORT": "3306",
"OPTIONS": {"init_command": "SET sql_mode='STRICT_TRANS_TABLES'"}, # 严格模式先打开
}
}
1. Duplicate entry ------ 主键/唯一键冲突
异常
django
django.db.utils.IntegrityError: (1062, "Duplicate entry 'tom' for key 'users.username'")
复现
python
class User(models.Model):
username = models.CharField(max_length=50, unique=True)
User.objects.create(username="tom")
User.objects.create(username="tom") # 再敲一次
修复
python
# 代码层先查后插 或 数据库层 on conflict
user, created = User.objects.get_or_create(username="tom")
2. Data too long ------ 列长度超限
异常
django
django.db.utils.DataError: (1406, "Data too long for column 'title' at row 1")
复现
python
class Article(models.Model):
title = models.CharField(max_length=10)
Article.objects.create(title="12345678901") # 11 个字符
修复
python
# 要么改模型
title = models.CharField(max_length=255)
# 要么截断
Article.objects.create(title=long_title[:10])
3. DoesNotExist ------ 查询为空抛异常
异常
django
django.core.exceptions.ObjectDoesNotExist: User matching query does not exist.
复现
python
user = User.objects.get(pk=999) # 没有这条记录
修复
python
# 用 filter + first 或 get_object_or_404
user = User.objects.filter(pk=999).first() # 返回 None
from django.shortcuts import get_object_or_404
user = get_object_or_404(User, pk=999)
4. Field cannot be null ------ 非空约束
异常
django
django.db.utils.IntegrityError: (1048, "Column 'email' cannot be null")
复现
python
class User(models.Model):
email = models.EmailField() # 默认 blank=False, null=False
User.objects.create(username="tom") # 没给 email
修复
python
# 1. 允许空
email = models.EmailField(blank=True, null=True)
# 2. 给默认值
email = models.EmailField(default="")
# 3. 代码里传值
User.objects.create(username="tom", email="tom@example.com")
5. Incorrect string value ------ 表情符/生僻字乱码
异常
django
django.db.utils.OperationalError: (1366, "Incorrect string value: '\\xF0\\x9F\\x98\\x80...' for column 'nickname'")
复现
python
User.objects.create(nickname="😀") # utf8mb3 存不进 emoji
修复
- 数据库/表/列字符集改成
utf8mb4:
sql
ALTER DATABASE django_err CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
- 连接层也指定:
python
"OPTIONS": {"charset": "utf8mb4"},
再插入 emoji 就正常了。
6. Lost connection ------ 长查询被 kill
异常
django
django.db.utils.OperationalError: (2013, 'Lost connection to MySQL server during query')
复现
python
# 故意搞个大表全表扫 + 不睡觉
User.objects.all().select_related("profile")[0:100000] # 100 万次 join
修复
- 优化 SQL(加索引、分页、只取所需列)
- 调大
wait_timeout/interactive_timeout(治标不治本) - 用
iterator()分批拉:
python
for user in User.objects.all().iterator(chunk_size=2000):
...
7. Too many connections ------ 连接打满
异常
django
django.db.utils.OperationalError: (1040, 'Too many connections')
复现
并发压测 ab -n 10000 -c 200 ... 超过 max_connections=151 即可。
修复
- 数据库层:温和涨上限
sql
SET GLOBAL max_connections = 300;
- 应用层:用连接池 + 合理并发
python
# settings.py 里加
DATABASES["default"]["CONN_MAX_AGE"] = 600 # 复用连接
- 代码里别泄露连接(长时间不 close)
8. Deadlock found ------ 并发死锁
异常
django
django.db.utils.OperationalError: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
复现
python
# 两个 view 同时
@transaction.atomic
def debit(request):
a = Account.objects.select_for_update().get(id=1)
b = Account.objects.select_for_update().get(id=2)
...
# 另一个线程反向顺序
@transaction.atomic
def debit2(request):
b = Account.objects.select_for_update().get(id=2) # 先 2
a = Account.objects.select_for_update().get(id=1) # 后 1
交叉拿锁 → 死锁。
修复
- 固定加锁顺序(总是 1→2)
- 精简事务范围,尽快提交
- 捕获重试(Django 3.2+ 自动重试
max_retries=3)
9. IntegrityError ------ 外键级联失败
异常
django
django.db.utils.IntegrityError: (1452, 'Cannot add or update a child row: a foreign key constraint fails ...)
复现
python
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
Order.objects.create(user_id=9999) # 不存在 user 9999
修复
python
# 保证外键值存在
user = User.objects.get(pk=user_id)
Order.objects.create(user=user)
10. ProgrammingError ------ 拼错 raw SQL
异常
django
django.db.utils.ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual ...")
复现
python
from django.db import connection
with connection.cursor() as c:
c.execute("SELCT * FROM users WHERE id = %s", [1]) # SELCT 拼错
修复
- 用 ORM 先顶一波,必须 raw 时开
sql_mode=STRICT并打印语句再执行。
一键速查表(打印贴墙)
| 错误码 | Django 异常 | 典型场景 | 最快修复 |
|---|---|---|---|
| 1062 | IntegrityError | 唯一键冲突 | get_or_create |
| 1406 | DataError | 列太长 | 加长/截断 |
| 1048 | IntegrityError | 非空未给 | 设默认值 |
| 1366 | OperationalError | emoji 乱码 | 改 utf8mb4 |
| 2013 | OperationalError | 长查询 | 索引+分页 |
| 1040 | OperationalError | 连接打满 | 连接池+涨上限 |
| 1213 | OperationalError | 死锁 | 固定顺序+重试 |
结语
把上面 10 个案例挨个 python manage.py shell 跑一遍,再遇到一样日志就能秒定位。
建议收藏 + 星标,下次面试/上线/救火,直接翻表。