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

Django配置unique_together迁移报1071键过长错误求助

问题根因

报错核心是联合唯一索引的总字节长度超过了InnoDB引擎3072字节的上限,和之前的缩进错误没有直接关系:

  1. 之前unique_together写在Meta类外时,Django完全没有识别到该约束,执行迁移时不会创建对应联合索引,因此不会触发报错
  2. 修正缩进后Django识别到联合唯一约束,尝试创建联合索引:你配置的四个CharField总长度为400+200+400+100=1100字符,若数据库实际使用的是Django默认推荐的utf8mb4字符集(支持emoji、全部生僻字,单字符最大占4字节),总索引长度为1100*4=4400字节,远超3072字节的限制。仅确认数据库用了utf8(3字节阉割版字符集)不代表表实际使用的字符集是utf8,很多场景下建表时会被Django默认配置覆写为utf8mb4。
可行解决方案
  • 方案1:使用带前缀长度的联合唯一约束(无需改动业务字段,适配原生迁移)
    替换原有的unique_together配置为Django原生的UniqueConstraint,给长文本字段设置合理的索引前缀长度,保证总索引字节数在3072以内即可。配置示例:
    from django.db import models
    
    class Show(models.Model):
        english_name = models.CharField(max_length=400)
        kanji_name = models.CharField(max_length=200, blank=True)
        romanji_name = models.CharField(max_length=400, blank=True)
        show_type = models.CharField(max_length=100, blank=True)
        # 其余字段...
    
        class Meta:
            verbose_name_plural = 'Series'
            ordering = ('-created',)
            constraints = [
                models.UniqueConstraint(
                    name='unique_show_basic_info',
                    fields=['english_name', 'kanji_name', 'romanji_name', 'show_type'],
                    # MySQL下通过option指定前缀长度,utf8mb4场景下191长度的前缀单字段占764字节
                    options={
                        'mysql_prefix_lengths': {
                            'english_name': 191,
                            'romanji_name': 191
                        }
                    }
                )
            ]
    
    操作前先删除之前生成的、执行失败的迁移文件,再重新执行python manage.py makemigrations和python manage.py migrate即可。该配置下总索引长度约2700字节,完全在限制范围内,191字符的前缀足以覆盖绝大多数业务场景下的重名判断需求。
  • 方案2:下调字段最大长度(零兼容成本)
    如果业务不需要存储超长的名称字段,直接下调对应CharField的max_length参数,保证四个字段总字符数*4(utf8mb4单字符最大字节数)<3072即可,总字符数控制在750以内就不会触发长度报错。
  • 方案3:新增哈希字段做唯一校验(全字段严格唯一,无长度限制)
    如果业务要求必须按四个字段的全内容做唯一性校验,不要直接给长字段建联合索引,新增一个固定长度的哈希字段存四个字段的拼接哈希值,给哈希字段加唯一约束即可,完全规避索引长度限制。代码示例:
    import hashlib
    from django.db import models
    
    class Show(models.Model):
        english_name = models.CharField(max_length=400)
        kanji_name = models.CharField(max_length=200, blank=True)
        romanji_name = models.CharField(max_length=400, blank=True)
        show_type = models.CharField(max_length=100, blank=True)
        identity_hash = models.CharField(max_length=64, unique=True, blank=True)
        # 其余字段...
    
        def save(self, *args, **kwargs):
            raw_content = f"{self.english_name}|{self.kanji_name}|{self.romanji_name}|{self.show_type}"
            self.identity_hash = hashlib.sha256(raw_content.encode('utf-8')).hexdigest()
            super().save(*args, **kwargs)
    
        class Meta:
            verbose_name_plural = 'Series'
            ordering = ('-created',)
    
    该方案哈希冲突概率极低,适合对唯一性要求严格的场景。
排查注意事项
  • 执行新迁移前先检查数据库中是否残留之前迁移失败生成的临时表、半截创建的索引,若有先手动删除,避免旧配置干扰新迁移执行。
  • 不要直接照搬老版本Django/MySQL5.x场景下的解决方案,MySQL8.0和Django3.1的字符集默认配置、索引规则和老版本有差异。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 16:15:32