數據遷移 —— 從 PostgreSQL 到 MySQL


一、背景


公司某個項目,本來生產環境一直用線上的 aliyun 的 PostgreSQL RDS 的,但是后來為了一些更高級的功能擴展,換成了 aliyun 的 MySQL RDS。於是需要進行數據庫的遷移。

技術棧:

  • Node.js(Express)
  • React
  • PostgreSQL

具體版本不介紹了

二、結構遷移


我們的庫比較簡單,只有表,沒有視圖、函數、存儲過程、觸發器什么的。所以結構這塊不用考慮太多。

我們 Node 應用用的是 Sequelize,需要改造的就一條:

  • 把 model 定義里涉及 JSONB 的都改成 JSON

其余的 Sequelize 都會幫忙抹平差異。

三、數據遷移


數據庫的結構遷移好了,接下來就是遷移數據了。

我們的庫比較簡單,只涉及表的數據。

步驟1、備份(backup)PostgreSQL

平常我們備份 pg 數據庫的時候,都會加上 -Fc 參數,表示壓縮。但因為這次要遷移到不同家的數據庫產品,所以只能導出 SQL statement 的純文本文件。

執行:

pg_dump --data-only --inserts --column-inserts -h xxx -U  xxx_production -d xxx_production > ./xxx_prod.sql

參數解釋:

  • --data-only:只遷移數據,不遷移結構
  • --inserts:生成 SQL statement 的純文本文件
  • --column-inserts:生成的 INSERT 語句,會帶上列清單(即明確地指定具體列名)

結果:生成 xxx_prod.sql 文件。

步驟2、修改(上一步生成的) xxx.sql 文件

(1)remove schema

做法:public. -> 空

如:
INSERT INTO public."Gift" (id, name, sku, "bindCount", type, enabled, "createdAt", "updatedAt")
->
INSERT INTO "Gift" (id, name, sku, "bindCount", type, enabled, "createdAt", "updatedAt")

原因:PostgreSQL 有 schema,MySQL 無。

(2)修改 引用系統標識符 形式

做法:把涉及 表名 + 字段名 的引用,從原來的 "" 包裹變成 `` 包裹。

如:
INSERT INTO "Gift" (id, name, sku, "bindCount", type, enabled, "createdAt", "updatedAt")
->
INSERT INTO Gift (id, name, sku, bindCount, type, enabled, createdAt, updatedAt)

原因:如下擴展所述:

[拓展] PostgreSQL 和 MySQL 的一些常用寫法的區別
  • 引用系統標識符,PostgreSQL 用 `` 注釋(ANSI標准),MySQL 用 ""
  • 注釋,PostgreSQL 用 -- (ANSI標准),MySQL 用 -- or #
  • 引用值,PostgreSQL 用 '' 注釋(ANSI標准),MySQL 用 ""

更多區別可參考:https://wiki.postgresql.org/wiki/Things_to_find_out_about_when_moving_from_MySQL_to_PostgreSQL

總結:PostgreSQL 更符合 ANSI標准,跨平台性更好。

這里推薦一個在線 web 的 pg 轉 mysql sql 的工具:http://www.lightbox.ca/pg2mysql.php (這個工具功能有限,引用系統標識符的修改還是可以用的,當然你手動修改也可以)。

(3)時區轉換
[拓展] PostgreSQL 和 MySQL 關於日期數據類型 時區的區別

① 關於 datetime 數據類型,兩者的對應關系:

PostgreSQL MySQL
不帶時區 timestamp DateTime
帶時區 timestamptz Timestamp


問:用帶時區的好,還是不帶時區的好?

答:建議帶時區

帶時區的好處

  • 省去你考慮不同時區的麻煩,數據庫的時區(or 操作系統的時區)只要變化,帶時區的時間都會自動調整變化到此時區下的值
  • 為了國際化和未來可拓展性的考慮

​ ​② 時區默認取決於誰?

PostgreSQL 默認取決於數據庫的設置:

-- 查看時區
show timezone;   -- PRC

-- 設置時區
select * from pg_timezone_names;   -- 查看可供選擇的時區列表
set timezone 'UTC';  

MySQL 默認取決於操作系統的時區設置:

-- 查看時區
show variables like '%time_zone%';

-- 結果:
-- system_time_zone CST
-- time_zone SYSTEM

​③ 如果是帶時區的數據類型,存進去、取出來、顯示 的差異:

  • PostgreSQL:2020-04-25 17:00:00.22+08 (會貼心的把時區信息帶上:+08
  • MySQL:2020-04-25 17:00:00.22 (沒有時區信息,如果想知道這個時間的時區是多少?需要查看 mysql 當前的時區)


上面的拓展介紹了差異,那么我們要怎么做?

因為我們用了 Sequelize,他的 DATE 類型,在 PostgreSQL 是帶時區的 timestamptz 類型(形如 2020-04-25 17:00:00.22+08),而在 MySQL 是不帶時區的 DateTime 類型(形如 2020-04-25 17:00:00.22),所以我們 INSERT 的時候,要把字符串里的 +08 remove 掉,所以:

做法:+08 -> 空

如:'2020-03-16 15:02:10.616+08' -> '2020-03-16 15:02:10.616'

注意:記得確保執行 sql 的時候, MySQL 的時區為 +08。

問:為什么 Sequelize 在 MySQL 不對應也是帶時區的 Timestamp 類型呢?

答:我網上沒有搜到相關解釋。我自己猜測,應該是 Sequelize 考慮到 Timestamp 類型有 2038 問題,DateTime 類型數值范圍更廣,是最優的選擇,用的人也多(我之前用 mysql 的時候,就習慣用 DateTime)

(4)注釋不要的語句
  • 注釋文件結尾處的update序列最新值 的 sql 語句(因為 mysql 沒有 postgres 單獨的 sequence 概念),如:SELECT pg_catalog.setval('public."PocketShopPower_id_seq"', 6640, true);

步驟3、在 MySQL 上執行 xxx.sql 語句

可以用 navicat 、命令行 都可。 略。

四、更多方案


1、GUI 工具

2、其他

pg2mysql : https://github.com/pivotal-cf/pg2mysql


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM