Database migration is one of the most challenging tasks in software engineering. While both PostgreSQL and MySQL are powerful relational database systems, they differ significantly in SQL syntax, data types, indexing strategies, and advanced features.
This guide explains how to migrate a PostgreSQL database to MySQL safely, with schema illustrations and practical examples.
Organizations migrate from PostgreSQL to MySQL for reasons such as:
Create a migration inventory:
| Item | Description |
|---|---|
| Tables | All database tables |
| Views | Stored views |
| Indexes | Primary, unique, composite indexes |
| Constraints | Foreign keys and checks |
| Functions | User-defined functions |
| Triggers | Database triggers |
| Stored Procedures | Business logic |
| Extensions | PostgreSQL-specific features |
| Data Volume | Total records and size |
Example:
-- PostgreSQL
SELECT table_name
FROM information_schema.tables
WHERE table_schema='public'; Before Migratio
After Migration
Application
│
▼
┌──────────────┐
│ MySQL │
└──────────────┘ Example PostgreSQL schema:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(100) UNIQUE,
email VARCHAR(255),
created_at TIMESTAMP DEFAULT NOW()
); Schema Illustration
users
├── id (SERIAL)
├── username
├── email
└── created_at One of the biggest migration challenges is data type conversion.
| PostgreSQL | MySQL |
|---|---|
| SERIAL | INT AUTO_INCREMENT |
| BIGSERIAL | BIGINT AUTO_INCREMENT |
| BOOLEAN | TINYINT(1) |
| TEXT | LONGTEXT |
| JSONB | JSON |
| UUID | CHAR(36) |
| TIMESTAMP | TIMESTAMP |
| BYTEA | BLOB |
| NUMERIC | DECIMAL |
CREATE TABLE products (
id SERIAL PRIMARY KEY,
active BOOLEAN,
metadata JSONB
); CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
active TINYINT(1),
metadata JSON
); PostgreSQL:
id SERIAL PRIMARY KEY Internally PostgreSQL creates:
Sequence
│
▼
Table ID MySQL uses:
id INT AUTO_INCREMENT PRIMARY KEY Architecture:
Table
└── AUTO_INCREMENT
│
▼
Generates IDs PostgreSQL:
id UUID PRIMARY KEY Often generated using:
gen_random_uuid() MySQL:
id CHAR(36) PRIMARY KEY Example:
INSERT INTO users(id)
VALUES(UUID()); PostgreSQL:
active BOOLEAN Values:
TRUE
FALSE MySQL:
active TINYINT(1) Values:
1 = TRUE
0 = FALSE PostgreSQL:
NOW() MySQL:
NOW() Compatible.
PostgreSQL:
SELECT AGE(NOW(), created_at); MySQL:
SELECT TIMESTAMPDIFF(DAY, created_at, NOW()); PostgreSQL:
DATE_TRUNC('month', created_at) MySQL:
DATE_FORMAT(created_at,'%Y-%m-01') PostgreSQL is famous for JSONB support.
PostgreSQL:
SELECT metadata->>'name'
FROM products; Example JSON:
{
"name":"Laptop",
"price":500
} MySQL:
SELECT JSON_UNQUOTE(
JSON_EXTRACT(metadata,'$.name')
)
FROM products; PostgreSQL:
CREATE SEQUENCE invoice_seq; Used as:
nextval('invoice_seq') MySQL has no direct equivalent.
Use:
AUTO_INCREMENT or a custom sequence table:
CREATE TABLE sequences (
next_id BIGINT
); PostgreSQL:
CREATE TYPE order_status AS ENUM (
'pending',
'paid',
'cancelled'
); status order_status MySQL:
status ENUM(
'pending',
'paid',
'cancelled'
) PostgreSQL:
ALTER TABLE users
ADD CONSTRAINT email_check
CHECK (
email LIKE '%@%'
); MySQL 8.0 supports CHECK constraints:
ALTER TABLE users
ADD CONSTRAINT email_check
CHECK (
email LIKE '%@%'
); For older MySQL versions:
Use triggers or application validation.
PostgreSQL:
CREATE INDEX idx_email
ON users(email); MySQL:
CREATE INDEX idx_email
ON users(email); Mostly identical.
PostgreSQL:
CREATE INDEX idx_search
ON articles
USING GIN(
to_tsvector('english',content)
); Search:
SELECT *
FROM articles
WHERE to_tsvector(content)
@@ plainto_tsquery('database'); MySQL:
ALTER TABLE articles
ADD FULLTEXT(content); Search:
SELECT *
FROM articles
WHERE MATCH(content)
AGAINST('database'); PostgreSQL:
CREATE FUNCTION update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql; Trigger:
CREATE TRIGGER users_update
BEFORE UPDATE
ON users
FOR EACH ROW
EXECUTE FUNCTION update_timestamp(); MySQL:
DELIMITER //
CREATE TRIGGER users_update
BEFORE UPDATE
ON users
FOR EACH ROW
BEGIN
SET NEW.updated_at = NOW();
END//
DELIMITER ; Export schema:
pg_dump -U postgres \
--schema-only \
mydb > schema.sql Export data:
pg_dump -U postgres \
--data-only \
mydb > data.sql Many teams use tools such as:
Popular choice:
pgloader postgres://user:pass@host/db \
mysql://user:pass@host/db Migration Flow:
PostgreSQL
│
▼
pgloader
│
▼
MySQL Create database:
CREATE DATABASE appdb; Import:
mysql -u root -p appdb < schema.sql
mysql -u root -p appdb < data.sql Compare row counts.
PostgreSQL:
SELECT COUNT(*)
FROM users; MySQL:
SELECT COUNT(*)
FROM users; Compare:
PostgreSQL = 150,000 rows
MySQL = 150,000 rows Check:
Update connection strings.
PostgreSQL:
DATABASE_URL=postgres://user:password@localhost/app MySQL:
DATABASE_URL=mysql://user:password@localhost/app Example Node.js connection:
// PostgreSQL
const { Pool } = require('pg'); becomes
// MySQL
const mysql = require('mysql2'); | Problem | Solution |
|---|---|
| SERIAL not working | Replace with AUTO_INCREMENT |
| JSONB operators fail | Use JSON_EXTRACT |
| UUID generation missing | Use UUID() |
| Sequences unavailable | Use AUTO_INCREMENT |
| GIN indexes unsupported | Use FULLTEXT indexes |
| PL/pgSQL functions fail | Rewrite as MySQL procedures |
| Array columns unsupported | Normalize tables or use JSON |
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id UUID,
amount NUMERIC(10,2),
metadata JSONB,
created_at TIMESTAMP DEFAULT NOW()
); CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id CHAR(36),
amount DECIMAL(10,2),
metadata JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); Schema Illustration
orders
├── id
│ └── AUTO_INCREMENT
├── customer_id
├── amount
├── metadata
└── created_at 1. Backup PostgreSQL
│
▼
2. Analyze Schema
│
▼
3. Convert Data Types
│
▼
4. Convert Functions
│
▼
5. Convert Triggers
│
▼
6. Migrate Data
│
▼
7. Verify Integrity
│
▼
8. Update Application
│
▼
9. Performance Testing
│
▼
10. Production Cutover
For large production databases (millions of records), tools such as pgloader, AWS Database Migration Service (DMS), or dual-write replication strategies are generally safer than exporting SQL dumps manually, because they can handle schema conversion, large datasets, and minimal downtime more effectively.
Latest tech news and coding tips.
Every application that stores and manages data relies on a set of basic operations known…
PHP remains one of the most widely used server-side programming languages, powering platforms such as…
Danfo.js is an open-source JavaScript library designed for data manipulation, analysis, and machine learning. It provides…
JavaScript's async and await keywords revolutionized asynchronous programming by making asynchronous code look and behave more like synchronous code.…
Pretty Good Privacy (PGP) is one of the most widely used encryption systems for securing emails,…
Modern JavaScript isn’t just let, const, arrow functions, and promises anymore. Over the years, the language has…