Mysql 5.6遷移至PostgreSQL 9.6的實踐小結

一、背景

實際生產中,發現mysql查詢性能存在抖動,同樣的sql,正常執行時間是秒級,但是偶爾會有執行上百秒的情況出現,經過DBA的排查,并沒有發現mysql的問題。考慮遷移一部分生成數據到PG中進行測試。(ps~個人覺得這個遷移背景有點牽強,還是應該先定位性能抖動的原因比較好)

二、遷移方案

遷移的大致步驟如下:

  1. 從生產環境的mysql備份中拉取一個備份出來

  2. 在測試機上通過備份恢復生產庫

  3. 導出mysql的表定義和數據

  4. 通過自己開發的小工具,將mysql表定義語法轉換至PG的表定義語法

  5. 在PG中創建表

  6. 將數據導入PG

三、遷移步驟說明

3.1 拉取備份

這個沒什么好說的,scp指定的備份文件到測試機即可

考慮是生產環境,有防火墻和權限等的限制,可以臨時創建臨時用戶tmp,關閉防火墻,待拷貝完成,刪除用戶,重啟防火墻

3.2 恢復生產庫

生產上通過xtrabackup做的備份,恢復方法這里就不啰嗦了,不是本次的重點,自行百度~

3.3 導出mysql的表定義和數據

從這步開始就有坑了~

首先,導出表定義(只貼出測試數據)

# 將名為test_db的庫中所有的ddl都導出到test_db.sql文件中
# 導出的定義以sql語句的形式寫入文件
[mysql@sndsdevdb01 ~]$ mysqldump -h127.0.0.1 -uroot -ppassword -d test_db > /mysql/test_db.sql
[mysql@sndsdevdb01 ~]$ cat /mysql/test_db.sql
...
/* 下面是導出的表定義部分 */
DROP TABLE IF EXISTS `tb1`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `tb1` (
  `c1` int(11) DEFAULT NULL,
  `c2` char(5) DEFAULT NULL,
  `c3` varchar(10) DEFAULT NULL,
  `c4` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
SET @@SESSION.SQL_LOG_BIN = @MYSQLDUMP_TEMP_LOG_BIN;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
...

導出表定義是為了之后人工檢查mysql到PG的ddl語法轉換的正確性
實際實施時,利用小工具直接連接mysql服務器即可完成mysql到PG的ddl語法轉換
關于小工具的說明,請見附錄~

然后,導出數據
考慮到數據格式,編碼的問題,決定統一將數據導出為UTF8編碼的csv文件
為了說明坑的地方,我插入了5條記錄

mysql> delete from tb1;
Query OK, 3 rows affected (0.01 sec)

mysql> insert into tb1 values(1,'qqq','www',current_time);
Query OK, 1 row affected (0.02 sec)

mysql> insert into tb1 values(1,'qq\nq','www',current_time);
Query OK, 1 row affected (0.01 sec)

mysql> insert into tb1 values(1,'qq\r\nq','www',current_time);
Query OK, 1 row affected (0.00 sec)

mysql> insert into tb1 values(1,'qqq','www','0000-00-00 00:00:00');
Query OK, 1 row affected (0.00 sec)

mysql> insert into tb1 values(1,'qqq','www',null);
Query OK, 1 row affected (0.00 sec)

mysql> select * from tb1;
+------+-------+------+---------------------+
| c1   | c2    | c3   | c4                  |
+------+-------+------+---------------------+
|    1 | qqq   | www  | 2017-07-14 17:36:25 |
|    1 | qq
q  | www  | 2017-07-14 17:36:30 |
|    1 | qq
q | www  | 2017-07-14 17:36:36 |
|    1 | qqq   | www  | 0000-00-00 00:00:00 |
|    1 | qqq   | www  | NULL                |
+------+-------+------+---------------------+
5 rows in set (0.00 sec)

mysql> select * from tb1 into outfile '/mysql/tb1.csv' fields terminated by ',' optionally enclosed by '"' escaped by '"' lines terminated by '\n';

其中第二條和第三條中,c2列分別包含了換行符和windows的特殊換行符
然后再通過vi 打開tb1.csv

1,"qqq","www","2017-07-14 17:36:25"
1,"qq"
q","www","2017-07-14 17:36:30"
1,"qq^M"
q","www","2017-07-14 17:36:36"
1,"qqq","www","0000-00-00 00:00:00"
1,"qqq","www","N

坑點如下

  1. \n換行符導致原本的一條記錄分為2行
  2. \r是特殊字符,vi模式下就表示為^M
  3. datetime類型可以存儲"0000-00-00 00:00:00",但是官方手冊上datetime的合法范圍是'1000-01-0100:00:00' to '9999-12-31 23:59:59',感覺是bug。。
  4. NULL值會被轉義為"N的形式

1和2兩點,導致csv格式混亂,導入PG會出錯;datetime對應PG的timestamp類型,而"0000-00-00 00:00:00"是不符合PG的時間戳類型的合法范圍的;PG也不認識"N表示的NULL。。。

由于上述的坑都是在將數據導入PG的時候才發現的,所以我的做法是通過shell的sed,awk等命令,去人工替換這些內容。因為生產數據量很大,一個庫大概200G,磁盤空間有限,加上導出數據需要較長時間,所以盡量不重復導數據

但是用shell處理大文件,效率也很低,150G的csv文件,遍歷sed多次,往往超過1小時,而且存在正則表達式寫的不精確,匹配出錯的情況

所以我個人推薦,select導出數據時,通過where條件過濾,用replace函數將需要處理的列直接處理掉,可以省去后面的麻煩,但是前提條件是需要知道有哪些列存在這些問題(生成中的表往往列很多,幾十甚至幾百列)

3.3 在PG中創建表并導入數據

首先創建相應的業務庫

postgres=# create database test_db;
CREATE DATABASE
postgres=# \c test_db 
You are now connected to database "test_db" as user "postgres".
postgres=#\i /pgsql/pg.sql
# 執行轉換后的ddl,定義表
...
postgres=#\copy tb1 from '/pgsql/tb1.csv' with(format csv,encoding 'UTF8',NULL 'null')
# 通過copy命令導入數據,通過指定NULL字符串來識別NULL值

如果導入過程不出現任何錯誤,那說明數據的遷移基本就完成了

3.4 其他

上述內容只是單純的業務庫的數據遷移,如果想完整的把整個業務系統遷移至PG,還有很多的別的遷移工作

例如表的索引
PG提供了豐富的索引類型,索引詳情參考:
PG 9.6 手冊 http://www.postgres.cn/docs/9.6/indexes.html
需要根據業務需求重新定制,例如AP型業務,gin索性就有很大的優勢,除此之外,業務定義的存儲過程,上層的增刪改查接口等等也需要修改
另外,數據庫的備份方案,日志歸檔設置,高可用方案的設計這些也需要定制

附錄

關于DDL語法轉換的小工具

  1. 功能簡述
    將mysql的表定義轉換為PG對應的語法。主要完成數據類型的映射,列屬性語法的轉換,主鍵和部分類型索引的轉換

1.1. 類型映射

case "tinyint":
      case "tinyint unsigned":
      case "smallint":
          if (col_is_auto_increment.equals("YES")){//increment type
              mysql_type.add("smallserial");
          }else{
              mysql_type.add("smallint");
          }
          break;
      case "mediumint":
      case "smallint unsigned":
      case "mediumint unsigned":
      case "integer":
      case "int":
          if (col_is_auto_increment.equals("YES")){//increment type
              mysql_type.add("serial");
          }else{
              mysql_type.add("int");
          }
          break;
      case "int unsigned":
      case "bigint":
          if (col_is_auto_increment.equals("YES")){//increment type
              mysql_type.add("bigserial");
          }else{
              mysql_type.add("bigint");
          }
          break;
      case "bigint unsigned":
          mysql_type.add("decimal");
          mysql_type.add("20");
          mysql_type.add("0");
          break;
      case "double":
          mysql_type.add("double precision");
          break;
      case "decimal":
          mysql_type.add("decimal");
          mysql_type.add(precision.toString());
          mysql_type.add(scale.toString());
          break;
      case "float":
          mysql_type.add("real");
          break;
      case "binary":
      case "char":
          mysql_type.add("char");
          mysql_type.add(precision.toString());
          break;
      case "varbinary":
      case "varchar":
          mysql_type.add("varchar");
          mysql_type.add(precision.toString());
          break;
      case "tinyblob":
      case "mediumblob":
      case "longblob":
      case "blob":
          mysql_type.add("bytea");
          break;
      case "date":
          mysql_type.add("date");
          break;
      case "datetime":
      case "year":
      case "timestamp":
          mysql_type.add("timestamp");
          break;
      case "time":
          mysql_type.add("time");
          break;
      /*case "bit":
          pg_type.add("bit");
          break;*/
      case "tinytext":
      case "text":
      case "mediumtext":
      case "longtext":
          mysql_type.add("text");
          break;
      default:
          mysql_type.add("This type may be user deifned type,confirm for yourself please!");
          break;

1.2. 列屬性
* not null屬性
* column注釋
* 自增屬性

1.3. 索引
統一將mysql的索引轉換為PG的btree索引,這個在應用中意義不大,因為多數情況,索引是需要根據業務需求重新定義的

  1. 實現方式
    通過JDBC連接mysql服務器,通過元數據(metadata)獲取所有的表名,列名以及列的數據類型等等信息,然后在程序中做轉換,最后寫入sql文件

思考

其實這只是簡單的遷移方案,目前也有一些商用或者開源的遷移工具,例如:
mysql2pg:https://sourceforge.net/projects/mysql2pg/

另外,關于遷移數據,用csv文件的方式,對磁盤空間的要求較高,而且有上述字符格式的問題。其實還可以考慮PG的插件mysql_fdw,可以直接用select into的方式將數據直接插入PG中,可以省去中間導出的步驟。但是9.6的PG,對foreign table的語法支持不完善,不支持like的方式建表,所以對寬表,create foreign table寫起來就比較麻煩,可以考慮用腳本自動化。
另外,生產中往往mysql和PG不在一臺機器上,mysql_fdw拉取和插入數據的效率還有待測試。我初步的嘗試發現,速度是很慢的,不過沒有深入調查原因,有可能是網絡問題,也有可能是配置問題
mysql_fdw的說明參考德哥的博客:http://blog.163.com/digoal@126/blog/static/163877040201493145214445/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容

  • 【MySQL】Linux下MySQL 5.5、5.6和5.7的RPM、二進制和源碼安裝 1.1BLOG文檔結構圖 ...
    小麥苗DB寶閱讀 10,575評論 0 31
  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,057評論 6 13
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • 1.小白兔有一家糖果鋪,小老虎有一個冰淇淋機。兔媽媽告訴小白兔,如果你喜歡一個人吶,就給一顆糖他。小白兔喜歡上了小...
    九馬閱讀 461評論 0 2
  • 2015,變數很大的一年。 現在的我力不從心。 本以為找到了一份較滿意的工作,開始自己的工作生涯。結果,現實給了狠...
    冰河木馬閱讀 179評論 0 1