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.
Modern JavaScript isn’t just let, const, arrow functions, and promises anymore. Over the years, the language has…
Software development is one of the most rewarding careers in technology, but it is also…
Printing a document in JavaScript usually means triggering the browser’s print dialog and controlling what…
The display property controls how an element behaves in the layout and how its children are arranged. Access software…
JavaScript is one of the most flexible programming languages ever created. That flexibility is powerful,…
What is Steam Locomotive (sl)? Steam Locomotive (sl) is a small terminal program on Unix/Linux systems…