結論先講

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' = 0true

-- 這些在 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 JSONPostgreSQL JSONB
原生型別5.7+9.4+(2014)
索引Generated column + indexGIN 索引直接用
部分更新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讀取效能好
複雜查詢、分析型PostgreSQLWindow Functions、CTE 支援更早更完整
GIS / 地理資料PostgreSQLPostGIS 是業界標準
全文搜尋看量小量兩者都行,大量考慮 Elasticsearch
已有 MySQL,運作正常不要搬搬家的成本和風險通常不值得

最後一點很重要:如果你的 MySQL 跑得好好的,不要因為看了一篇文章就搬家。 搬資料庫的風險比搬網站高一百倍。


常見問題

MySQL 真的那麼差嗎?

不是差,是「預設值不夠安全」。MySQL 8.0+ 在嚴格模式下,大部分問題都已解決。真正的問題是大量舊教學和舊系統還在用寬鬆設定。

如果要新專案,選哪個?

2026 年,預設選 PostgreSQL。除非你有明確的理由(如 WordPress 生態、團隊只會 MySQL),否則 PostgreSQL 在型別安全、JSON 支援、擴展性上都更好。

從 MySQL 搬到 PostgreSQL 難嗎?

看複雜度。簡單的 CRUD 應用可能幾天搞定,用了大量 MySQL 特有語法的就要好幾週。工具推薦:pgloader、AWS DMS。

MySQL 的隱式轉換可以關掉嗎?

不行。這是 MySQL 的核心行為,無法透過設定關閉。你能做的是在應用層確保型別一致(ORM 通常會幫你處理)。

本系列文章

  1. 資料庫全景圖:一張表看懂所有類型
  2. MySQL:為什麼越來越多人覺得它不夠好?(本篇)
  3. PostgreSQL:為什麼它變成了預設選擇
  4. 快取與 Session 管理:Redis、Memcached、還是直接用 DB?
  5. 全文搜尋:Elasticsearch、Meilisearch、還是 PostgreSQL 就夠了?
  6. NoSQL 什麼時候該用
  7. 資料庫設計:正規化與索引策略
  8. SQL 效能調校
  9. 資料庫遷移實戰
  10. 擴展策略:讀寫分離與分片
  11. Docker 環境的資料庫管理
  12. 資料庫監控與告警