Django迁移卡住主因是底层SQL锁表,非代码错误:PostgreSQL的ALTER需ACCESS EXCLUSIVE锁,MySQL旧版本ADD COLUMN全表锁;解决需拆步操作、手写CONCURRENTLY索引、升级DB并配置INSTANT算法,生产前必在从库试跑、用--plan预览、对大表禁用自动迁移。

大规模迁移时卡住、超时、连接被拒绝,大概率不是代码写错了,而是数据库锁住了——尤其是 PostgreSQL 或 MySQL 在执行 ALTER TABLE 类操作时会锁整表,Django 的迁移文件一旦包含字段增删、类型变更、索引添加,就可能触发它。
为什么 migrate 会锁表?
Django 迁移本身不加锁,但底层 SQL 会。比如:
-
ADD COLUMN在 MySQL 5.6 之前是全表锁;5.7+ 支持ALGORITHM=INPLACE,但仍可能锁写 -
ALTER COLUMN TYPE在 PostgreSQL 中默认需要ACCESS EXCLUSIVE锁,阻塞所有读写 -
CREATE INDEX CONCURRENTLY不锁表,但 Django 默认不用这个,除非你手动写迁移
所以不是 Django 有问题,是你跑的那条 SQL 在数据库层面卡住了。
如何绕过或缓解锁表?
核心思路:不让 Django 自动生成危险 SQL,或把大操作拆成安全步骤。
立即学习“Python免费学习笔记(深入)”;
- 对 MySQL:升级到 8.0+,并在
settings.py中配置OPTIONS = {"init_command": "SET SESSION alter_algorithm='INSTANT'"}(仅限支持 INSTANT 的变更,如加列) - 对 PostgreSQL:用
RunSQL手写带CONCURRENTLY的建索引语句,避免 Django 自动生成的CREATE INDEX - 字段改非空?别直接
blank=False, null=False—— 先加字段、再用RunPython填默认值、最后改约束,三步拆开 - 删字段?先
makemigrations生成删除语句,再手动注释掉RemoveField操作,改用RunSQL分批清空 + 删除,避免长事务
怎么判断是不是锁导致的迁移失败?
看错误日志里有没有这些关键词:
-
Lock wait timeout exceeded(MySQL) -
database is locked(SQLite,少见但明确) -
could not serialize access due to concurrent update(PostgreSQL 的序列化失败,常因长事务) - migrate 命令卡住超过 2 分钟没输出,且
ps aux | grep postgres或SHOW PROCESSLIST显示有ALTER/CREATE INDEX状态为Waiting for table metadata lock
这时候别 Ctrl+C 强退,先查锁源:SELECT * FROM pg_locks WHERE granted = false;(PG)或 SELECT * FROM information_schema.INNODB_TRX\G(MySQL),再 kill 掉阻塞者。
生产环境大规模迁移必须做的三件事
不是“建议”,是上线前必须确认:
- 在从库上先试跑迁移(只读库不会锁主库,但能验证 SQL 是否合法)
- 用
python manage.py migrate --plan看清楚每一步执行什么,尤其注意是否含ALTER TABLE ... ADD COLUMN或CREATE INDEX - 对 >100 万行的表,禁用 Django 自动迁移,改用
django.db.migrations.RunSQL+ 数据库原生命令(如 MySQL 的pt-online-schema-change或 PG 的pg_repack)
真正麻烦的从来不是“怎么让 migrate 跑通”,而是“怎么让它跑完还不影响线上读写”。锁表问题一旦发生,往往已经晚了——得在写模型变更时就预判,而不是等 migrate 报错才翻文档。


















