因为 Django 项目配置 SQLite 非常简单,而且考虑到个人博客访问量不会很大,所以线上环境直接使用了 SQLite 数据库。
博客上线初期一切运行良好,直到最近频繁收到 database is locked
的异常告警。经过一番调研,大致确定了导致异常的原因:并发情况下,如果线程等待数据库锁的时间超过指定时间(默认为 5 秒)则会抛出 database is locked
异常。Stackoverflow 上有人问了类似的问题,解决方案大致可总结为这几种:
- 更换数据库引擎。
- 优化应用程序,减少并发。
- 增加锁超时时间。
方案 3 治标不治本,方案 2 比较麻烦,而且一直使用 SQLite 也不是长远之计,所以长痛不如短痛,决定线上环境彻底换掉 SQLite,投入 PostgreSQL 的怀抱。
数据迁移方案
一开始想到的方案是将 SQLite 中的数据导出为 SQL 语句,再导入 PostgreSQL 数据库中,但毕竟分属 2 个不同的数据库引擎,很容易出现不兼容的 SQL 语句,所以这个方案被否掉了。
最终决定采用的方案是将 SQLite 中的数据导出为 JSON 格式的数据,再导入的 PostgreSQL 数据库中,Django 提供了导出和导入的命令,实施起来非常方便。
以下是整个迁移流程:
-
导出 JSON 格式的数据:
python manage.py dumpdata > db.json
; -
修改 Django 项目的数据库引擎配置;
-
生成新的数据库表:
python manage.py migrate
; -
删除新表中自动生成的 ContentType 有关的数据:
$ python manage.py shell
>>> from django.contrib.contenttypes.models import ContentType
>>> ContentType.objects.all().delete()
- 导入 JSON 数据:
python manage.py loaddata db.json
。
第 4 步需要特别注意,新生成的数据库表,Django 会自动写入 ContentType 有关的数据,需要先删除掉,否则会和导入的数据的冲突。
遇到的坑
尽管迁移步骤非常的简单,但是也存在不少的坑,以下就是我遇到的一些坑和填坑方法。
坑1:不兼容的数据格式
SQLite 数据库字段的类型比较简单,而 PostgreSQL 字段类型更加丰富,有些能够存入 SQLite 的数据导入 PostgreSQL 时无法通过格式校验。
例如我的博客评论有一个 ip 字段,某些空记录的值为 b''
。SQLite 中字段类型是 char,而 PostgreSQL 中是 inet,inet 施加的校验更强,值 b''
无法通过校验,因此导入时会报错。
解决方案就是导出数据前,先将 ip 字段的值修改为和 PostgreSQL inet 字段类型兼容的值。
坑2:PostgreSQL 不允许 join 不同类型的字段
PostgreSQL 不同类型的字段不允许 join 操作。因此在 SQLite 和 MySQL 中执行没问题的 SQL 语句在 PostgreSQL 中就会报错。
没有太好的解决方案,只能把各个表中需要 join 的字段改为相同类型。如果无法修改,建议更换数据库为 MySQL。
坑3:当心应用程序自动生成的数据
应用程序很可能会有当某条记录存入数据库时,自动生成某些关联数据的逻辑,导入数据时就会产生冲突。
例如我的博客应用中,当 Users 表中存入一条用户记录后,就会在 token 表中生成一条关联的记录,那么导入数据时,原数据中的 token 就会和新生成的 token 冲突。
解决方案是,找出自动生成的数据,将他们从导出的数据中排除。dumpdata
命令支持指定排除的数据,例如:python manage.py dumpdata > db.json --exclude=authtoken
将排除 authtoken 应用中的数据。
总结
- 更换数据库引擎是一件异常痛苦的事,所以在项目初期就要选好数据库引擎,减少数据迁移的麻烦。
- 尽管 Django ORM 非常强大,在一定程度上提供了数据库引擎的抽象,但仍然无法避免在某个数据库工作良好的代码,换到另一个数据库就无法使用的情况,所以无论是开发、测试还是线上,尽量使用相同的数据库引擎。
- 这种数据迁移方式效率非常低,我博客 200M 的数据导入就花了 30 多分钟,如果数据量比较大,最好还是换一种更加高效的方式。
-- EOF --
Good work
123
博主,去年10月等你更新,然后你一直没更……自己到处找资料,用的 nodejs+typescript+koa2+react+mongodb 撸了一套前后端。但是期待博主更新。
点击发布后无法刷新。
好麻烦,不如写个脚本撸一下数据。。。
脚本撸也是一个方案,不过要考虑数据之间的耦合性,也是个细致活。
博主,这种风格的web界面, 有模板吗,我想临摹一波,设计的话,太麻烦了,不过,楼主不介意的话,我临摹一把哈,我有大把时间呢
代码是开源的,可以去看看源码:django-blog-project
感谢博主
test