11

Django 老项目如何从 SQLite 迁到 PostgreSQL

 3 years ago
source link: https://www.zmrenwu.com/post/95/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

因为 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 --


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK