「加一个带默认值的字段」会不会锁表?这取决于默认值是什么。这篇在 200 万行的表上,把常量、可调用、db_default、volatile 函数四种默认值各加一遍,看 Django 发出的 SQL 和耗时,再演示分三步的安全做法。
AddField(default=0) 常量默认值 0.25 s
SQL: ALTER TABLE "lab_big" ADD COLUMN "c_const" integer DEFAULT 0 NOT NULL;
SQL: ALTER TABLE "lab_big" ALTER COLUMN "c_const" DROP DEFAULT;
AddField(null=True) 没有默认值 0.23 s
SQL: ALTER TABLE "lab_big" ADD COLUMN "c_null" integer NULL;
AddField(UUID, default=uuid.uuid4 可调用) 0.24 s
SQL: ALTER TABLE "lab_big" ADD COLUMN "c_uuid" uuid DEFAULT '85f408302477460da172953935ece872'::uuid NULL;
SQL: ALTER TABLE "lab_big" ALTER COLUMN "c_uuid" DROP DEFAULT;
→ 可调用默认值只算一次: 1 个不同的 uuid(共 200 万行)
AddField(db_default=5) 数据库层面的默认值 0.24 s
SQL: ALTER TABLE "lab_big" ADD COLUMN "c_dbdef" integer DEFAULT 5 NOT NULL;
→ 之后表上该列的默认值: 5
AddField(db_default=Random()) volatile 函数默认值 1.52 s
SQL: ALTER TABLE "lab_big" ADD COLUMN "c_random" double precision DEFAULT (RANDOM()) NOT NULL;
→ 各行的值是否不同: tPostgreSQL 11 起,加一个带「常量」默认值的列不再重写表(默认值存在目录里,读到旧行时补上)。所以前两个都是毫秒到亚秒级。本机总用时里约 0.25 s 是 Django 命令自身的启动。
步骤一:AddField(null=True) 0.25 s
SQL: ALTER TABLE "lab_big" ADD COLUMN "s_null" integer NULL;
步骤二:分 20 批(每批 10 万行,每批自己提交)回填:4.25 s
步骤三:AlterField → NOT NULL:0.80 s对确实要「每行不同」或者「基于其它列计算」的新列:先加一个可空的列(快)、分批回填(每批一个事务,可以随时中断)、最后再加 NOT NULL(会扫全表一次,见第 59 篇)。批的大小要根据写入负载调。