MySQL을 PostgreSQL 로 이관하는 방법은 PostgreSQL에서 제공하는 기능인 pgloader 를 사용하면 오브젝트부터 데이터까지 쉽게 이관할 수 있지만
반대인 PostgreSQL -> MySQL 은 적당한 툴도 없고, 두 DB의 schema 개념도 달라 사전에 알아둬야 할 점이 많습니다.
이번에 이관했던 서비스를 예시로 PostgreSQL => MySQL 이관 방법을 정리해보았습니다.

PostgreSQL 과 MySQL schema 차이

  • PostgreSQL에서는 테이블의 집합이 schema ( 위 그림에서 public, myschema )
  • schema 의 집합이 database ( 위 그림에서 postgres , mydb )
  • 테이블의 집합 = schema 가 곧 논리 database 인 MySQL과는 개념이 다름
  • 단 아래와 같이 DB 내에 사용하는 schema 가 단 하나일 땐 PostgreSQL database -> MySQL database 1:1 매핑이 가능함
amon=# \dn
          List of schemas
        Name        |    Owner
--------------------+--------------
 information_schema | testuser
 pg_catalog         | testuser
 pg_toast           | testuser
 pg_toast_temp_1    | testuser
 public             | testuser
(5 rows)

=> public 을 제외한 나머지 schema는 시스템 관련 스키마, pulic은 모든 schema가 접근 가능한 default schema  

test=# select count(*) from information_schema.tables where table_schema='public';
 count
-------
   100
(1 row)

=> 해당 서비스에서는 다른 서비스용 schema 를 생성하지 않고 기본 public schema에 오브젝트를 생성하였음

이관대상 확인

database

postgres=# \l+
                                                                  List of databases
   Name    |    Owner     | Encoding | Collation  |   Ctype    |         Access privileges         |  Size   | Tablespace |        Description
-----------+--------------+----------+------------+------------+-----------------------------------+---------+------------+---------------------------
 test1      | testuser      | UTF8     | en_US.UTF8 | en_US.UTF8 |                                   | 13 MB   | pg_default |
 test2      | testuser      | UTF8     | en_US.UTF8 | en_US.UTF8 |                                   | 172 MB  | pg_default |
                                                               : 

 template0 | postres        | UTF8     | en_US.UTF8 | en_US.UTF8 | =c/"testuser"                 | 5408 kB | pg_default |
                                                               : "testuser"=CTc/"testuser"
 template1 | postgres       | UTF8     | en_US.UTF8 | en_US.UTF8 | =c/"testuser"                 | 5408 kB | pg_default | default template database
                                                               : "testuser"=CTc/"testuser"
  • 이관 대상 PostgreSQL DB 서버 내에 여러 database 들이 존재하는 상황임
  • 원래 개념대로라면 위의 database 하나하나가 MySQL의 DB instance 로 매핑되지만 아래와 같이 각 database 내에서 schema 를 public schema 하나만 사용하고 있어 PostgreSQL 과 MySQL 1:1 매핑이 가능함

schema

test1=# \dn
          List of schemas
        Name        |    Owner
--------------------+--------------
 information_schema | testuser
 pg_catalog         | testuser
 pg_toast           | testuser
 pg_toast_temp_1    | testuser
 public             | testuser
(5 rows)

test1=# select count(*) from information_schema.tables where table_schema='public';
 count
-------
    100
(1 row)

test1=# \dn
          List of schemas
        Name        |    Owner
--------------------+--------------
 information_schema | testuser
 pg_catalog         | testuser
 pg_toast           | testuser
 pg_toast_temp_1    | testuser
 public             | testuser
(5 rows)


test2=# select count(*) from information_schema.tables where table_schema='public';
 count
-------
    84
(1 row)

user

postgres=# select * from pg_catalog.pg_roles;
   rolname    | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcatupdate | rolcanlogin | rolconnlimit | rolpassword | rolvaliduntil | rolconfig |  oid
--------------+----------+------------+---------------+-------------+--------------+-------------+--------------+-------------+---------------+-----------+-------
 testuser     | t        | t          | t             | t           | t            | t           |           -1 | ********    |               |           |    10
.
.

pg2mysql

PostgreSQL dump 파일을 MySQL 쿼리로 변환해주는 툴로 더이상 개발되지 않아 실사용 하기위해 몇가지 부분을 추가하였음

pg2mysql 이슈 및 수정사항

  • 테이블 정의 중 Constraint 부분을 의도적으로 생략하는 이슈
    • foreign , unique constraint 생성 구문 변환되도록 수정
  • Unique key가 일반인덱스로 생성 되는 이슈
    • Unique key로 생성되도록 수정
  • engine 변환 구문 오류
    • default로 InnoDB engine 으로 생성하도록 수정
  • 메모리 제한 이슈
    • pg2mysql 프로그램이 사용할 수 있는 최대 메모리 limit 을 512MB로 제한
  • 이관했던 서비스가 테이블, 컬럼을 모두 대문자로 사용하여 생성구문을 모두 대문자로 생성하도록 strtoupper 함수를 추가했음 소문자로 사용하는 경우 strtoupper 함수부분을 제거해야함
  • cidr, inet 컬럼 미지원
    • PostgreSQL에는 IP를 표현하는 cidr, inet 컬럼 타입이 있으나 MySQL에는 없음, 이부분은 MySQL에서 big int + inet_aton, inet_ation으로 구현할지 varchar 타입으로 전환할지 결정이 필요하여 수정하지 않았음. 참고로, varchar 로 전환시 order by 결과값이 inet 과는 다름
  • FK option
    • PostgreSQL에는 DEFERRABLE INITIALLY DEFERRED ( 제약조건을 트랜잭션 종료할 때 검사하는 설정) 같은 MySQL에서는 지원하지 않는 기능이 있음. 경우에 따라 fk 조건을 삭제하거나 트랜잭션 시작시에 set foreign_key_checks = 0 ; 설정을 주도록 개발팀과 협의가 필요하기 때문에 변환과정에서 생략하지 않고 포함하도록함

사용방법

  • php 설치
$ php --version
PHP 5.3.3 (cli) (built: Mar 22 2017 12:27:09)
  • 첨부파일의 pg2mysql tar 파일을 품
$ ls -ltr
total 44
-rw-r--r-- 1 irteam irteam 15145 May 26  2006 gpl.txt
-rw-r--r-- 1 irteam irteam  2370 Jul 21  2011 pg2mysql.php
-rw-r--r-- 1 irteam irteam  1534 Jul 21  2011 pg2mysql_cli.php
-rw-r--r-- 1 irteam irteam  2985 Jul 21  2011 README
-rw-rw-r-- 1 irteam irteam 13204 Aug 10 20:44 pg2mysql.inc.php.org
-rw-r--r-- 1 irteam irteam 13235 Aug 10 20:44 pg2mysql.inc.php
  • 이관 대상 PostgreSQL dump 파일 준비
### schema only
pg_dump -p port -U username -S dbname  --format p -f dbname_schema.sql

### data only, insert 구문으로
pg_dump -p port -U username -a dbname --inserts --format p -f dbname_data.sql

### schema+ data
pg_dump -p port -U username  dbname --inserts --format p -f dbname_full.sql
  • MySQL 쿼리로 변환
php pg2mysql_cli.php dbname_full.sql mysql_dbname.sql 

데이터 검증 (count 확인)

  • PostgreSQL
#!/bin/bash
database=database_name
user=postgres
echo "Database : $database"
PGCOMMAND=" psql -U $user -d $database -At -c \"
            SELECT   table_name
            FROM     information_schema.tables
            WHERE    table_type='BASE TABLE'
            AND      table_schema='public'
            \""

TABLENAMES=$(export PGPASSWORD=패스워드; eval "$PGCOMMAND")

for TABLENAME in $TABLENAMES; do
    PGCOMMAND=" psql -U $user -d $database -At -c \"  
                SELECT   '$TABLENAME',  
                         count(*)   
                FROM     $TABLENAME  
                \""
    eval "$PGCOMMAND"
done
  • MySQL
#!/bin/bash
database=database_name
for name in `mys $database -B -e "show tables"`;
do
   mys -B --vertical $database -e "select count(*) as $name from $name" | grep -v '^\*' | replace ": " "|"
done
  • index count
# MySQL
select table_name,count(distinct index_name) from information_schema.STATISTICS where index_schema='amon' group by 1 order by 1;

# PostgreSQL
select tablename,count(*) from pg_indexes where schemaname='public' group by 1 order by 1;

=> MySQL로 변환되면서 Auto-increment 컬럼이 PK 가 되기 때문에 MySQL 이 인덱스가 더 많을 수 있음