追梦人物❤️包子 博主
一直走在追梦的路上。

Django 老项目如何从 SQLite 迁到 PostgreSQL

2020-10-04107238 阅读10 评论

因为 Django 项目配置 SQLite 非常简单,而且考虑到个人博客访问量不会很大,所以线上环境直接使用了 SQLite 数据库。

博客上线初期一切运行良好,直到最近频繁收到 database is locked 的异常告警。经过一番调研,大致确定了导致异常的原因:并发情况下,如果线程等待数据库锁的时间超过指定时间(默认为 5 秒)则会抛出 database is locked 异常。Stackoverflow 上有人问了类似的问题,解决方案大致可总结为这几种:

  1. 更换数据库引擎。
  2. 优化应用程序,减少并发。
  3. 增加锁超时时间。

方案 3 治标不治本,方案 2 比较麻烦,而且一直使用 SQLite 也不是长远之计,所以长痛不如短痛,决定线上环境彻底换掉 SQLite,投入 PostgreSQL 的怀抱。

数据迁移方案

一开始想到的方案是将 SQLite 中的数据导出为 SQL 语句,再导入 PostgreSQL 数据库中,但毕竟分属 2 个不同的数据库引擎,很容易出现不兼容的 SQL 语句,所以这个方案被否掉了。

最终决定采用的方案是将 SQLite 中的数据导出为 JSON 格式的数据,再导入的 PostgreSQL 数据库中,Django 提供了导出和导入的命令,实施起来非常方便。

以下是整个迁移流程:

  1. 导出 JSON 格式的数据:python manage.py dumpdata > db.json

  2. 修改 Django 项目的数据库引擎配置;

  3. 生成新的数据库表:python manage.py migrate

  4. 删除新表中自动生成的 ContentType 有关的数据:

$ python manage.py shell
>>> from django.contrib.contenttypes.models import ContentType
>>> ContentType.objects.all().delete()
  1. 导入 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 应用中的数据。

总结

  1. 更换数据库引擎是一件异常痛苦的事,所以在项目初期就要选好数据库引擎,减少数据迁移的麻烦。
  2. 尽管 Django ORM 非常强大,在一定程度上提供了数据库引擎的抽象,但仍然无法避免在某个数据库工作良好的代码,换到另一个数据库就无法使用的情况,所以无论是开发、测试还是线上,尽量使用相同的数据库引擎。
  3. 这种数据迁移方式效率非常低,我博客 200M 的数据导入就花了 30 多分钟,如果数据量比较大,最好还是换一种更加高效的方式。

-- EOF --

10 评论
登录后回复
MiruiWang2021
2022-03-14 10:04:25

Good work

回复
1颜阿
2021-12-31 11:27:45

123

回复
nkyiming
2021-04-18 23:05:12

博主,去年10月等你更新,然后你一直没更……自己到处找资料,用的 nodejs+typescript+koa2+react+mongodb 撸了一套前后端。但是期待博主更新。

回复
MiruiWang2021 nkyiming
2022-03-14 10:06:55

点击发布后无法刷新。

回复
leoython
2021-04-09 15:05:35

好麻烦,不如写个脚本撸一下数据。。。

回复
追梦人物 leoython
2021-04-11 20:08:28

脚本撸也是一个方案,不过要考虑数据之间的耦合性,也是个细致活。

回复
SeeYouTomorrow
2020-10-15 19:58:09

博主,这种风格的web界面, 有模板吗,我想临摹一波,设计的话,太麻烦了,不过,楼主不介意的话,我临摹一把哈,我有大把时间呢

回复
追梦人物 SeeYouTomorrow
2020-10-16 23:23:25

代码是开源的,可以去看看源码:django-blog-project

回复
xiadata 追梦人物
2021-05-01 17:29:24

感谢博主

回复
hw平头哥 追梦人物
2021-07-07 00:14:15

test

回复