softare development

How To Migrate from PostgreSQL to MySQL

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.

Why Migrate?

Organizations migrate from PostgreSQL to MySQL for reasons such as:

  • Existing infrastructure already uses MySQL
  • Lower operational complexity
  • Team familiarity with MySQL
  • Hosting provider preferences
  • Application compatibility requirements
  • Performance characteristics for specific workloads

Before You Start

Create a migration inventory:

ItemDescription
TablesAll database tables
ViewsStored views
IndexesPrimary, unique, composite indexes
ConstraintsForeign keys and checks
FunctionsUser-defined functions
TriggersDatabase triggers
Stored ProceduresBusiness logic
ExtensionsPostgreSQL-specific features
Data VolumeTotal records and size

Example:

-- PostgreSQL
SELECT table_name
FROM information_schema.tables
WHERE table_schema='public';

Architecture Overview

Before Migratio

After Migration

Application
     │
     ▼
┌──────────────┐
│ MySQL        │
└──────────────┘

Step 1: Analyze PostgreSQL Schema

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

Step 2: Convert Data Types

One of the biggest migration challenges is data type conversion.

Common Mappings

PostgreSQLMySQL
SERIALINT AUTO_INCREMENT
BIGSERIALBIGINT AUTO_INCREMENT
BOOLEANTINYINT(1)
TEXTLONGTEXT
JSONBJSON
UUIDCHAR(36)
TIMESTAMPTIMESTAMP
BYTEABLOB
NUMERICDECIMAL

Example

PostgreSQL

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    active BOOLEAN,
    metadata JSONB
);

MySQL

CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,
    active TINYINT(1),
    metadata JSON
);

Step 3: Convert Auto Increment Fields

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

Step 4: Convert UUID Columns

PostgreSQL:

id UUID PRIMARY KEY

Often generated using:

gen_random_uuid()

MySQL:

id CHAR(36) PRIMARY KEY

Example:

INSERT INTO users(id)
VALUES(UUID());

Step 5: Convert Boolean Values

PostgreSQL:

active BOOLEAN

Values:

TRUE
FALSE

MySQL:

active TINYINT(1)

Values:

1 = TRUE
0 = FALSE

Step 6: Convert Date and Time Functions

Current Timestamp

PostgreSQL:

NOW()

MySQL:

NOW()

Compatible.

Date Difference

PostgreSQL:

SELECT AGE(NOW(), created_at);

MySQL:

SELECT TIMESTAMPDIFF(DAY, created_at, NOW());

Date Truncation

PostgreSQL:

DATE_TRUNC('month', created_at)

MySQL:

DATE_FORMAT(created_at,'%Y-%m-01')

Step 7: Convert JSON Operations

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;

Step 8: Convert Sequences

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
);

Step 9: Convert PostgreSQL ENUMs

PostgreSQL:

CREATE TYPE order_status AS ENUM (
    'pending',
    'paid',
    'cancelled'
);
status order_status

MySQL:

status ENUM(
    'pending',
    'paid',
    'cancelled'
)

Step 10: Convert Constraints

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.

Step 11: Convert Indexes

PostgreSQL:

CREATE INDEX idx_email
ON users(email);

MySQL:

CREATE INDEX idx_email
ON users(email);

Mostly identical.

Step 12: Convert Full-Text Search

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');

Step 13: Convert Triggers

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 ;

Step 14: Export PostgreSQL Data

Export schema:

pg_dump -U postgres \
--schema-only \
mydb > schema.sql

Export data:

pg_dump -U postgres \
--data-only \
mydb > data.sql

Step 15: Transform SQL

Many teams use tools such as:

  • pgloader
  • DBeaver
  • AWS DMS
  • Talend
  • Apache NiFi

Popular choice:

pgloader postgres://user:pass@host/db \
mysql://user:pass@host/db

Migration Flow:

PostgreSQL
      │
      ▼
   pgloader
      │
      ▼
    MySQL

Step 16: Import into MySQL

Create database:

CREATE DATABASE appdb;

Import:

mysql -u root -p appdb < schema.sql
mysql -u root -p appdb < data.sql

Step 17: Verify Data Integrity

Compare row counts.

PostgreSQL:

SELECT COUNT(*)
FROM users;

MySQL:

SELECT COUNT(*)
FROM users;

Compare:

PostgreSQL = 150,000 rows
MySQL      = 150,000 rows

Check:

  • Foreign keys
  • Null values
  • UUIDs
  • JSON fields
  • Dates
  • Monetary values

Step 18: Application Changes

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');

Common Problems and Solutions

ProblemSolution
SERIAL not workingReplace with AUTO_INCREMENT
JSONB operators failUse JSON_EXTRACT
UUID generation missingUse UUID()
Sequences unavailableUse AUTO_INCREMENT
GIN indexes unsupportedUse FULLTEXT indexes
PL/pgSQL functions failRewrite as MySQL procedures
Array columns unsupportedNormalize tables or use JSON

Example Complete Conversion

PostgreSQL

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_id UUID,
    amount NUMERIC(10,2),
    metadata JSONB,
    created_at TIMESTAMP DEFAULT NOW()
);

MySQL

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

Recommended Migration Workflow

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 pgloaderAWS 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.

Recent Posts

Hidden Gems Inside Modern JavaScript

Modern JavaScript isn’t just let, const, arrow functions, and promises anymore. Over the years, the language has…

14 hours ago

Software Developer Pain Points Ranked: What Frustrates Developers the Most?

Software development is one of the most rewarding careers in technology, but it is also…

1 day ago

How to Print Documents in JavaScript

Printing a document in JavaScript usually means triggering the browser’s print dialog and controlling what…

3 days ago

CSS Display Cheatsheet

The display property controls how an element behaves in the layout and how its children are arranged. Access software…

1 week ago

10 JavaScript Habits Destroying Your Code

JavaScript is one of the most flexible programming languages ever created. That flexibility is powerful,…

1 week ago

Linux Steam Locomotive Bash program

What is Steam Locomotive (sl)? Steam Locomotive (sl) is a small terminal program on Unix/Linux systems…

2 months ago