00:00

MySQL Vs PostgreSQL

Last updated: 4 min read

MySQL and PostgreSQL are the two most popular open-source relational databases. Both are mature, fast and reliable, and both run the core SQL you learn on this site. Their differences lie in philosophy, data types, some SQL behaviour, extensibility and operations. This page compares them with examples.

1. Overview

MySQLPostgreSQL
First release19951996 (from the Berkeley POSTGRES project, 1986)
Owner / governanceOracle CorporationPostgreSQL Global Development Group (community)
LicenceGPL v2 (+ commercial)PostgreSQL Licence (permissive, BSD-like)
TypeRelational (RDBMS)Object-relational (ORDBMS)
Default port33065432
ReputationSimple, fast, easy to run, web-friendlyStandards-compliant, feature-rich, extensible

2. Architecture

Connection handling in MySQL and PostgreSQLMySQL runs one server process and gives each client connection a thread. PostgreSQL starts a separate operating system process for each client connection, so connection poolers such as PgBouncer are common.MySQL: thread per connectionmysqld (one process)thread 1thread 2thread 3clientsPostgreSQL: process per connectionpostmasterbackendbackendbackendclients
Figure 1: MySQL's threads are cheap, so it handles many connections easily; PostgreSQL usually sits behind a connection pooler.
  • Storage: MySQL has pluggable engines (InnoDB by default) with a clustered primary key. PostgreSQL has one engine that stores rows in heap files, with all indexes pointing to them.
  • MVCC: both keep old row versions so readers don't block writers. InnoDB keeps old versions in undo logs; PostgreSQL keeps them in the table and cleans up with VACUUM (autovacuum).
  • Schemas: in MySQL, database = schema. In PostgreSQL, a database contains several schemas (public by default).

3. SQL Behaviour Differences

BehaviourMySQLPostgreSQL
String comparisonCase-insensitive by default (utf8mb4_0900_ai_ci)Case-sensitive; use ILIKE or citext
Insert-or-updateINSERT … ON DUPLICATE KEY UPDATEINSERT … ON CONFLICT … DO UPDATE, MERGE
Return changed rowsNot supported; run a SELECT afterINSERT/UPDATE/DELETE … RETURNING
Auto-incrementAUTO_INCREMENTGENERATED AS IDENTITY / SERIAL
String concat with ||Logical OR (unless PIPES_AS_CONCAT mode)Concatenation
Quoting identifiers`name`"name"
DDL inside a transactionCauses an implicit commitTransactional: CREATE TABLE can be rolled back
Default isolationREPEATABLE READREAD COMMITTED

Case-insensitive comparison in MySQL, shown live:

case.sql
CREATE DATABASE pgcompare;
USE pgcompare;
CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(60) UNIQUE, visits INT DEFAULT 0);
INSERT INTO users VALUES (1, 'Asha@Example.com', 3);

SELECT id, email FROM users WHERE email = 'asha@example.com';

Result: 1 row

idemail
1Asha@Example.com

In PostgreSQL the same query returns no rows unless you compare with lower(email) or use citext. MySQL's case-insensitive collation also makes the UNIQUE index treat 'ASHA@EXAMPLE.COM' as a duplicate:

query.sql
INSERT INTO users VALUES (2, 'ASHA@EXAMPLE.COM', 0);
Output
ERROR 1062 (23000): Duplicate entry 'ASHA@EXAMPLE.COM' for key 'users.email'

Upsert syntax side by side. MySQL (run here):

upsert-mysql.sql
INSERT INTO users (id, email, visits) VALUES (1, 'Asha@Example.com', 1)
ON DUPLICATE KEY UPDATE visits = visits + 1;

SELECT id, visits FROM users;

Result: 1 row

idvisits
14
upsert-postgresql.sql
INSERT INTO users (id, email, visits) VALUES (1, 'Asha@Example.com', 1)
ON CONFLICT (id) DO UPDATE SET visits = users.visits + 1
RETURNING id, visits;

4. Data Types

AreaMySQLPostgreSQL
JSONJSON (binary, indexed via generated columns or multi-valued indexes)json and jsonb (binary, GIN-indexable)
ArraysNo (use JSON arrays)Native arrays: int[], text[]
RangesNodaterange, int4range with overlap operators
UUIDBINARY(16) + UUID_TO_BIN()Native uuid
Network typesNoinet, cidr, macaddr
Custom typesNoCREATE TYPE, domains, enums
Unsigned integersYes (INT UNSIGNED)No
SpatialBuilt-in GIS typesPostGIS extension (very powerful)

5. Indexes and Extensions

  • MySQL: B-tree (most), hash (MEMORY), full-text, spatial (R-tree), functional and multi-valued (JSON array) indexes; invisible indexes for safe testing.
  • PostgreSQL: B-tree, hash, GIN, GiST, SP-GiST and BRIN, plus partial indexes (WHERE active) and expression indexes.
  • Extensions: PostgreSQL's extension system is a major strength: PostGIS, TimescaleDB, pgvector, pg_stat_statements. MySQL extends through components and plugins, but the ecosystem is smaller.

6. Replication, Scaling and Operations

TopicMySQLPostgreSQL
Built-in replicationBinary-log replication (row-based), GTIDs; Group ReplicationStreaming (physical) and logical replication
HA packagesInnoDB Cluster (Router + Shell)Patroni, repmgr, cloud services
Connection scalingThousands of threads are fineUse a pooler (PgBouncer) for many connections
MaintenanceLittle routine maintenanceAutovacuum must keep up on busy tables
Online schema changeInstant ADD COLUMN; gh-ost / pt-online-schema-change toolsMany changes are fast; transactional DDL

7. Which Should You Choose?

MySQL is a great fit when…PostgreSQL is a great fit when…
You want simplicity and easy operationsYou need advanced SQL and data types (arrays, ranges, jsonb)
Workloads are high-volume, simple reads and writesYou run complex analytical queries
You use common stacks: WordPress, LAMP, many SaaS toolsYou need GIS (PostGIS) or vector search (pgvector)
You want case-insensitive text matching by defaultYou need strict standard compliance and transactional DDL
For learning SQL either is fine: the SELECT, JOIN, GROUP BY and transaction skills on this site transfer directly.

8. Summary

  • Both are excellent open-source relational databases; MySQL favours simplicity and speed, PostgreSQL favours features and extensibility.
  • MySQL compares strings case-insensitively by default; PostgreSQL is case-sensitive.
  • Upserts: ON DUPLICATE KEY UPDATE (MySQL) vs ON CONFLICT (PostgreSQL); PostgreSQL also has RETURNING.
  • PostgreSQL has richer types and index kinds; MySQL handles many connections with lighter threads.
  • Pick by team skills, workload and the features you actually need.