⚠️不到万不得已,不建议手写migration文件,大佬除外。
事情的起因:
在RSS翻译器的缓存模型中,有一个做为hash的主键,当初脑抽使用了BinaryField类型,导致数据库的检索速度一直不理想。
所以这次准备换成检索速度最快的Integer类型。
正如所料,事情没那么简单,这事得从hash的计算方式说起。
过程:
为了能在低配置的机器上尽可能快的运行,我使用了谷歌开发的CityHash64来计算hash,理由很简单,它的代码逻辑比SHA的要简单的多,对于长文的计算要快的多,又能保证唯一性,所以非常理想。
接着在将BinaryField
改为BigIntegerField
,并在migrate后,测试存储数据时,程序报错了:OverflowError: Python int too large to convert to SQLite INTEGER
,一番GPT后才知道,SQLite只能存储64位的有符号整数,而CityHash64的值是无符号64位,超出了范围。
于是,为了方便后期维护和扩展,决定使用char类型来存储值,虽然比Integer的检索慢,但至少比Binary快呀。
既然使用了char类型,就没有大小限制,果断升级到CityHash128来计算hash,并限制39个字符数,相关代码在此
接下来就是重头戏:数据迁移
首先需要重新计算所有的hash值,所以简单的类型migrate只是做了第一步,还需要在migration文件中手动更新数据。
于是我更改了migration文件:
from django.db import migrations, models
import cityhash
def update_hash(apps, schema_editor):
MyModel = apps.get_model('translator', 'translated_content')
for obj in MyModel.objects.all():
obj.hash = cityhash.CityHash128(f"{obj.original_content}{obj.translated_language}")
obj.save()
class Migration(migrations.Migration):
dependencies = [
('translator', '0030_alter_azureaitranslator_content_translate_prompt_and_more'),
]
operations = [
migrations.AlterField(
model_name='translated_content',
name='hash',
field=models.CharField(editable=False, max_length=39, primary_key=True, serialize=False),
),
migrations.RunPython(update_hash),
]
加了update_hash
函数和RunPython,不要问为啥不用bulk_update
,因为它无法用于主键类型字段的更新。
接着migrate没有报错,添加数据也正常,在我以为大功告成时,我检查了表里的数据,果然我还是太乐观了,表里原有的数据没有被更新,而是重新创建了新的一条数据。
又是一番GPT,了解到Django为了安全,任何migrate都不能修改原有的数据,但我不死心,改了update_hash函数:
def update_hash(apps, schema_editor):
MyModel = apps.get_model('translator', 'translated_content')
for obj in MyModel.objects.all():
new_hash = cityhash.CityHash128(f"{obj.original_content}{obj.translated_language}")
MyModel.objects.filter(hash=obj.hash).update(hash=new_hash)
这次没有意外,依旧无法修改,filter无法正常筛选出数据,一直为空。
只能另辟蹊径,创建多个migration来完成这次的迁移:
- 保留原有的hash字段(BinaryField),创建新字段
hash_new
(CharField)。(代码) - 计算
hash_new
值。(代码) - 删除hash字段,并设置
hash_new
为主键。(代码) - 重命名
hash_new
为hash。(代码)
同时,为了数据安全,写了一个backup_db
函数,在执行迁移前,拷贝一份sqlite数据库做备份。
好了,万事俱备,只欠东风。
本地运行测试,一切正常。
服务器dev环境运行测试,一切正常。
demo环境运行测试,一切正常。
个人使用的生产环境运行测试,一切正常。
在我以为肯定还有什么问题的时候,它运行正常了。。。。
我怀着忐忑不安的心,将新的docker镜像push了。。。。
接下来就是等待迎接新的Bug了。
PS:我相信我的方案肯定不是最完美的,如果你有更好的方案,欢迎留言联系我哈,感激不尽!
发表回复