Django配置unique_together迁移报1071键过长错误求助
问题根因
报错核心是联合唯一索引的总字节长度超过了InnoDB引擎3072字节的上限,和之前的缩进错误没有直接关系:
- 之前
unique_together写在Meta类外时,Django完全没有识别到该约束,执行迁移时不会创建对应联合索引,因此不会触发报错 - 修正缩进后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
相关产品推荐
相关产品推荐

