bigywater.tech
Django · 59

大表结构迁移的风险

迁移在开发库上一秒完成,到了生产大表上会锁多久?这篇造一张 200 万行的表,让一个线程持续写入,再对它执行 7 种常见迁移操作,把「写入被阻塞了多久」量出来。

1220 ms
integer → bigint(重写整表)期间,后台写入最长被阻塞的时间
587 → 1 ms
普通 CREATE INDEX → CREATE INDEX CONCURRENTLY 期间写入最长阻塞
1 ms
加带常量默认值的列(PostgreSQL)几乎不阻塞

7 种操作实测

实测 · 本机执行输出
  操作                                             结果
  AddField(int, default=0, NOT NULL)             迁移总用时   0.28 s   期间写入最长一次        1 ms(平时中位 0.2 ms)
  AddField(int, null=True)                       迁移总用时   0.27 s   期间写入最长一次        4 ms(平时中位 0.3 ms)
  AlterField varchar(20) → varchar(60)           迁移总用时   0.29 s   期间写入最长一次        2 ms(平时中位 0.3 ms)
  AlterField integer → bigint(重写整张表)             迁移总用时   1.51 s   期间写入最长一次     1220 ms(平时中位 0.2 ms)
  AlterField null → NOT NULL(扫全表)                迁移总用时   0.56 s   期间写入最长一次      286 ms(平时中位 0.2 ms)
  AddIndex(普通 CREATE INDEX)                      迁移总用时   0.87 s   期间写入最长一次      587 ms(平时中位 0.4 ms)
  AddIndexConcurrently(CREATE INDEX CONCURRENTLY) 迁移总用时   0.70 s   期间写入最长一次        1 ms(平时中位 0.3 ms)

「迁移总用时」包含 Django 命令自己的启动(约 0.25 s)。「期间写入最长一次」是后台线程在迁移进行期间(前后各留 0.3 s 余量)观察到的单次 UPDATE 最长耗时,平时中位数约 0.2–0.4 ms。

怎么办

分步演示:迁移与写入阻塞

大表迁移
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。PostgreSQL 16.15(本机临时实例,端口 9434,fsync 关闭),200 万行;后台线程 psycopg 直连每 5 ms 一次 UPDATE;每项只测一次
    • 没有验证:MySQL(在线 DDL 算法)、更大或更宽的表、多个并发写入者、SET lock_timeout 的保护效果、NOT VALID 约束两步法、CREATE INDEX CONCURRENTLY 失败后的清理
    京ICP备2026067502号-1京公网安备11010502063487号