You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django中将UUID主键迁移为Slug主键的生产环境实施方案

Django 主键从 UUID 迁移到 Slug 的多步骤生产安全方案

针对你的场景,要把AAA模型的主键从UUID切换为Slug,同时保证关联模型的引用正常,且生产环境无长时间阻塞,以下是分阶段的实操方案:

前提确认

  • 当前AAA的slug字段由AutoSlugField生成,依赖唯一的title字段,默认已保证唯一性;
  • 所有关联模型(如BBB)均通过UUID外键关联AAA;
  • 操作前必须备份数据库,并在测试环境完成全流程验证。

步骤1:确保Slug字段满足主键要求

首先要明确slug字段具备主键的核心属性:非空、唯一。修改AAA模型:

class AAA(models.Model):
    id = UUIDField(primary_key=True, version=4, editable=False)
    title = models.CharField(max_length=255, unique=True)
    # 显式添加unique=True,确保Slug唯一(AutoSlugField默认可能已处理,但显式更安全)
    slug = AutoSlugField(populate_from='title', primary_key=False, unique=True)

生成并执行迁移:

python manage.py makemigrations
python manage.py migrate

注意:这一步仅强化Slug的约束,不改动主键,对生产环境无影响。

步骤2:为AAA添加临时UUID字段(保留旧主键)

添加old_uuid字段存储原UUID主键,避免后续步骤丢失关联依据:

class AAA(models.Model):
    id = UUIDField(primary_key=True, version=4, editable=False)
    title = models.CharField(max_length=255, unique=True)
    slug = AutoSlugField(populate_from='title', primary_key=False, unique=True)
    # 添加临时字段,存储原UUID
    old_uuid = models.UUIDField(unique=True, editable=False, null=True)

生成迁移后,编辑迁移文件,添加RunPython操作批量赋值:

from django.db import migrations

def copy_uuid_to_old_uuid(apps, schema_editor):
    AAA = apps.get_model('your_app', 'AAA')
    # 分批更新避免锁表(数据量大时建议按id分段处理)
    for obj in AAA.objects.all().iterator():
        obj.old_uuid = obj.id
        obj.save(update_fields=['old_uuid'])

class Migration(migrations.Migration):
    dependencies = [
        ('your_app', 'previous_migration_name'),
    ]

    operations = [
        migrations.AddField(
            model_name='aaa',
            name='old_uuid',
            field=models.UUIDField(editable=False, null=True, unique=True),
        ),
        migrations.RunPython(copy_uuid_to_old_uuid),
    ]

执行迁移:

python manage.py migrate

步骤3:为关联模型添加临时Slug外键字段

以BBB模型为例,添加aaa_slug字段,同步现有关联的Slug值:

class BBB(models.Model):
    aaa = models.ForeignKey(AAA, on_delete=models.CASCADE)
    # 添加临时字段,存储关联AAA的Slug
    aaa_slug = models.SlugField(null=True)
    # ... 其他字段 ...

生成迁移后,编辑迁移文件添加批量赋值逻辑:

from django.db import migrations

def copy_aaa_slug_to_temp_field(apps, schema_editor):
    BBB = apps.get_model('your_app', 'BBB')
    # 分批处理,避免长时间锁表
    for obj in BBB.objects.all().iterator():
        obj.aaa_slug = obj.aaa.slug
        obj.save(update_fields=['aaa_slug'])

class Migration(migrations.Migration):
    dependencies = [
        ('your_app', 'previous_migration_name'),
    ]

    operations = [
        migrations.AddField(
            model_name='bbb',
            name='aaa_slug',
            field=models.SlugField(null=True),
        ),
        migrations.RunPython(copy_aaa_slug_to_temp_field),
    ]

执行迁移:

python manage.py migrate

步骤4:切换关联模型的外键指向Slug

分小步修改,避免一次性锁表:

  1. 先允许原外键字段为空:
class BBB(models.Model):
    # 临时设置null=True,避免迁移时因数据关联报错
    aaa = models.ForeignKey(AAA, on_delete=models.CASCADE, null=True)
    aaa_slug = models.SlugField(null=True)
    # ... 其他字段 ...

生成并执行迁移:

python manage.py makemigrations
python manage.py migrate
  1. 修改外键指向AAA的Slug,并同步临时字段的值:
class BBB(models.Model):
    # 外键指向AAA的slug字段
    aaa = models.ForeignKey(AAA, on_delete=models.CASCADE, null=True, to_field='slug')
    aaa_slug = models.SlugField(null=True)
    # ... 其他字段 ...

生成迁移后,编辑迁移文件添加同步逻辑:

from django.db import migrations

def sync_temp_slug_to_foreign_key(apps, schema_editor):
    BBB = apps.get_model('your_app', 'BBB')
    AAA = apps.get_model('your_app', 'AAA')
    for obj in BBB.objects.all().iterator():
        if obj.aaa_slug:
            obj.aaa = AAA.objects.get(slug=obj.aaa_slug)
            obj.save(update_fields=['aaa'])

class Migration(migrations.Migration):
    dependencies = [
        ('your_app', 'previous_migration_name'),
    ]

    operations = [
        migrations.AlterField(
            model_name='bbb',
            name='aaa',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='your_app.aaa', to_field='slug'),
        ),
        migrations.RunPython(sync_temp_slug_to_foreign_key),
    ]

执行迁移:

python manage.py migrate
  1. 恢复外键非空约束:
class BBB(models.Model):
    aaa = models.ForeignKey(AAA, on_delete=models.CASCADE, to_field='slug')
    aaa_slug = models.SlugField(null=True)
    # ... 其他字段 ...

生成并执行迁移:

python manage.py makemigrations
python manage.py migrate

步骤5:切换AAA的主键为Slug

修改AAA模型,将slug设为主键,原UUID字段改为普通唯一字段:

class AAA(models.Model):
    # 原主键改为普通唯一字段,不再是primary_key
    uuid = models.UUIDField(unique=True, editable=False)
    title = models.CharField(max_length=255, unique=True)
    # 设置slug为主键
    slug = AutoSlugField(populate_from='title', primary_key=True, unique=True)
    old_uuid = models.UUIDField(unique=True, editable=False, null=True)

生成迁移后,检查迁移文件,确保操作顺序是:先移除原id的主键属性,再设置slug为主键。如果Django生成的迁移有问题,手动调整操作顺序:

from django.db import migrations, models

class Migration(migrations.Migration):
    dependencies = [
        ('your_app', 'previous_migration_name'),
    ]

    operations = [
        # 先修改原id字段为普通字段,并重命名为uuid
        migrations.RenameField(
            model_name='aaa',
            old_name='id',
            new_name='uuid',
        ),
        migrations.AlterField(
            model_name='aaa',
            name='uuid',
            field=models.UUIDField(editable=False, unique=True),
        ),
        # 再设置slug为主键
        migrations.AlterField(
            model_name='aaa',
            name='slug',
            field=django_extensions.db.fields.AutoSlugField(primary_key=True, populate_from='title', unique=True),
        ),
    ]

执行迁移:

python manage.py migrate

步骤6:清理临时字段

删除不再需要的临时字段:

class AAA(models.Model):
    uuid = models.UUIDField(unique=True, editable=False)
    title = models.CharField(max_length=255, unique=True)
    slug = AutoSlugField(populate_from='title', primary_key=True, unique=True)
    # 移除old_uuid字段
    # old_uuid = models.UUIDField(unique=True, editable=False, null=True)

class BBB(models.Model):
    aaa = models.ForeignKey(AAA, on_delete=models.CASCADE, to_field='slug')
    # 移除aaa_slug字段
    # aaa_slug = models.SlugField(null=True)
    # ... 其他字段 ...

生成并执行迁移:

python manage.py makemigrations
python manage.py migrate

生产环境注意事项

  • 所有迁移步骤必须在业务低峰期执行,每个步骤单独执行,避免批量操作长时间锁表;
  • 数据量较大时,RunPython中的批量更新要改为分段处理(比如按id__range分批),避免内存溢出和锁表;
  • 执行每个迁移前,暂停可能写入AAA或关联模型的业务操作(如定时任务、API写入),迁移完成后恢复;
  • 若使用读写分离,需确保迁移完成后同步从库数据。

内容的提问来源于stack exchange,提问作者wowkin2

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 18:36:41