MySQL vs PostgreSQL SQL 문법 20가지 다른점 — 실무 예제로 비교하기

데이터베이스를 처음 배우면 이런 생각을 하기 쉽습니다.

“SQL이면 다 같은 SQL 아닌가?”

절반은 맞고 절반은 틀립니다.

MySQL과 PostgreSQL 모두 SQL을 사용하기 때문에 기본적인 SELECT, INSERT, UPDATE, DELETE, JOIN, GROUP BY 문법은 상당히 비슷합니다.

SELECT id, name, email
FROM users
WHERE age >= 20
ORDER BY created_at DESC;

이 정도의 기본 쿼리는 두 데이터베이스에서 거의 동일하게 사용할 수 있습니다.
하지만 실제 프로젝트에서는 이야기가 달라집니다.

MySQL에서 잘 실행되던 SQL을 PostgreSQL에 그대로 복사했더니 오류가 나거나, 오류 없이 실행됐는데 결과가 미묘하게 달라지는 경우도 있습니다.

특히 다음 영역에서 차이가 자주 발생합니다.

  • 문자열과 컬럼명을 감싸는 방법
  • 자동 증가 PK
  • UPSERT
  • 대소문자 검색
  • 문자열 연결
  • 날짜와 시간 함수
  • Boolean 처리
  • 형 변환
  • JSON
  • 자동 생성 ID 반환
  • UPDATE/DELETE 건수 제한
  • NULL 처리와 정렬
  • 데이터베이스와 스키마 개념

이번 글에서는 단순 문법표가 아니라 MySQL을 사용하던 개발자가 PostgreSQL로 넘어갈 때 실제로 자주 부딪히는 차이를 예제로 살펴보겠습니다.


Table of Contents

MySQL vs PostgreSQL SQL 문법 한눈에 보기

기능MySQLPostgreSQL
문자열'text', 설정에 따라 "text"도 가능'text'
식별자`column`"column"
자동 증가AUTO_INCREMENTIDENTITY, SERIAL
UPSERTON DUPLICATE KEY UPDATEON CONFLICT
문자열 연결CONCAT()`, CONCAT()`
대소문자 무시 검색Collation에 따라 달라짐ILIKE
현재 시각NOW()NOW()
날짜 계산DATE_ADD(), DATE_SUB()INTERVAL
형 변환CAST()CAST(), ::
BooleanTINYINT(1) 사용이 흔함BOOLEAN
JSONJSONJSON, JSONB
생성 ID 반환LAST_INSERT_ID()RETURNING
배열 타입네이티브 배열 없음네이티브 배열 지원
UPDATE LIMIT지원직접 지원하지 않음

1. 문자열과 테이블·컬럼명을 감싸는 방법

SQL에서는 먼저 두 가지를 구분해야 합니다.

  1. 실제 데이터 값
  2. 테이블명이나 컬럼명 같은 식별자

MySQL

SELECT `name`
FROM `users`
WHERE `name` = '홍길동';

MySQL에서는 테이블이나 컬럼명을 명시적으로 감쌀 때 백틱을 사용합니다.

`users`
`name`

문자열 값은 작은따옴표를 사용하는 것이 가장 안전합니다.

'홍길동'

MySQL 기본 설정에서는 다음 코드가 동작하는 경우도 있습니다.

WHERE name = "홍길동"

하지만 ANSI_QUOTES SQL 모드가 활성화되어 있다면 큰따옴표는 문자열이 아니라 식별자로 해석됩니다.

따라서 실무에서는 MySQL에서도 문자열을 다음처럼 쓰는 습관이 좋습니다.

WHERE name = '홍길동'

PostgreSQL

SELECT "name"
FROM "users"
WHERE "name" = '홍길동';

PostgreSQL에서는:

'문자열'
"식별자"

라고 생각하면 됩니다.

하지만 PostgreSQL에서 모든 컬럼에 큰따옴표를 붙여야 하는 것은 아닙니다.

보통은 다음처럼 사용합니다.

SELECT name FROM users;

실무에서는 테이블과 컬럼명을 소문자 snake_case로 만들면 훨씬 편합니다.

user_profiles
created_at
updated_at

반대로 다음처럼 이름을 만들면:

CREATE TABLE "UserProfile" (
    "userId" INTEGER
);

이후에도 계속 정확하게 큰따옴표를 써야 합니다.

SELECT "userId"
FROM "UserProfile";

따라서 PostgreSQL에서도 다음처럼 설계하는 편이 관리하기 좋습니다.

CREATE TABLE user_profiles (
    user_id INTEGER
);

2. LIMIT과 OFFSET — 페이징 처리

게시글, 회원, 상품 목록을 페이지 단위로 가져올 때 매우 자주 사용합니다.

예를 들어 20개를 건너뛰고 다음 10개를 가져온다고 해보겠습니다.

MySQL

SELECT *
FROM posts
ORDER BY id DESC
LIMIT 10 OFFSET 20;

MySQL에서는 다음 축약형도 가능합니다.

SELECT *
FROM posts
ORDER BY id DESC
LIMIT 20, 10;

여기서:

LIMIT 건너뛸_개수, 가져올_개수

입니다.

즉:

LIMIT 20, 10

은 20개를 건너뛰고 10개를 가져옵니다.

PostgreSQL

SELECT *
FROM posts
ORDER BY id DESC
LIMIT 10 OFFSET 20;

PostgreSQL에서는 MySQL의 다음 형태를 사용할 수 없습니다.

LIMIT 20, 10

실무 팁

두 DB 간 이식성을 생각한다면 MySQL에서도 다음 방식을 쓰는 편이 좋습니다.

LIMIT 10 OFFSET 20

3. 자동 증가 PK — AUTO_INCREMENT vs IDENTITY

사용자 테이블을 만든다고 해보겠습니다.

MySQL

CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

이후:

INSERT INTO users (name)
VALUES ('홍길동');

이라고 하면 id가 자동으로 증가합니다.

PostgreSQL

예전부터 매우 많이 사용된 방식은 SERIAL입니다.

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

큰 정수가 필요하다면:

BIGSERIAL

을 사용할 수 있습니다.

다만 새로운 PostgreSQL 테이블에서는 SQL 표준에 가까운 IDENTITY 방식도 많이 사용합니다.

CREATE TABLE users (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

또는:

id BIGINT GENERATED BY DEFAULT AS IDENTITY

를 사용할 수 있습니다.

정리하면:

MySQL
AUTO_INCREMENT

PostgreSQL
GENERATED ... AS IDENTITY

입니다.

기존 PostgreSQL 코드에서 SERIAL을 발견했다고 해서 잘못된 코드는 아닙니다.


4. INSERT 후 생성된 ID 가져오기

백엔드 개발에서 정말 자주 필요한 기능입니다.

사용자를 하나 생성하고 방금 만들어진 id를 받아야 한다고 해보겠습니다.

MySQL

INSERT INTO users (name)
VALUES ('홍길동');

SELECT LAST_INSERT_ID();

MySQL 드라이버나 ORM에서 Insert ID를 직접 반환해주는 경우도 많습니다.

PostgreSQL

PostgreSQL에서는 RETURNING을 사용할 수 있습니다.

INSERT INTO users (name)
VALUES ('홍길동')
RETURNING id;

결과:

id
---
101

여러 컬럼도 가능합니다.

INSERT INTO users (name, email)
VALUES ('홍길동', 'hong@example.com')
RETURNING id, name, email, created_at;

전체 행을 반환할 수도 있습니다.

RETURNING *;

심지어 UPDATE, DELETE에서도 사용할 수 있습니다.

UPDATE users
SET name = '김길동'
WHERE id = 10
RETURNING *;
DELETE FROM users
WHERE id = 10
RETURNING id, name;

REST API나 백엔드 개발에서는 상당히 편리한 기능입니다.


5. 중복 데이터 처리 — UPSERT

UPSERT란 간단히 말하면:

없으면 INSERT
이미 있으면 UPDATE

입니다.

회원 이메일, 상품 코드, 외부 API ID 등을 저장할 때 자주 사용합니다.

MySQL

INSERT INTO users (id, email, name)
VALUES (1, 'hong@example.com', '홍길동')
ON DUPLICATE KEY UPDATE
    name = '홍길동';

PostgreSQL

INSERT INTO users (id, email, name)
VALUES (1, 'hong@example.com', '홍길동')
ON CONFLICT (id)
DO UPDATE SET
    name = '홍길동';

이메일 중복을 기준으로 처리하고 싶다면:

INSERT INTO users (email, name)
VALUES ('hong@example.com', '홍길동')
ON CONFLICT (email)
DO UPDATE SET
    name = EXCLUDED.name;

여기서 EXCLUDED.name새롭게 INSERT하려던 값을 의미합니다.

예를 들어 기존 데이터가:

hong@example.com | 홍길동

인데 새 요청이:

hong@example.com | 홍길동2

라면:

EXCLUDED.name

'홍길동2'입니다.


6. 문자열 연결

성과 이름을 합친다고 해보겠습니다.

MySQL

SELECT CONCAT(last_name, first_name)<br>FROM users;

공백까지 넣는다면:

SELECT CONCAT(first_name, ' ', last_name)
FROM users;

PostgreSQL

PostgreSQL에서는 ||를 자주 사용합니다.

SELECT first_name || ' ' || last_name
FROM users;

CONCAT()도 사용할 수 있습니다.

SELECT CONCAT(first_name, ' ', last_name)
FROM users;

7. LIKE 대소문자 검색

여기서 자주 잘못 알려지는 내용이 있습니다.

MySQL의 LIKE는 항상 대소문자를 구분하지 않는다.

정확히는 그렇지 않습니다.

MySQL 문자열 비교는 사용 중인 character set과 collation의 영향을 받습니다.

SELECT *
FROM users
WHERE name LIKE 'kim%';

대소문자를 구분하지 않는 collation이라면:

kim
Kim
KIM

이 모두 검색될 수 있습니다.

하지만 대소문자를 구분하는 collation이라면 결과가 달라질 수 있습니다.

즉 MySQL에서는:

LIKE = 무조건 대소문자 무시

라고 외우면 안 됩니다.

PostgreSQL

일반적인 패턴 검색:

SELECT *
FROM users
WHERE name LIKE 'kim%';

대소문자를 무시하려면:

SELECT *
FROM users
WHERE name ILIKE 'kim%';

검색창을 만들 때는 단순 문법 외에도 다음을 확인해야 합니다.

  • Collation
  • 인덱스
  • 데이터량
  • 한글·영문 혼합
  • Prefix 검색
  • %keyword% 검색

특히:

LIKE '%검색어%'

형태는 데이터량이 커지면 성능 이슈가 발생할 수 있습니다.


8. Boolean 타입

회원 활성화 여부를 저장한다고 해보겠습니다.

is_active

MySQL

MySQL 프로젝트에서는 흔히 다음 타입을 볼 수 있습니다.

is_active TINYINT(1)

관례상:

1 = true
0 = false

처럼 사용합니다.

SELECT *
FROM users
WHERE is_active = 1;

PostgreSQL

PostgreSQL에는 네이티브 BOOLEAN 타입이 있습니다.

is_active BOOLEAN

삽입:

INSERT INTO users (name, is_active)
VALUES ('홍길동', TRUE);

조회:

SELECT *
FROM users
WHERE is_active = TRUE;

또는:

SELECT *
FROM users
WHERE is_active;

반대 조건:

WHERE NOT is_active;

MySQL → PostgreSQL 마이그레이션에서는 TINYINT(1)이 실제 숫자인지 Boolean 플래그인지 구분해야 합니다.


9. 날짜와 시간 계산

날짜 함수는 DB 이전 시 수정할 일이 매우 많습니다.

현재 시간

두 DB 모두 다음을 사용할 수 있습니다.

SELECT NOW();

최근 7일 데이터

MySQL

SELECT *
FROM orders
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY);

PostgreSQL

SELECT *
FROM orders
WHERE created_at >= NOW() - INTERVAL '7 days';

한 달 후 날짜

MySQL

SELECT DATE_ADD(NOW(), INTERVAL 1 MONTH);

PostgreSQL

SELECT NOW() + INTERVAL '1 month';

날짜 부분만 추출

MySQL:

SELECT DATE(created_at)
FROM orders;

PostgreSQL:

SELECT created_at::date
FROM orders;

10. 형 변환 — CAST와 PostgreSQL의 ::

문자열 '123'을 숫자로 변환해보겠습니다.

MySQL

SELECT CAST('123' AS UNSIGNED);

또는:

SELECT CAST('123' AS SIGNED);

PostgreSQL

표준 방식:

SELECT CAST('123' AS INTEGER);

PostgreSQL에서는 다음 축약 표현도 매우 자주 사용합니다.

SELECT '123'::INTEGER;

날짜:

SELECT '2026-08-31'::DATE;

JSONB:

SELECT '{"name":"Hong"}'::JSONB;

즉:

값::타입

이라는 의미입니다.


11. NULL 처리 — IFNULL vs COALESCE

닉네임이 없으면 이름을 출력한다고 해보겠습니다.

MySQL

SELECT IFNULL(nickname, name)
FROM users;

하지만 MySQL에서도 COALESCE()를 사용할 수 있습니다.

SELECT COALESCE(nickname, name)
FROM users;

PostgreSQL

SELECT COALESCE(nickname, name)
FROM users;

여러 값도 가능합니다.

SELECT COALESCE(
    nickname,
    display_name,
    name,
    '이름 없음'
)
FROM users;

왼쪽부터 검사해 처음 발견되는 NULL이 아닌 값을 반환합니다.

실무 팁

두 DB에서 모두 동작하는 SQL을 선호한다면:

COALESCE()

를 사용하는 편이 좋습니다.


12. NULL 정렬

다음 데이터가 있다고 해보겠습니다.

score
-----
100
80
NULL
70

NULL의 기본 정렬 위치는 DBMS와 정렬 방향에 따라 기대와 다르게 보일 수 있습니다.

PostgreSQL에서는 다음처럼 명시할 수 있습니다.

SELECT *
FROM users
ORDER BY score DESC NULLS LAST;

또는:

ORDER BY score ASC NULLS FIRST;

사용자 화면에 표시되는 순서가 중요한 경우 DB 기본값에 의존하지 말고 NULL 위치를 의도적으로 지정하는 것이 안전합니다.


13. JSON — MySQL JSON vs PostgreSQL JSONB

현대 웹 개발에서는 JSON 지원도 중요한 비교 포인트입니다.

사용자 설정을 다음처럼 저장한다고 해보겠습니다.

{
  "theme": "dark",
  "language": "ko"
}

MySQL

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    settings JSON
);

조회:

SELECT JSON_EXTRACT(settings, '$.theme')
FROM users;

또는:

SELECT settings->'$.theme'
FROM users;

문자열 값으로 꺼내려면:

SELECT settings->>'$.theme'
FROM users;

PostgreSQL

PostgreSQL에서는 JSONJSONB를 제공합니다.

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    settings JSONB
);

JSON 값 조회:

SELECT settings->'theme'
FROM users;

텍스트로 꺼내려면:

SELECT settings->>'theme'
FROM users;

조건 검색:

SELECT *
FROM users
WHERE settings->>'theme' = 'dark';

포함 여부 검사도 가능합니다.

SELECT *
FROM users
WHERE settings @> '{"theme":"dark"}';

검색과 인덱싱이 중요한 데이터에서는 PostgreSQL의 JSONB가 강력한 선택지가 될 수 있습니다.


14. PostgreSQL에는 배열 타입도 있다

게시글의 태그를 저장한다고 해보겠습니다.

PostgreSQL에서는 네이티브 배열 타입을 사용할 수 있습니다.

CREATE TABLE posts (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    title TEXT,
    tags TEXT[]
);

삽입:

INSERT INTO posts (title, tags)
VALUES (
    'PostgreSQL 입문',
    ARRAY['database', 'postgresql', 'sql']
);

조회:

SELECT *
FROM posts
WHERE 'postgresql' = ANY(tags);

MySQL에는 PostgreSQL과 같은 네이티브 배열 타입이 없습니다.

일반적으로 다음 방법을 사용합니다.

  • 별도 관계 테이블
  • JSON
  • 다른 정규화 구조

물론 PostgreSQL에 배열 타입이 있다고 해서 모든 다대다 관계를 배열로 만드는 것이 좋은 것은 아닙니다.

관계형 모델이 적합하다면 별도 테이블이 더 좋은 설계입니다.


15. UPDATE / DELETE에 LIMIT 사용

대량 작업에서 중요한 차이입니다.

오래된 로그 100개만 삭제한다고 해보겠습니다.

MySQL

DELETE FROM logs
WHERE created_at < '2025-01-01'
LIMIT 100;

UPDATE에도 다음과 같은 패턴을 사용할 수 있습니다.

UPDATE jobs
SET status = 'processing'
WHERE status = 'waiting'
LIMIT 10;

PostgreSQL

PostgreSQL에서는 같은 형태의:

UPDATE ... LIMIT
DELETE ... LIMIT

을 직접 사용하지 않습니다.

대상 행을 먼저 선택하는 방식으로 처리할 수 있습니다.

DELETE FROM logs
WHERE id IN (
    SELECT id
    FROM logs
    WHERE created_at < DATE '2025-01-01'
    ORDER BY id
    LIMIT 100
);

MySQL의 배치 작업을 PostgreSQL로 옮길 때 자주 수정하게 되는 부분입니다.


16. 데이터베이스와 스키마 개념

MySQL 사용자에게 PostgreSQL의 schema는 처음에는 낯설 수 있습니다.

MySQL 프로젝트에서는 보통:

shop
  users
  products
  orders

처럼 하나의 데이터베이스 안에 테이블들을 구성합니다.

PostgreSQL에서는 하나의 데이터베이스 안에 여러 스키마를 둘 수 있습니다.

shop database

 public
   users
   products

 payment
   transactions
   refunds

 analytics
    events
    reports

조회:

SELECT *
FROM payment.transactions;

큰 프로젝트에서 기능이나 도메인을 논리적으로 분리할 때 유용합니다.


17. 예약어를 테이블명이나 컬럼명으로 쓰지 말자

다음과 같은 이름은 DBMS 예약어와 충돌할 가능성이 있습니다.

order
group
user
references
limit

MySQL에서는 필요한 경우:

SELECT `order`
FROM orders;

PostgreSQL에서는:

SELECT "order"
FROM orders;

처럼 처리해야 할 수 있습니다.

하지만 애초에 다음처럼 명확한 이름을 사용하는 편이 좋습니다.

orders
user_accounts
user_groups

MySQL에서는 문제없던 이름이 PostgreSQL의 예약어와 충돌하는 상황도 있기 때문에 마이그레이션 전에 확인할 필요가 있습니다.


18. PostgreSQL RETURNING의 실무 활용

다음 API가 있다고 해보겠습니다.

POST /users

사용자를 생성하고 생성된 정보를 바로 응답해야 합니다.

PostgreSQL:

INSERT INTO users (name, email)
VALUES ('홍길동', 'hong@example.com')
RETURNING id, name, email, created_at;

별도 SELECT가 필요하지 않습니다.

UPDATE도:

UPDATE users
SET name = '김길동'
WHERE id = 10
RETURNING *;

DELETE도:

DELETE FROM users
WHERE id = 10
RETURNING id, name;

가 가능합니다.

백엔드 API 개발에서 PostgreSQL을 사용할 때 알아두면 매우 유용합니다.


19. MySQL → PostgreSQL 이전 시 우선 검색할 문법

실제 마이그레이션을 진행하고 있다면 코드와 SQL 파일에서 아래 항목부터 검색하는 것이 좋습니다.

AUTO_INCREMENT

MySQL:

AUTO_INCREMENT

PostgreSQL 후보:

GENERATED BY DEFAULT AS IDENTITY

백틱

MySQL:

SELECT `name`
FROM `users`;

PostgreSQL:

SELECT name
FROM users;

IFNULL

MySQL:

IFNULL(name, '없음')

PostgreSQL:

COALESCE(name, '없음')

DATE_ADD / DATE_SUB

MySQL:

DATE_SUB(NOW(), INTERVAL 7 DAY)

PostgreSQL:

NOW() - INTERVAL '7 days'

ON DUPLICATE KEY UPDATE

MySQL:

ON DUPLICATE KEY UPDATE

PostgreSQL:

ON CONFLICT (...) DO UPDATE

LIMIT offset,count

MySQL:

LIMIT 20, 10

PostgreSQL:

LIMIT 10 OFFSET 20

TINYINT(1)

Boolean 값으로 사용하고 있다면 PostgreSQL에서는:

BOOLEAN

전환을 검토할 수 있습니다.


JSON 함수

MySQL 코드에:

JSON_EXTRACT()
JSON_SET()
JSON_CONTAINS()

등이 있다면 PostgreSQL JSON/JSONB 연산자와 함수를 이용해 다시 작성해야 할 수 있습니다.


20. ORM을 사용하면 SQL 차이를 몰라도 될까?

Prisma, Sequelize, TypeORM, SQLAlchemy, Django ORM, Hibernate 같은 ORM을 사용하면 많은 차이를 ORM이 대신 처리합니다.

그래서 평소에는:

User.findMany()

같은 애플리케이션 코드만 보고 SQL 차이를 크게 느끼지 못할 수 있습니다.

하지만 다음 상황에서는 결국 DBMS 차이를 알아야 합니다.

  • 직접 SQL을 작성할 때
  • Native Query를 사용할 때
  • 복잡한 JOIN을 작성할 때
  • Migration SQL을 수정할 때
  • JSON 검색을 할 때
  • 인덱스를 튜닝할 때
  • 대량 UPDATE/DELETE를 수행할 때
  • MySQL → PostgreSQL 이전
  • ORM이 생성한 SQL을 분석할 때
  • 느린 쿼리의 실행 계획을 확인할 때

즉 ORM을 쓰더라도 데이터베이스의 특성까지 완전히 추상화할 수 있는 것은 아닙니다.


MySQL과 PostgreSQL 중 무엇을 선택해야 할까?

단순히:

MySQL = 초보용
PostgreSQL = 고급용

처럼 구분하는 것은 적절하지 않습니다.

두 제품 모두 오랜 기간 검증된 강력한 관계형 데이터베이스입니다.

MySQL이 잘 맞는 경우

예를 들면:

  • 기존 서비스가 MySQL 기반
  • 팀에 MySQL 운영 경험이 많음
  • 사용하는 CMS나 솔루션이 MySQL 생태계에 최적화
  • 일반적인 웹 애플리케이션
  • 기존 인프라와 호환성이 중요함

특히 WordPress 생태계에서는 MySQL 및 호환 데이터베이스를 매우 흔하게 사용합니다.

PostgreSQL이 잘 맞는 경우

예를 들면:

  • 복잡한 SQL을 많이 사용
  • JSONB를 적극 활용
  • 다양한 데이터 타입이 필요
  • 복잡한 분석 쿼리가 많음
  • 확장 기능을 적극 사용할 계획
  • SQL 표준 친화적인 설계를 선호

중요한 것은 제품 이름보다:

프로젝트 요구사항
+
팀의 경험
+
운영 환경
+
프레임워크
+
데이터 규모
+
쿼리 특성

입니다.


MySQL → PostgreSQL 실무 체크리스트

마이그레이션 중이라면 최소한 아래를 확인해보세요.

  • 백틱(`)을 사용하고 있는가?
  • 문자열 값에 큰따옴표를 사용하고 있는가?
  • AUTO_INCREMENT가 있는가?
  • LIMIT offset, count 문법이 있는가?
  • ON DUPLICATE KEY UPDATE가 있는가?
  • IFNULL()을 사용하고 있는가?
  • DATE_ADD() 또는 DATE_SUB()을 사용하고 있는가?
  • TINYINT(1)을 Boolean으로 사용하고 있는가?
  • UPDATE ... LIMIT을 사용하고 있는가?
  • DELETE ... LIMIT을 사용하고 있는가?
  • MySQL 전용 JSON 함수를 사용하는가?
  • 특정 Collation의 문자열 비교 방식에 의존하는가?
  • 자동 증가 ID를 가져오는 로직이 있는가?
  • 예약어를 테이블명 또는 컬럼명으로 사용하는가?
  • NULL 정렬 순서에 의존하고 있는가?
  • Raw SQL이나 Stored Procedure가 있는가?

하나라도 해당한다면 DB 연결 정보만 MySQL에서 PostgreSQL로 변경해서는 안 됩니다.

SQL뿐 아니라 다음도 함께 확인해야 합니다.

  • 데이터 타입
  • 인덱스
  • 제약조건
  • 트랜잭션
  • Collation
  • 날짜·시간 처리
  • JSON
  • ORM 동작

정리

MySQL과 PostgreSQL은 모두 SQL을 사용하기 때문에 기본적인 CRUD는 상당히 비슷합니다.

SELECT
INSERT
UPDATE
DELETE
JOIN
GROUP BY
ORDER BY

단계에서는 큰 차이를 느끼지 못할 수도 있습니다.

하지만 실제 서비스를 개발하거나 데이터베이스를 이전하기 시작하면 차이가 분명하게 나타납니다.

핵심만 다시 정리하면:

MySQLPostgreSQL
`column` “column”
AUTO_INCREMENTIDENTITY
ON DUPLICATE KEY UPDATE ON CONFLICT
IFNULLCOALESCE
DATE_ADD / DATE_SUBNTERVAL 연산
LIMIT offset,countLIMIT count OFFSET offset
TINYINT(1)BOOLEAN
LAST_INSERT_ID() RETURNING
JSONJSON / JSONB

가장 중요한 것은:

“SQL 문법이 비슷하다”와 “서로 완전히 호환된다”는 전혀 다른 이야기입니다.

MySQL과 PostgreSQL 사이에서 서비스를 이전한다면 단순 문법 변환만 하지 말고 다음까지 함께 확인하는 것이 좋습니다.

  • 데이터 타입
  • 인덱스
  • 제약조건
  • 트랜잭션
  • Collation
  • 날짜와 시간
  • JSON
  • ORM
  • 실행 계획

이 부분까지 확인해야 “SQL은 실행되는데 결과가 다르다”, “이전 후 갑자기 쿼리가 느려졌다” 같은 문제를 줄일 수 있습니다.


FAQ

MySQL SQL을 PostgreSQL에서 그대로 사용할 수 있나요?

기본적인 CRUD 쿼리는 상당 부분 비슷하지만 완전히 호환되지는 않습니다.

특히 자동 증가, UPSERT, 날짜 함수, JSON 함수, 문자열 처리 등은 수정이 필요한 경우가 많습니다.

PostgreSQL에서 SERIAL을 사용하면 안 되나요?

사용할 수 있습니다.

기존 PostgreSQL 프로젝트에서도 매우 흔합니다. 신규 테이블에서는 GENERATED ... AS IDENTITY 방식도 함께 검토하면 됩니다.

PostgreSQL에서는 모든 컬럼에 큰따옴표를 붙여야 하나요?

아닙니다.

소문자와 snake_case로 이름을 만들면 대부분 다음처럼 사용할 수 있습니다.

SELECT user_id, created_at
FROM user_profiles;

MySQL의 LIKE는 항상 대소문자를 구분하지 않나요?

아닙니다.

사용 중인 Character Set과 Collation에 따라 비교 결과가 달라집니다.

MySQL에서 PostgreSQL로 변경하면 성능이 무조건 좋아지나요?

그렇지 않습니다.

데이터 구조, SQL, 인덱스, 쿼리 패턴, 동시 접속량, 메모리 설정 등 다양한 요소가 성능을 결정합니다.


출처 및 참고자료

이 글은 MySQL과 PostgreSQL의 공식 문서를 기준으로 작성·검토했습니다.

MySQL 공식 문서

PostgreSQL 공식 문서

※ MySQL과 PostgreSQL의 문법 및 기능은 버전에 따라 차이가 있을 수 있습니다. 실제 프로젝트에서는 사용 중인 데이터베이스 버전의 공식 문서를 함께 확인하는 것을 권장합니다.

Leave a Comment