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

CRUD Operations: The Foundation of Data Management

Every application that stores and manages data relies on a set of basic operations known…

1 week ago

Common PHP Mistakes Every Developer Should Avoid

PHP remains one of the most widely used server-side programming languages, powering platforms such as…

1 week ago

Danfo.js: The JavaScript Data Science Library

Danfo.js is an open-source JavaScript library designed for data manipulation, analysis, and machine learning. It provides…

2 weeks ago

Common Async/Await Mistakes Every JavaScript Developer Should Avoid

JavaScript's async and await keywords revolutionized asynchronous programming by making asynchronous code look and behave more like synchronous code.…

2 weeks ago

PGP Encryption And How It Works

Pretty Good Privacy (PGP) is one of the most widely used encryption systems for securing emails,…

2 weeks ago

Hidden Gems Inside Modern JavaScript

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

3 weeks ago