Django更換資料庫和遷移數據方案

前言

雙十一光顧著買東西都沒怎麼寫文章,現在筆記里還有十幾篇半成品文章沒寫完…

今天來分享一下 Django 項目切換資料庫和遷移數據的方案,網路上找到的文章方法不一,且使用中容易遇到各類報錯,本文根據 Django 官方文檔和工作中的經驗,穩定可靠,在部落格中長期更新~

如果你遇到同樣的問題,閱讀本文應該能得到比較好的解決方案。

基本步驟

Django 默認使用 SQLite 資料庫方便開發,同時其 ORM 支援多種資料庫,只要安裝對應的驅動就行。

切換資料庫一般是將開發環境的 SQLite 切換到 MySQL (MariaDB) 或 PostgreSql ,本文只測試了從 SQLite 到 MySQL / PostgreSQL,同理,其他切換路徑也是可以的。

資料庫的表結構沒啥問題,使用 Django 的 migrate 功能就行了

關鍵在於數據遷移,可以使用 Navicat 之類的資料庫工具進行數據同步,但往往會因為表之間的約束關係導致同步失敗(要求按特定順序導入數據)。

所以最好的方法是使用 Django 的 dumpdata 功能,將資料庫導出為 json 或 xml 文件,然後切換資料庫再導入。

步驟如下:

  • 導出原有數據: python manage.py dumpdata -o db.json
  • 在目標資料庫(MySQL / PostgreSql)里創建一個空的庫
  • settings.py 里切換到新的資料庫
  • 建立新的資料庫表結構 python manage.py migrate
  • 導入原有數據: python manage.py loaddata db.json

搞定~

附上幾種資料庫配置,方便使用

db_config = {
    'sqlite': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        'OPTIONS': {
            'timeout': 20,
        }
    },
    'pgsql': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': '資料庫名稱',
        'USER': '用戶名',
        'PASSWORD': '密碼',
        'HOST': '資料庫伺服器地址',
        'PORT': 5432,
    },
    'mysql': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': '資料庫名稱',
        'USER': '用戶名',
        'PASSWORD': '密碼',
        'HOST': '資料庫伺服器地址',
        'PORT': 3306,
    }
}
# 這裡可以方便切換不同資料庫
DATABASES = {'default': db_config['pgsql']}

其中:

  • MySQL 需要安裝 mysqlclient
  • PostgreSql 需要安裝 psycopg2

然後,事情往往沒有這麼簡單和順利,導出導入的過程中可能會遇到一些問題,請繼續看~

導出報錯

報錯資訊

CommandError: Unable to serialize database: 'gbk' codec can't encode character '\u30fb' in position 4: illegal multibyte sequence

原因跟編碼有關

解決方法

使用 Python 的 UTF-8 模式導出數據就沒問題

用這個命令導出文件

(不導出 auth.permissioncontenttypes ,這倆在 migrate 時會自動生成,這樣避免了導入原有數據時衝突)

python -Xutf8 manage.py dumpdata --exclude auth.permission --exclude contenttypes > db.json

或者

python -Xutf8 manage.py dumpdata -o db.json

導入過程出錯解決

報錯1: Duplicate entry

報錯資訊

django.db.utils.IntegrityError: Problem installing fixture 'db.json'  Could not load contenttypes.ContentType(pk=15): (1062, "Duplicate entry 'xxx' for key 'django_content_type.django_content_type_app_label_model_76bd3d3b_uniq'")

解決方法一: 重新導出數據

加上這倆參數

  • --natural-primary: Omits the primary key in the serialized data of this object since it can be calculated during deserialization.
  • --natural-foreign: Uses the natural_key() model method to serialize any foreign key and many-to-many relationship to objects of the type that defines the method.

作用是導出的時候去除一些約束,導入時會自動處理,減少導入時因為表之間約束關係的問題

python3 manage.py dumpdata --natural-primary --natural-foreign -o db.json

解決方法二: 刪除 content_type 數據

另一種思路,把 migrate 過程產生的初始化數據刪了,避免導入時和原有數據衝突

先進入 python shell

python3 manage.py shell

輸入以下Python程式碼執行

from django.contrib.contenttypes.models import ContentType
ContentType.objects.all().delete()

報錯2: 編碼錯誤

報錯資訊

UnicodeDecodeError: 『utf-8』 codec can』t decode byte 0xff in position 0: invalid start byte in Django

解決方法一: 使用 Python 的 UTF8 模式(推薦)

在導入命令前面加上 -Xutf8 參數

python -Xutf8 manage.py loaddata db.json

解決方案二: 魔改 Django 程式碼

能用,但不推薦,實在沒辦法再來試這個方法

修改文件

lib\site-packages\django\core\serializers\json.py

Deserializer 方法中找到這行程式碼

stream_or_string = stream_or_string.decode()

改成這樣

stream_or_string = stream_or_string.decode('UTF-16')

再進行導入操作

參考資料