intermediate 1 day to 2 weeks depending on database size and complexity100% Free Guide

How to Migrate from MySQL to PostgreSQL

Migrating from MySQL to PostgreSQL is one of the most common database migrations. Both are mature open-source relational databases, but they have significant differences in SQL dialect, data types, and behavior. PostgreSQL is stricter about SQL standards compliance, which can surface issues that MySQL silently accepts.

15

Data Type Mappings

12

Syntax Differences

9

Migration Steps

3

Free Tools

Data Type Mappings

MySQLPostgreSQLNotes
INTINTEGER or INT4Identical behavior
BIGINTBIGINT or INT8Identical
TINYINT(1)BOOLEANMySQL uses TINYINT(1) for booleans; PostgreSQL has native BOOLEAN type
AUTO_INCREMENTSERIAL or GENERATED ALWAYS AS IDENTITYPostgreSQL IDENTITY columns are the modern standard
VARCHAR(n)VARCHAR(n)Identical, but PostgreSQL TEXT is often preferred over VARCHAR with no limit
TEXTTEXTIdentical
DATETIMETIMESTAMPPostgreSQL uses TIMESTAMP for datetime values
DATETIME with timezoneTIMESTAMPTZUse TIMESTAMPTZ for timezone-aware timestamps in PostgreSQL
UNSIGNED INTINTEGER (with CHECK constraint)PostgreSQL has no UNSIGNED types; use CHECK (col >= 0)
ENUM('a','b')CREATE TYPE name AS ENUM ('a','b')PostgreSQL ENUMs are database types; MySQL ENUMs are column attributes
JSONJSON or JSONBUse JSONB for indexable, efficient JSON storage in PostgreSQL
BLOBBYTEA or Large ObjectsBYTEA for up to ~1GB; Large Objects for larger binary data
MEDIUMTEXT / LONGTEXTTEXTPostgreSQL TEXT has no size limit
DOUBLEDOUBLE PRECISION or FLOAT8Same semantics
DECIMAL(p,s)NUMERIC(p,s)Functionally identical; NUMERIC is the standard SQL name

SQL Syntax Differences

String Quoting

MySQL

Both single and double quotes for strings

PostgreSQL

Single quotes only for strings; double quotes for identifiers

Must convert: "string" → 'string' in all queries

Auto Increment

MySQL

id INT AUTO_INCREMENT PRIMARY KEY

PostgreSQL

id SERIAL PRIMARY KEY or id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY

SERIAL is a shorthand; IDENTITY is the SQL standard

Backtick Quoting

MySQL

SELECT `column` FROM `table`

PostgreSQL

SELECT "column" FROM "table"

Replace all backticks with double quotes

LIMIT / OFFSET

MySQL

SELECT * FROM t LIMIT 10 OFFSET 20

PostgreSQL

SELECT * FROM t LIMIT 10 OFFSET 20

Identical syntax — no change needed

String Concatenation

MySQL

CONCAT(a, b, c) or a + b

PostgreSQL

a || b || c or CONCAT(a, b, c)

|| is standard SQL concat; CONCAT() also works in PostgreSQL

Date Functions

MySQL

NOW(), DATE_FORMAT(), DATEDIFF()

PostgreSQL

NOW(), TO_CHAR(), date - date

Date functions differ significantly between MySQL and PostgreSQL

GROUP BY

MySQL

Allows non-aggregated columns not in GROUP BY

PostgreSQL

Strict: all non-aggregated columns must be in GROUP BY

Review all GROUP BY queries — many will fail in PostgreSQL

Case Sensitivity

MySQL

Table names case-insensitive on Windows/Mac

PostgreSQL

Column names fold to lowercase; identifiers case-sensitive with quotes

All table/column names should be lowercase without quotes in PostgreSQL

INSERT IGNORE

MySQL

INSERT IGNORE INTO t (col) VALUES (val)

PostgreSQL

INSERT INTO t (col) VALUES (val) ON CONFLICT DO NOTHING

ON CONFLICT is PostgreSQL's upsert mechanism

ON DUPLICATE KEY UPDATE

MySQL

INSERT INTO t (...) VALUES (...) ON DUPLICATE KEY UPDATE col = value

PostgreSQL

INSERT INTO t (...) VALUES (...) ON CONFLICT (col) DO UPDATE SET col = EXCLUDED.col

EXCLUDED refers to the rejected row in PostgreSQL

FULL OUTER JOIN

MySQL

Not supported natively (requires UNION workaround)

PostgreSQL

Natively supported: FULL OUTER JOIN

PostgreSQL supports FULL OUTER JOIN directly

IFNULL

MySQL

IFNULL(col, 'default')

PostgreSQL

COALESCE(col, 'default')

COALESCE is the SQL standard; works in both databases

Step-by-Step Migration Guide

1

Schema Assessment and Inventory

Export your MySQL schema and catalog all tables, views, stored procedures, triggers, and foreign key relationships. Identify MySQL-specific features that need PostgreSQL equivalents.

mysqldump --no-data --routines --triggers mydb > schema.sql
2

Set Up Target PostgreSQL Instance

Install PostgreSQL, create the target database, and configure connection parameters. Install pgloader or your chosen migration tool.

createdb target_db
psql target_db
3

Convert Schema

Convert the MySQL DDL to PostgreSQL-compatible DDL. Replace data types, AUTO_INCREMENT with SERIAL, ENUM types, backticks with double quotes, and MySQL-specific options.

-- MySQL:
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  email VARCHAR(255) NOT NULL
) ENGINE=InnoDB;

-- PostgreSQL:
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email VARCHAR(255) NOT NULL
);
4

Migrate Data

Use pgloader for automated, efficient data migration. It handles type conversions automatically and provides detailed error reports.

-- pgloader command file:
LOAD DATABASE
  FROM mysql://user:pass@localhost/source_db
  INTO postgresql://user:pass@localhost/target_db
SET work_mem to '16MB',
    maintenance_work_mem to '512MB';
5

Migrate Stored Procedures and Functions

MySQL stored procedures use a different syntax than PostgreSQL PL/pgSQL. Each procedure must be manually rewritten for PostgreSQL.

-- MySQL Procedure
CREATE PROCEDURE GetUser(IN userId INT)
BEGIN
  SELECT * FROM users WHERE id = userId;
END;

-- PostgreSQL Function
CREATE OR REPLACE FUNCTION get_user(p_user_id INT)
RETURNS TABLE(id INT, email VARCHAR) AS $$
BEGIN
  RETURN QUERY SELECT u.id, u.email FROM users u WHERE u.id = p_user_id;
END;
$$ LANGUAGE plpgsql;
6

Update Application Queries

Update application code: replace backtick quoting with double quotes or no quotes, replace MySQL-specific functions with PostgreSQL equivalents, fix GROUP BY strictness issues, and update UPSERT syntax.

7

Validate Data Integrity

Compare row counts between source MySQL and target PostgreSQL for all tables. Spot-check critical business records. Run application integration tests against the PostgreSQL database.

-- Run in both MySQL and PostgreSQL, compare results:
SELECT table_name, COUNT(*)
FROM information_schema.tables
JOIN (SELECT table_name FROM information_schema.tables WHERE table_schema = 'mydb') t USING(table_name)
GROUP BY table_name;
8

Performance Testing and Index Optimization

Run EXPLAIN ANALYZE on your most critical queries. PostgreSQL's query planner may choose different strategies than MySQL. Add or adjust indexes as needed. Run ANALYZE to update table statistics.

ANALYZE;
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 42;
9

Cutover

Plan the cutover: take the MySQL database read-only, run a final incremental sync to PostgreSQL, update application connection strings, switch DNS/load balancer, and monitor closely for 24-48 hours.

Common Issues & Solutions

GROUP BY errors: column must appear in GROUP BY clause

PostgreSQL enforces SQL standard GROUP BY. Add all non-aggregated SELECT columns to GROUP BY, or wrap them in an aggregate function. Review every GROUP BY query in your codebase.

Case sensitivity: queries that worked in MySQL fail in PostgreSQL

PostgreSQL folds unquoted identifiers to lowercase. Rename all tables and columns to lowercase in both MySQL and PostgreSQL before migrating. Avoid mixed-case identifiers.

TINYINT(1) boolean values stored as 0/1 not true/false

Map MySQL TINYINT(1) to PostgreSQL BOOLEAN. pgloader does this automatically. Verify that application code handles true/false (not 1/0) from PostgreSQL.

Date/time function differences (DATE_FORMAT, DATEDIFF, etc.)

Replace MySQL-specific date functions: DATE_FORMAT() → TO_CHAR(), DATEDIFF() → (date1 - date2), DATE_ADD() → date + INTERVAL 'n days', NOW() stays the same.

Stored procedures require complete rewrite in PL/pgSQL

MySQL and PostgreSQL use different procedural languages. Each stored procedure must be manually ported to PL/pgSQL syntax. Consider this the largest time investment in the migration.

Recommended Migration Tools

pgloader

FREE

Open-source ETL tool specifically designed for MySQL → PostgreSQL migration. Handles type conversions automatically.

AWS Database Migration Service

Managed cloud migration service supporting MySQL → PostgreSQL with continuous replication.

pgAdmin

FREE

Free PostgreSQL GUI for schema management and query testing.

Flyway / Liquibase

FREE

Schema version management tools for managing schema changes post-migration.

Frequently Asked Questions

How long does it take to migrate from MySQL to PostgreSQL?
Timeline varies significantly: schema-only migration: 1-2 days. Small database (<10GB, few stored procedures): 1 week. Medium database (10-100GB, complex application): 2-4 weeks. Large database (>100GB, many stored procedures/triggers): 1-3 months. The biggest time investment is usually rewriting stored procedures and fixing GROUP BY strictness issues in application queries.
What is the best tool to migrate MySQL to PostgreSQL?
pgloader is the most popular free tool for MySQL → PostgreSQL migration. It handles data type conversions automatically, migrates data efficiently in parallel, and provides detailed error reports. For cloud environments, AWS DMS (Database Migration Service) supports continuous replication for zero-downtime migrations.
Do I need to change my SQL queries after migrating from MySQL to PostgreSQL?
Yes, almost certainly. The most common changes needed: 1) Fix GROUP BY strictness (add all non-aggregated columns). 2) Replace backtick quoting with double quotes. 3) Replace MySQL-specific functions (DATE_FORMAT, IFNULL, REPLACE INTO). 4) Update INSERT IGNORE to ON CONFLICT DO NOTHING. 5) Fix string quoting (double quotes become identifiers in PostgreSQL). Run your full test suite against PostgreSQL to catch all issues.
Can I run MySQL and PostgreSQL simultaneously during migration?
Yes. A dual-write or shadow-write strategy runs both databases in parallel during the migration period. New writes go to both MySQL and PostgreSQL. This allows validation of data consistency before the final cutover. pglogical or AWS DMS can handle continuous replication from MySQL to PostgreSQL during this period.

Ready to Migrate?

Test your PostgreSQL queries in our free SQL Playground before migrating.