結論先講
MySQL 不是爛,是它「太友善」了。 它允許太多不應該被允許的事情——隱式型別轉換、截斷資料不報錯、utf8 不是真的 UTF-8。這些設計在 2000 年代讓入門變簡單,但在 2026 年的生產環境裡,每一個都是定時炸彈。
但公平地說,MySQL 8.0 以後修了很多。問題是大量教學和既有系統還在用舊的設定,所以坑一直在。
被嫌的第一名:隱式型別轉換
這是 MySQL 最被詬病的行為。看這個查詢:
-- 假設 phone 欄位是 VARCHAR
SELECT * FROM users WHERE phone = 0;你覺得會回傳什麼?
MySQL 的答案:所有 phone 不是純數字的資料列。
為什麼?MySQL 在比較 VARCHAR 和 INT 時,會把 VARCHAR 轉成數字。'abc' 轉成數字是 0,所以 'abc' = 0 是 true。
-- 這些在 MySQL 裡都是 true 🤯
SELECT 'abc' = 0; -- 1 (true)
SELECT '123abc' = 123; -- 1 (true)
SELECT '' = 0; -- 1 (true)PostgreSQL 的做法:直接報錯。
ERROR: operator does not exist: character varying = integer
哪個比較好?錯是會痛的,但至少你馬上知道出問題了。MySQL 不報錯,你可能跑了三個月才發現查詢結果一直是錯的。
真實事故
我遇過的案例:某電商後台用會員編號(VARCHAR)查訂單,有人在前端輸入 0,結果撈出了全部 5 萬筆訂單。因為所有非數字的會員編號都被 MySQL 轉成 0 了。
被嫌的第二名:utf8 不是 UTF-8
MySQL 的 utf8 編碼只支援最多 3 bytes 的字元。但 UTF-8 標準定義的是最多 4 bytes。
這代表什麼?Emoji 存不進去。
-- 用 utf8 編碼
INSERT INTO posts (content) VALUES ('寫得好 👍');
-- ERROR 1366: Incorrect string value: '\xF0\x9F\x91\x8D'要用 utf8mb4 才是真正的 UTF-8:
-- 正確的做法
ALTER TABLE posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;為什麼會這樣? 因為 MySQL 在 2003 年實作 utf8 時,為了省空間只用 3 bytes。後來 Unicode 標準加了 4 bytes 字元(包括 emoji 和很多 CJK 擴展字元),MySQL 不能改 utf8 的定義(會破壞向後相容),所以搞了個 utf8mb4。
PostgreSQL 的做法: UTF-8 就是 UTF-8,從來沒有這個問題。
2026 年的狀態
MySQL 8.0 起,預設的字元集已經改成 utf8mb4。但如果你的資料庫是從舊版升級的,或者教學裡寫 charset=utf8,你還是會踩到。
被嫌的第三名:靜默截斷
在寬鬆模式下,MySQL 會靜默截斷超出長度的資料:
-- name 欄位定義為 VARCHAR(5)
INSERT INTO users (name) VALUES ('Alexander');
-- 寬鬆模式:成功!name 被截成 'Alexa',沒有警告
-- 嚴格模式:ERROR 1406: Data too long for column 'name'日期也是:
-- 寬鬆模式
INSERT INTO events (event_date) VALUES ('2024-02-30');
-- 成功!存成 '0000-00-00',沒有警告 💀
INSERT INTO events (event_date) VALUES ('abc');
-- 成功!也存成 '0000-00-00' 💀💀2026 年的狀態: MySQL 8.0 預設已經是 STRICT_TRANS_TABLES,上述的靜默截斷不會發生。但很多舊教學和舊設定檔還在用寬鬆模式。
怎麼確認你的 MySQL 是嚴格模式?
SELECT @@sql_mode;
-- 應該包含 STRICT_TRANS_TABLES
-- 理想值:
-- STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,
-- ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION被嫌的第四名:GROUP BY 不嚴格
-- 這個查詢在 MySQL(寬鬆模式)可以跑
SELECT department, name, MAX(salary)
FROM employees
GROUP BY department;
-- name 是哪個 name?不確定。MySQL 隨便挑一個SQL 標準要求:SELECT 裡的非聚合欄位都要出現在 GROUP BY 裡。MySQL 以前不管這個,隨便你寫。
MySQL 8.0 預設 加了 ONLY_FULL_GROUP_BY,上面的查詢會報錯。但舊專案升級時,很多人會把它關掉來「修」舊的查詢…
被嫌的第五名:沒有原生的 JSON 型別(直到 5.7)
MySQL 直到 5.7 版(2015 年)才加入 JSON 型別。而且功能比 PostgreSQL 的 JSONB 弱:
| 功能 | MySQL JSON | PostgreSQL JSONB |
|---|---|---|
| 原生型別 | 5.7+ | 9.4+(2014) |
| 索引 | Generated column + index | GIN 索引直接用 |
| 部分更新 | 8.0+(JSON_SET) | 原生支援 |
| 運算子 | 函式呼叫(JSON_EXTRACT) | ->, ->>, @> 運算子 |
| 全文搜尋 JSON | 不支援 | 支援 |
| 效能 | 每次讀取都要 parse | 存成二進位,讀取快 |
如果你的應用大量用 JSON 欄位,PostgreSQL 的體驗好非常多。
那 MySQL 有什麼優點?
說了這麼多缺點,MySQL 能活到現在一定有原因:
| 優點 | 說明 |
|---|---|
| 簡單 | 安裝、設定、上手都比 PostgreSQL 簡單 |
| 生態系 | WordPress、Drupal、大量 PHP 框架預設用 MySQL |
| 讀取效能 | 單純的讀取查詢,MySQL 通常比 PostgreSQL 快 |
| 複製 | 主從複製設定相對簡單 |
| 雲端支援 | AWS RDS、GCP Cloud SQL、Azure 都有 managed MySQL |
| 社群 | 全球最多人用的開源資料庫,問題容易找到解答 |
而且 MySQL 8.0+ 已經修了很多
- ✅ 預設 utf8mb4
- ✅ 預設嚴格模式(STRICT_TRANS_TABLES)
- ✅ 預設 ONLY_FULL_GROUP_BY
- ✅ Window Functions
- ✅ CTE(Common Table Expressions)
- ✅ JSON 部分更新
- ✅ 原生 JSON 索引(functional index)
什麼時候該用 MySQL,什麼時候該用 PostgreSQL?
| 場景 | 推薦 | 原因 |
|---|---|---|
| WordPress / PHP 生態 | MySQL | 原生支援,沒得選 |
| 新專案,資料正確性重要 | PostgreSQL | 嚴格型別、更好的 JSON |
| 大量讀取、簡單查詢 | MySQL | 讀取效能好 |
| 複雜查詢、分析型 | PostgreSQL | Window Functions、CTE 支援更早更完整 |
| GIS / 地理資料 | PostgreSQL | PostGIS 是業界標準 |
| 全文搜尋 | 看量 | 小量兩者都行,大量考慮 Elasticsearch |
| 已有 MySQL,運作正常 | 不要搬 | 搬家的成本和風險通常不值得 |
最後一點很重要:如果你的 MySQL 跑得好好的,不要因為看了一篇文章就搬家。 搬資料庫的風險比搬網站高一百倍。
常見問題
MySQL 真的那麼差嗎?
不是差,是「預設值不夠安全」。MySQL 8.0+ 在嚴格模式下,大部分問題都已解決。真正的問題是大量舊教學和舊系統還在用寬鬆設定。
如果要新專案,選哪個?
2026 年,預設選 PostgreSQL。除非你有明確的理由(如 WordPress 生態、團隊只會 MySQL),否則 PostgreSQL 在型別安全、JSON 支援、擴展性上都更好。
從 MySQL 搬到 PostgreSQL 難嗎?
看複雜度。簡單的 CRUD 應用可能幾天搞定,用了大量 MySQL 特有語法的就要好幾週。工具推薦:pgloader、AWS DMS。
MySQL 的隱式轉換可以關掉嗎?
不行。這是 MySQL 的核心行為,無法透過設定關閉。你能做的是在應用層確保型別一致(ORM 通常會幫你處理)。