The short answerMySQL and PostgreSQL are both free, mature relational databases that run most apps well, but PostgreSQL can undo table changes inside a transaction, has richer JSON and starts at READ COMMITTED, while MySQL starts at REPEATABLE READ and ignores case when comparing text by default, so pick the one your team and host already know unless one of those differences matters to you.
This page is the free part.
The course goes deeper on MySQL and PostgreSQL
₹499 in India/$49 everywhere elseonce, for the whole course
The System Design course covers MySQL and PostgreSQL across a run of lessons, not one page. These 4 alone are about 118 minutes of step-by-step reading, every one with a quiz.
The planner stopped using an index somewhere between 19.7 and 39.8 percent of the table. A query on the second column of a composite index took 37.269 ms against 0.054, and five indexes took the same inserts from 160 ms to 751.
Ive started this course by accident - LLM advised me this site. When I passed few free lessons, I had no doubt - this course should be bought. And I did it with special pleasent price and it was best decision of the month. Content is great, interesting to read and with a lot of practial cases (what makes it different from other souces and books) I hope to teach all the lessons, that will be my supergoal. Glad that I found this resource and speacial Thank You to Roni Das - Great Job!
Oleksander, Backend Engineer · System Design Masterclass · read 15 lessons · all reviews
Huge install base: WordPress, PHP hosts, many older apps.
InnoDB starts every transaction at REPEATABLE READ.
Changing a table (CREATE, ALTER) quietly commits first.
Default text comparison ignores case: 'asha' finds 'Asha'.
vs
PostgreSQL, often called Postgres
PostgreSQL
the strict, extensible one
Starts every transaction at READ COMMITTED.
Table changes can be rolled back like any other change.
jsonb type with a containment operator (@>) and GIN indexes.
Extensions such as PostGIS add whole new features.
The same two sessions on each databaseMySQL's default level keeps a transaction on the snapshot it started with. PostgreSQL's default shows each new read the latest committed data. Both are correct for their level; they just differ.
MySQL vs PostgreSQL, side by side
Read across a row to compare one thing. Every word that may be new is explained just below the table.
Keeps showing the first snapshot. We read 100, then 100 again.
Sees other sessions' commits at once. We read 100, then 150.
Upsert syntax
INSERT ... AS new ON DUPLICATE KEY UPDATE col = new.col
INSERT ... ON CONFLICT (id) DO UPDATE SET col = EXCLUDED.col
CREATE TABLE inside a transaction
Commits the open transaction first. ROLLBACK cannot undo it.
Part of the transaction. ROLLBACK removes the table.
JSON
JSON type, JSON_CONTAINS(), ->> paths like '$.city'.
json and jsonb types, @> containment, GIN indexes, ->> keys.
Text comparison by default
utf8mb4_0900_ai_ci: case and accents ignored, so 'asha' = 'Asha'.
Case counts, so 'asha' does not match 'Asha'. Use ILIKE or citext.
Extensions
Storage engines and plugins. No extension system like Postgres.
CREATE EXTENSION: PostGIS for maps, pgvector for vectors, and more.
Licence
GPL version 2 for the Community edition (the LICENSE file in Oracle's mysql-server repository).
The PostgreSQL License, a liberal licence similar to BSD or MIT.
Version we tested
MySQL 9.7.1 (Homebrew)
PostgreSQL 18.6 (Homebrew). 18 first shipped 25 September 2025.
Words on this page, in plain English
Relational database
A database that keeps data in tables of rows and columns and answers questions written in SQL.
SQL
Structured Query Language: the language you use to read and change data in a relational database.
Transaction
A group of changes that succeed together or fail together. BEGIN starts one, COMMIT saves it, ROLLBACK throws it away.
Isolation level
How much one transaction can see of other transactions' changes while it is still running.
DDL
Data Definition Language: statements that change the shape of the database, such as CREATE TABLE or ALTER TABLE.
Upsert
Insert a row, or update it if a row with the same key already exists. Each database spells it differently.
JSON column
A column that stores a whole JSON document, such as {"plan": "pro"}, inside one row.
GIN index
A PostgreSQL index type that can find rows whose JSON contains a given key or value without reading every row.
Collation
The rules a database uses to compare and sort text, including whether upper and lower case count as equal.
When to use MySQL, when to use PostgreSQL
Real situations, and the pick we would make in each one.
Pick Both
You are adding a feature to an app that already uses one of them.
Stay where you are. A migration costs weeks and the two are close enough for most work.
Pick MySQL
A WordPress site, or cheap PHP hosting.
WordPress asks for MySQL 8.0 or newer, or its fork MariaDB 10.11 or newer, and these hosts ship it ready to use.
Pick PostgreSQL
You run schema migrations often and want a failed one to leave nothing behind.
In PostgreSQL, BEGIN; ALTER TABLE ...; ROLLBACK really undoes the change. In MySQL each DDL statement commits on its own.
Pick PostgreSQL
Lots of semi-structured data you need to search inside, such as user settings or events.
jsonb with a GIN index answers 'which rows contain this' without scanning the table. MySQL can do JSON, but with fewer index options.
Pick PostgreSQL
Maps and locations: nearest shop, points inside a city.
The PostGIS extension adds real geographic types and indexes, and it is the usual choice for this.
Pick MySQL
Usernames or emails that must match no matter the case.
MySQL's default collation already ignores case. In PostgreSQL you add citext, lower() with an index, or a non-deterministic collation.
Three questions pick the databaseFor most teams the first question decides it. The other two are for fresh projects.
we ran this, here is what happened
Hands-on: We ran the same SQL on both, side by side
Most comparisons list features. We wanted to see the differences with our own eyes, so we started a fresh MySQL and a fresh PostgreSQL on one laptop and gave them the same table and the same three rows.
Then we ran six checks: the default isolation level, what a transaction sees after another session commits, each one's upsert (and the other one's, to see the error), a table created and then rolled back, a JSON search, and a lower-case name search.
Every value below is what the server actually sent back. We ran the whole script twice and got the same answers both times.
Where it ran: Apple M4, macOS 15.6, MySQL 9.7.1 and PostgreSQL 18.6 from Homebrew, both with default settings, fresh data folders, Python 3.13 with PyMySQL 1.1.1 and psycopg 3.2.10. Two runs on 4 October 2026.
# session A reads twice inside ONE transaction; session B commits a change in between
ca.execute("START TRANSACTION") # BEGIN on PostgreSQL
ca.execute("SELECT balance FROM users WHERE id = 1")
first = ca.fetchone()[0]
cb.execute("UPDATE users SET balance = balance + 50 WHERE id = 1") # autocommit: committed now
ca.execute("SELECT balance FROM users WHERE id = 1")
second = ca.fetchone()[0]
# a table created inside a transaction, then rolled back
c.execute("START TRANSACTION")
c.execute("INSERT INTO users VALUES (9, 'Temp', 1, '{}')")
c.execute("CREATE TABLE audit_log (id INT)")
c.execute("ROLLBACK")
The lines that matter from scripts/labs/compare/mysql_vs_postgresql.py. The same steps run on both servers; only BEGIN is spelled differently.
real output
"non_repeatable_read": {
"mysql": { "first_read": 100, "second_read_same_txn": 100, "after_commit": 150 },
"postgres": { "first_read": 100, "second_read_same_txn": 150, "after_commit": 150 }
},
"upsert": {
"mysql": { "postgres_syntax": { "ok": false, "error": "ProgrammingError: (1064, \"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CONFLICT (id) DO UPDATE SET balance = EXCLUDED.balance' at line 1\")" } },
"postgres": { "mysql_syntax": { "ok": false, "error": "SyntaxError: syntax error at or near \"AS\"" } }
},
"transactional_ddl": {
"mysql": { "table_exists_after_rollback": [[1]], "row_9_after_rollback": [[1]] },
"postgres": { "table_exists_after_rollback": [[0]], "row_9_after_rollback": [[0]] }
},
"collation": {
"mysql": { "match": [[1, "Asha"]], "collation": [["utf8mb4_0900_ai_ci"]] },
"postgres": { "match": [], "ilike": [[1, "Asha"]] }
}
Real output of run 1 (out-mysql-postgres-run1.json), trimmed to the fields discussed and with each SQL string removed. Run 2 is identical.
The results
What we measured
MySQL
PostgreSQL
Default isolation levelSELECT @@transaction_isolation / SHOW default_transaction_isolation
REPEATABLE-READ
read committed
Second read, same transaction, after another commitboth are correct for their level
100 (old value)
150 (new value)
Rows left after ROLLBACK of INSERT + CREATE TABLEMySQL committed the insert when CREATE TABLE ran
table kept, row kept
both gone
WHERE name = 'asha' (stored as 'Asha')ILIKE found it on PostgreSQL
1 row
0 rows
JSON rows containing {"plan": "pro"}same answer, different syntax
2 (JSON_CONTAINS)
2 (@>, used the GIN index)
What each server said backEvery cell is read from our run file when the figure is drawn, so the picture cannot drift from the run.
What this shows
The surprise was the rollback. In MySQL, CREATE TABLE committed the open transaction first, so ROLLBACK kept not just the table but the row we had inserted before it. PostgreSQL threw both away. Neither server was wrong anywhere in this lab; each did exactly what its manual says. The danger is assuming one behaves like the other.
What this test does not show: Three rows on one laptop. This lab shows behaviour, not speed: it says nothing about which one is faster for your app. Defaults can be changed in both (isolation level, collation), and managed clouds sometimes change them for you, so check your own server. The script is scripts/labs/compare/mysql_vs_postgresql.sh in our repository.
Common mistakes
"PostgreSQL is faster" (or "MySQL is faster").
Speed depends on your queries, indexes, settings and hardware. Test your own workload. Most published races compare a tuned server with an untuned one.
Wrapping a migration in BEGIN ... ROLLBACK on MySQL and trusting it.
Every CREATE, ALTER or DROP TABLE commits on its own in MySQL. Plan migrations so each step is safe alone, or test them on a copy first.
Copying an upsert from a blog post written for the other database.
MySQL wants ON DUPLICATE KEY UPDATE; PostgreSQL wants ON CONFLICT. Our run got error 1064 on MySQL and a syntax error on PostgreSQL. Also, MySQL's VALUES(col) form still works but now raises a deprecation warning.
Assuming email lookups ignore case everywhere.
They do on MySQL's default collation and do not on PostgreSQL. Moving between them can create duplicate accounts or failed logins.
Thinking both default to the same isolation level.
MySQL starts at REPEATABLE READ and PostgreSQL at READ COMMITTED, so the same report run inside one transaction can show different numbers.
Questions people ask
Which is better, PostgreSQL or MySQL?
Neither wins in general. PostgreSQL has more features (transactional DDL, jsonb with GIN indexes, extensions like PostGIS). MySQL is simpler to find on cheap hosting and runs a huge share of existing PHP and WordPress sites. Pick the one your team knows unless you need a specific feature.
Is MySQL still relevant in 2026?
Yes. It is still released and supported (we tested MySQL 9.7.1), WordPress still asks for it (or MariaDB), and tools like Vitess exist to spread it across many servers. Its popularity with new projects has moved toward PostgreSQL, but that does not make existing MySQL systems obsolete.
Why is everyone using PostgreSQL?
Mostly features and licence. It can roll back schema changes, it has strong JSON support and an extension system (PostGIS, pgvector), and it uses the permissive PostgreSQL License. Many managed clouds now offer it as a first-class choice.
Which SQL database is fastest?
There is no honest single answer. It depends on the workload, the indexes and the settings. We did not run a speed test because three rows on one laptop would prove nothing. Benchmark your own queries on your own data.
Is PostgreSQL the same as SQL?
No. SQL is the language. PostgreSQL and MySQL are two databases that understand it, each with its own extra syntax. That is why our PostgreSQL upsert failed on MySQL and the MySQL one failed on PostgreSQL.
Can I move from MySQL to PostgreSQL later?
Yes, and many teams do, but plan for real work: upserts, JSON queries, case-sensitive text matching and isolation behaviour all change, as our lab shows. Test every query against the new database before switching.
Lessons that go deeper
From the System Design course, in the order we would read them.
MySQL vs PostgreSQL is one row in a much bigger table. Our System Design course has 770 lessons on networks, databases, caching, scaling, messaging, security and reliability, each drawn step by step, so you can explain the trade-off in an interview and pick right at work. 18 lessons are free to read, with no card needed.
the hands-on parts are real runs, like this one
course 1
System Design Masterclass
From absolute beginner to principal engineer, drawn step by step.