MySQL 8.0 → PostgreSQL
Step-by-step procedure for migrating a MySQL 8.0 database (on-premises or RDS MySQL) to a PostgreSQL 14+ target using Endrias Bridge, including prerequisite binlog configuration, type conversion guidance, and post-migration sequence resets.
Overview & Scope
This SOP covers a full copy (schema + data) from a MySQL 8.0 source into a PostgreSQL 14+ target using Endrias Bridge's cross-engine row-copy mode. Cross-engine migration requires more type-conversion awareness than a same-engine copy — key differences include ENUM handling, AUTO_INCREMENT sequences, zero-date behavior, unsigned integers, and case sensitivity of identifiers.
Prerequisites — Source (MySQL 8.0)
| Requirement | Notes / Verification |
|---|---|
MigrationUser: SELECT + LOCK TABLES +
REPLICATION CLIENT privileges on EndriasBridge |
Minimum for full-copy mode; REPLICATION CLIENT required for CDC |
binlog_format = ROW |
Check: SHOW VARIABLES LIKE 'binlog_format'; |
Binlog retention: expire_logs_days=30 (5.7) or
binlog_expire_logs_seconds=2592000 (8.0) |
For RDS MySQL: CALL mysql.rds_set_configuration('binlog retention hours', 720); |
default_authentication_plugin=mysql_native_password recommended |
Avoids caching_sha2_password handshake issues with some connector versions |
TCP 3306 reachable from migration host to mysql-source-server |
Confirm with: Test-NetConnection mysql-source-server -Port 3306 |
Prerequisites — Target (PostgreSQL 14+)
Endrias Bridge connects
directly via postgresql+psycopg2:// — it does not create the
database, only tables inside it. Run the following once on the PostgreSQL server
(as a superuser, e.g. psql -U postgres) before Step 1:
-- 1. Create the target database
CREATE DATABASE endriasbridge_target;
-- 2. Create the migration user
CREATE USER MigrationUser WITH PASSWORD 'your-password';
-- 3. Grant ownership so CREATE/DROP/INSERT work freely
-- (avoids "permission denied for schema public" on PostgreSQL 15+)
ALTER DATABASE endriasbridge_target OWNER TO MigrationUser;
\connect endriasbridge_target
ALTER SCHEMA public OWNER TO MigrationUser;
psycopg2 is already bundled with Endrias Bridge —
no separate Python driver installation is required.Configure Connections
Open Endrias Bridge, navigate to Migration Projects → New Project, and fill in both connection blocks. Select MySQL as source type and PostgreSQL as target type.
SOURCE DATABASE
TARGET DATABASE
caching_sha2_password mismatch — set
default_authentication_plugin=mysql_native_password on the source
to resolve it.Run Assessment
Switch to the Assessment tab and click Run Assessment. For cross-engine migrations the assessment panel shows a richer compatibility report — all type conversions that will be applied are listed per column.
SOURCE OBJECTS
users 28,400 rows
orders 142,000 rows
products 4,200 rows
categories 85 rows
… (34 more)
COMPATIBILITY ISSUES
• orders.id: INT AUTO_INCREMENT → SERIAL
• products.metadata: JSON → JSONB
• categories.type: ENUM → VARCHAR(n)
• inventory.qty: INT UNSIGNED → BIGINT
VARCHAR(n) where n is the length of the longest label.
Values are fully preserved as strings — no data loss occurs, but
PostgreSQL will not enforce the constraint. See
Known Issues.Select Tables
Switch to the Migration tab. Click Load Tables from Source, confirm all tables are checked, then configure migration options.
Start Migration & Monitor
Click Start Migration. The Migration Log updates in real time. Type conversion lines are printed first, then schema creation, then row-by-row copy progress per table.
0000-00-00 / 0000-00-00 00:00:00)
cannot be stored in PostgreSQL DATE / TIMESTAMP
columns. Endrias Bridge
converts them to NULL and logs a WARNING per affected row.
Review the log for zero-date WARNINGs after migration completes.Verify & Export Report
- Click Verify Data Integrity — compares source vs. target row counts for every migrated table and logs any mismatches.
- Review zero-date warnings: check the Migration Log
for
WARNING: zero-date converted to NULLlines, then query the target to confirm NULLs are expected. - Click Export Report (enabled once migration finishes) to save an HTML/text summary of tables, row counts, type conversions, and all warnings.
Post-Migration Tasks
After a successful full copy the following tasks are required before switching application traffic to the PostgreSQL target.
1. Verify ENUM columns — values are stored as strings in
VARCHAR(n) columns. No PostgreSQL ENUM type is created.
-- Spot-check a former ENUM column
SELECT DISTINCT type FROM categories ORDER BY type;
2. Reset sequences — PostgreSQL SERIAL sequences start at 1 after schema creation; they are not seeded from the migrated MAX(id) value. Reset each auto-increment column:
-- Run for each table with an AUTO_INCREMENT / SERIAL primary key
SELECT setval(
pg_get_serial_sequence('users', 'id'),
(SELECT MAX(id) FROM users)
);
SELECT setval(
pg_get_serial_sequence('orders', 'id'),
(SELECT MAX(id) FROM orders)
);
-- Repeat for every SERIAL column in the schema
3. Zero-date check
-- Confirm expected NULLs where MySQL stored 0000-00-00
SELECT COUNT(*) AS zero_date_rows
FROM orders
WHERE created_at IS NULL;
4. Case sensitivity — PostgreSQL identifiers are case-sensitive. All MySQL identifiers were lowercased during migration. Update any application queries that reference mixed-case table or column names.
5. Full-text indexes — MySQL FULLTEXT indexes are not migrated.
Recreate them as GIN indexes with tsvector on PostgreSQL:
-- Example: recreate full-text search index on products.description
ALTER TABLE products ADD COLUMN tsv tsvector
GENERATED ALWAYS AS (to_tsvector('english', description)) STORED;
CREATE INDEX idx_products_tsv ON products USING GIN (tsv);
ANALYZE; on the target database after migration completes
to update planner statistics before switching application traffic.
PostgreSQL autovacuum will not have gathered statistics on newly populated
tables yet.Known Issues
| Issue | Details & Resolution |
|---|---|
| ENUM / SET types | MySQL ENUM becomes VARCHAR(n); SET
becomes TEXT (comma-separated values). No PostgreSQL ENUM type
is created — this avoids ALTER TYPE table lock issues.
Values are fully preserved as strings. |
| Zero dates | MySQL 0000-00-00 / 0000-00-00 00:00:00 are
invalid on PostgreSQL — converted to NULL. A WARNING is logged
per affected row in the Migration Log. |
| UNSIGNED integers | INT UNSIGNED → BIGINT to avoid overflow
above 2,147,483,647. BIGINT UNSIGNED →
NUMERIC(20). |
| Case sensitivity | All MySQL identifiers are lowercased during migration. Application queries using mixed-case names must be updated. |
| AUTO_INCREMENT seed | PostgreSQL SERIAL sequences are not seeded from migrated data — they start
at 1. Reset each sequence to MAX(id)+1 before going live
(see Step 6). |
| JSON → JSONB | MySQL JSON is migrated as PostgreSQL JSONB
(binary-indexed). The ->> operator works identically; the
@> containment operator behaves differently — review any
queries using containment after migration. |
| Full-text indexes | MySQL FULLTEXT indexes are not migrated. Recreate as
GIN indexes with tsvector (see Step 6). |
Type Conversions (MySQL → PostgreSQL)
| Source (MySQL) | Target (PostgreSQL) | Note |
|---|---|---|
| TINYINT(1) | BOOLEAN | MySQL boolean convention |
| TINYINT | SMALLINT | No 1-byte integer on PostgreSQL |
| SMALLINT | SMALLINT | |
| MEDIUMINT | INTEGER | |
| INT | INTEGER | |
| INT UNSIGNED | BIGINT | Avoids overflow above 2,147,483,647 |
| BIGINT | BIGINT | |
| BIGINT UNSIGNED | NUMERIC(20) | Exceeds BIGINT max range |
| FLOAT | REAL | |
| DOUBLE | DOUBLE PRECISION | |
| DECIMAL(p,s) | NUMERIC(p,s) | |
| CHAR(n) / VARCHAR(n) | CHAR(n) / VARCHAR(n) | |
| TINYTEXT / TEXT / MEDIUMTEXT / LONGTEXT | TEXT | |
| BINARY(n) / VARBINARY(n) | BYTEA | |
| TINYBLOB / BLOB / MEDIUMBLOB / LONGBLOB | BYTEA | |
| DATE | DATE | |
| TIME | TIME | |
| DATETIME | TIMESTAMP | No timezone information |
| TIMESTAMP | TIMESTAMPTZ | MySQL stores as UTC internally |
| YEAR | SMALLINT | |
| ENUM | VARCHAR(n) | n = max label length; values preserved |
| SET | TEXT | Stored as comma-separated values |
| JSON | JSONB | Binary-indexed on PostgreSQL |
| AUTO_INCREMENT INT | SERIAL | Sequence reset required post-migration |
| AUTO_INCREMENT BIGINT | BIGSERIAL | Sequence reset required post-migration |