데이터베이스 연구실.log

MySQL 연결

PyMySQL과 psycopg 접속, information_schema 기반 MySQL 스키마 분석과 통계 행 수 및 PK 이름의 차이

SSCursorunsignedenumsetvalPyMySQLpsycopginformation_schematable_rowscount(*)auto_incrementidentityPRIMARYAccess denied사용자@호스트

#핵심 요약

실습 미니 MySQL 마이그레이터는 Oracle판 미니 마이그레이터와 화면, 흐름이 같고 소스만 MySQL임

MySQL 접속에는 PyMySQL을 씀
순수 Python이라 따로 설치할 클라이언트가 없음

분석은 MySQL information_schema를 읽음
InnoDB의 tables.table_rows는 통계로 짐작한 값이라, 화면의 행 수는 테이블마다 count(*)로 다시 셈

MySQL 계정은 사용자@호스트 단위라서 PC에서 접속할 수 있는 호스트로 만든 계정이어야 함

#개념과 동작 원리

3~6단계(변환, 이행, 검증, 점검)의 흐름은 Oracle판과 같으며, MySQL에서만 달라지는 타입, 자동 증가, 인덱스, 권한, 쿼리 문법을 아래 절에서 따로 정리함

연결 항목 접속 DB 및 라이브러리 실습 계정 권한
소스 MySQL 8.4, 포트 3306, 예제3에서 만든 데이터베이스 읽기만
대상 PostgreSQL 15, 포트 5432, DB <DB_NAME>의 새 스키마 스키마, 테이블 생성, DB 소유자

#information_schema에서 읽는 것

데이터 사전 수집하는 스키마 정보
tables 테이블 목록, 크기(data_length + index_length), AUTO_INCREMENT 다음 값
columns 컬럼 타입(column_type), NULL 허용, 기본값, extra(auto_increment 등)
table_constraints, key_column_usage, referential_constraints PK, UNIQUE, FK와 참조 규칙
statistics 인덱스
FK를 만들 때 InnoDB가 자동으로 만든 인덱스 포함
views, routines, triggers 뷰 정의, 저장 프로시저, 함수, 트리거

실습 분석 결과: cust 100,000행, orders 1,000,000행, 크기 102MB
자동 4(테이블 2, 뷰 1, 자동 증가 1), 검토 0, 수동 1(프로시저)로 자동 이행 가능 80%
뷰는 백틱을 없애고 IFNULL을 COALESCE로 바꿔 자동 변환하고, orders.order_id의 auto_increment는 다음 값 1,000,001부터 identity로 옮기며, datetime은 timestamp(0)으로 바꿈

Oracle 데이터 사전을 읽는 방식은 메타데이터와 스키마 분석에 있음

#필수 명령어 또는 SQL

#연결 전 MySQL 상태 확인

DB 서버 OS조회만 함
systemctl is-active mysqld
mysqladmin -h 127.0.0.1 -u <USERNAME> -p ping

결과 해석: active, mysqld is alive면 정상임
inactive면 systemctl start mysqld로 띄움
실습에서는 비밀번호를 -p 뒤에 붙여 썼고 그래서 명령줄 비밀번호 경고가 나왔음
-p만 지정하면 비밀번호를 입력받음

#라이브러리 설치와 실행

Anaconda Prompt (도구 폴더)객체 또는 설정을 변경함
cd C:\work\mini_mysql_migrator
pip install -r requirements.txt

결과 해석: streamlit, plotly, pandas, pymysql, psycopg를 설치함
새로 설치한 Anaconda에서는 psycopg와 pymysql만 새로 설치되고, 실습 확인값은 PyMySQL 1.2.3, psycopg 3.3.6임

PC (도구 폴더의 run_mysql_migrator.bat 일부)현재 세션 설정만 변경함
set SRC_HOST=<HOST>
set SRC_PORT=3306
set SRC_USER=<USERNAME>
set SRC_PASSWORD=<PASSWORD>
set TGT_HOST=<HOST>
set TGT_PORT=5432
set TGT_DB=<DB_NAME>
set TGT_USER=<USERNAME>
set TGT_PASSWORD=<PASSWORD>
python -m streamlit run app.py --server.port 8503

결과 해석: 브라우저에서 http://localhost:8503을 엶
미니 PG 게이지 8501, Oracle판 마이그레이터 8502와 겹치지 않게 8503을 쓰며 세 프로그램을 동시에 띄울 수 있음
Anaconda를 찾아 켜는 방법은 Oracle과 PostgreSQL 연결에서 다룸
메모장으로 고칠 때는 CRLF 줄바꿈으로 저장해야 한글 줄이 깨지지 않음

#접속 가능한 호스트 확인

MySQL (관리 계정)조회만 함
select user, host from mysql.user;

결과 해석: 실습 계정은 어디서나 접속할 수 있는 호스트(%)로 만들어져 있음
다른 계정을 쓸 때 Access denied for user가 나면 이 조회로 호스트를 확인함

#3단계 타입 변환과 DDL (MySQL에서 달라지는 것)

MySQL PostgreSQL
int / int unsigned / bigint unsigned integer / bigint / numeric(20)(부호 없는 정수는 한 단계 큰 타입)
tinyint(1) smallint(옵션으로 boolean)
datetime(n) / timestamp(n) timestamp(n)
tinytext~`longtext` / blob 계열 text / bytea
enum('A','B') varchar(n) + CHECK (... in ('A','B'))
json jsonb
auto_increment generated by default as identity + 적재 뒤 setval
생성된 DDL (mig_sql_pg_ddl.sql 일부)객체 또는 설정을 변경함
create table mig_sql_pg.orders (
    order_id  bigint generated by default as identity not null,
    cust_id   bigint,
    order_dt  timestamp(0),
    amount    numeric(12,2),
    status    varchar(10),
    memo      text
);
-- 데이터 적재 후
alter table mig_sql_pg.orders add constraint orders_pkey primary key (order_id);
create index orders_cust_fk on mig_sql_pg.orders (cust_id);
alter table mig_sql_pg.orders add constraint orders_cust_fk foreign key (cust_id) references mig_sql_pg.cust (cust_id);
select setval(pg_get_serial_sequence('mig_sql_pg.orders', 'order_id'),
              greatest(coalesce((select max(order_id) from mig_sql_pg.orders), 0) + 1, 1000001), false);

결과 해석:

대상 스키마 기본값은 소스DB_pg(실습은 mig_sql_pg)이며, 제약 조건, 인덱스는 Oracle판처럼 데이터를 다 넣은 뒤 만듦

InnoDB는 FK 컬럼에 인덱스를 자동으로 만들지만 PostgreSQL은 만들지 않아, MySQL의 FK 인덱스(orders_cust_fk)를 같은 이름으로 FK보다 먼저 만듦
빠뜨리면 orders.cust_id로 찾는 쿼리가 느려짐

뷰는 백틱과 mig_sql. 접두어를 지우고 IFNULL → COALESCE로 바꾸며, 이름만 쓴 테이블이 대상 스키마를 가리키도록 set search_path to mig_sql_pg, public을 먼저 둠

MySQL 8의 information_schema.columns.column_default는 문자 기본값을 따옴표 없이(N) 보여 주므로 문자 타입이면 'N'처럼 따옴표를 붙임

함수 이름을 정규식으로 바꾸면 따옴표 안의 문자열('ifnull(x)')까지 바뀌므로, SQL을 코드와 문자열 조각으로 나눠 코드 쪽만 바꿈

#4단계 데이터 이행 (SSCursor와 COPY)

MySQL은 PyMySQL의 SSCursor(서버에서 흘려 읽기)로 읽어 100만 행을 한꺼번에 메모리에 올리지 않고, PostgreSQL에는 5000행 배치의 COPY로 넣음

실습 기록: 1,100,000행을 8.7초(초당 127,013행), cust 0.7초, orders 6.9초, 적재 후 DDL 7/7 성공

COPY는 order_id 값을 직접 넣어 identity 시퀀스가 1에 머물므로, setval을 하지 않으면 이행 뒤 새 주문에서 duplicate key value violates unique constraint "orders_pkey"가 남

MySQL TIME은 -838~838시간의 기간까지 담지만 PostgreSQL time은 하루 안의 시각만 담아, 기간 값을 담은 컬럼은 interval로 바꿈

#5단계 데이터 검증

mysql 클라이언트 (/tmp/my_src_check.sql)조회만 함
select count(*) from cust;
select count(*) from orders;
select count(*), sum(total_amt) from v_cust_sales;
select * from v_cust_sales order by cust_id limit 3;
select table_name, auto_increment from information_schema.tables
 where table_schema = 'mig_sql' and table_type = 'BASE TABLE';
show index from orders;
psql (/tmp/my_pg_check.sql)조회만 함
\d mig_sql_pg.orders
select count(*) from mig_sql_pg.orders;
select count(*), sum(total_amt) from mig_sql_pg.v_cust_sales;
select * from mig_sql_pg.v_cust_sales order by cust_id limit 3;
select last_value, is_called from mig_sql_pg.orders_order_id_seq;
VM 셸 (oracle)현재 세션 설정만 변경함
cd /tmp
export MYSQL_PWD='<PASSWORD>'
mysql -h 127.0.0.1 -u <USERNAME> mig_sql -t -vvv < my_src_check.sql
psql -h localhost -U <USERNAME> <TARGET_DB_NAME> -e -f my_pg_check.sql

결과 해석:

실습 기록: 양쪽 모두 cust 100,000, orders 1,000,000, 뷰 합계 250444357921.73과 앞 3행이 같고, MySQL AUTO_INCREMENT 1000001과 PostgreSQL last_value 1000001, is_called f가 같음(다음 번호가 1000001이라는 뜻)

화면 자동 검증은 건수, 숫자 합계, 날짜 최소최대, 문자 길이 합, TEXT 길이 합 14개 검사가 모두 일치함

MySQL length()는 바이트 수, PostgreSQL length()는 글자 수라 문자 길이는 양쪽 모두 char_length()로 셈

명령줄의 -p비밀번호는 ps에 보일 수 있어 MYSQL_PWD 환경변수나 대화형 입력을 씀

-t는 표 모양, -vvv는 실행한 SQL과 걸린 시간을 함께 찍음

#6단계 AS-IS/TO-BE 점검

관점 MySQL PostgreSQL
오브젝트 information_schema의 tables, statistics, table_constraints, routines, triggers pg_class, pg_constraint, pg_proc, pg_trigger, identity 컬럼
프로시저 SHOW CREATE PROCEDURE pg_get_functiondef, psql \sf
업무 쿼리 DATE_FORMAT, IFNULL, LIMIT a, b to_char, COALESCE, LIMIT b OFFSET a
권한 schema_privileges, table_privileges has_table_privilege, has_function_privilege

실습 기록: 첫 점검은 프로시저 p_upgrade_grade 누락과 그 EXECUTE 권한 누락으로 조치 2건, Oracle판에서 변환해 둔 mig.p_upgrade_grade를 mig_sql_pg로 가져온 뒤 오브젝트 9/9, 데이터 2/2, 프로시저 1/1, 업무 쿼리 7/7, 권한 15/15로 통과

가져온 프로시저는 본문의 mig. 참조도 mig_sql_pg.로 바꿔야 다른 스키마 테이블을 고치지 않음

가져온 판의 인자 타입은 MySQL DECIMAL(14,2)에서 bigint로 바뀌어 있어, 소수점 금액을 넘길 일이 있으면 numeric(14,2)로 다시 만듦

MySQL은 기본 DB를 고르지 않으면 이름만 쓴 쿼리가 No database selected로 실패해 select_db로 지정함

MySQL은 DB 단위로 ALTER, DROP을 주지만 PostgreSQL에서는 객체 소유자만 가지므로 DDL 권한은 소유권으로 대응하고 권한 대조에서 뺌

#사용 조건과 주의사항

auto_increment 다음 값: 실습에서는 예제3에서 INSERT 후 ROLLBACK으로 번호를 한 번 썼으므로 MySQL의 실제 다음 값은 1,000,002이고, information_schema가 값을 최대 24시간 캐시해 1,000,001로 보일 수 있으며 둘 다 정상이라고 적었음
도구가 캐시된 값으로 identity를 시작할 때 생기는 영향은 기재 없음

MySQL은 PK 이름이 모두 PRIMARY임
PostgreSQL은 스키마 안에서 이름이 겹치면 안 되므로 실습 도구는 <테이블>_pkey로 바꿔 만듦

MySQL에서 PostgreSQL로 옮기는 SQL 방식은 MySQL에서 PostgreSQL로 이행에 있음

SEO_HKabsumveritas@gmail.comBuilt with Claude Code & Codex

마지막 갱신 개인 학습 기록이며 운영 환경 적용 전 공식 문서 확인 필요

두 글자 이상 입력, 위아래 화살표로 이동하고 Enter로 열기