bigywater.tech
Django · 60

字段默认值为什么可能锁表

「加一个带默认值的字段」会不会锁表?这取决于默认值是什么。这篇在 200 万行的表上,把常量、可调用、db_default、volatile 函数四种默认值各加一遍,看 Django 发出的 SQL 和耗时,再演示分三步的安全做法。

0.25 vs 1.52 s
常量默认值 vs volatile 函数默认值(RANDOM())加列的用时
1 个
default=uuid.uuid4 加列后,200 万行里不同 uuid 的个数(callable 默认值只算一次)
DROP DEFAULT
Django 加带 default 的列后会把数据库默认值去掉;db_default 才保留

各种默认值的 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;
   → 各行的值是否不同: t

PostgreSQL 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 篇)。批的大小要根据写入负载调。

分步演示:默认值与锁

字段默认值
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。PostgreSQL 16.15(本机临时实例,端口 9435,fsync 关闭),200 万行;每项测一次;没有并发写入(锁的阻塞见第 59 篇)
    • 没有验证:MySQL / SQLite 的行为、PostgreSQL 10 及更老版本(加带默认值的列会重写整表)、加 NOT NULL 前用 CHECK (… IS NOT NULL) NOT VALID 的两步法、分批回填期间的复制延迟
    京ICP备2026067502号-1京公网安备11010502063487号