一、背景
公司某個項目,本來生產環境一直用線上的 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 工具
- 1、Navicat [Premium](親測無效,tools -> Data Synchronization 倒是可以用,但是報錯,check 生成的 SQL 語句,發現根本沒幫我做轉換啊!)
- 2、MySQL Workbench(沒試,可看:https://mysqlworkbench.org/2012/11/how-to-migrate-postgresql-databases-to-mysql-using-the-mysql-workbench-migration-wizard/
2、其他
pg2mysql : https://github.com/pivotal-cf/pg2mysql