MySQL ENUM 型別允許在建表時預定義一組允許值,看似能提供資料完整性約束。然而在 PHP 等弱型別語言環境下,極易引發「數值與字串混淆」的嚴重 Bug,資深開發者普遍建議改用 TINYINT + 應用層常數/字典表。
CREATE TABLE `enum2tinyint` (
`switchs` ENUM('none','success','fail','delete','skip','1') DEFAULT NULL,
`switch` TINYINT(1) NOT NULL,
KEY `switchs` (`switchs`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `enum2tinyint` (`switchs`, `switch`) VALUES ('1', '1');
-- switchs = '1'(第 6 個枚舉值),switch = 1
INSERT INTO `enum2tinyint` (`switchs`, `switch`) VALUES (1, '1'); -- switchs = 'none'(第 1 個枚舉值)!
INSERT INTO `enum2tinyint` (`switchs`, `switch`) VALUES (2, '1'); -- switchs = 'success'(第 2 個枚舉值)!
根因:MySQL 內部將 ENUM 映射為整數索引(1-based)。PHP 變數 $val = 1 直接拼入 SQL 時未加引號,MySQL 視為數值索引而非字串值。這類 Bug 極難排查,因語法正確、不報錯、資料卻錯誤。
ENUM('a','b','c') → 內部映射:1→'a', 2→'b', 3→'c'。| 特性 | ENUM | TINYINT |
|---|---|---|
| 儲存空間 | 1-2 bytes | 1 byte |
| 值域 | 最多 65,535 個枚舉 | -128~127 (signed) / 0~255 (unsigned) |
| 語意清晰度 | 資料庫層可見枚舉標籤 | 需應用層或字典表維護映射 |
| PHP 安全性 | 極低(易混淆索引與值) | 高(整數比較直觀) |
| 索引效能 | 較低(基數低) | 同等 |
| 修改枚舉值 | 需 ALTER TABLE(鎖表) | 應用層修改常數/字典表即可 |
TINYINT(1)、TINYINT(4) 儲存範圍完全相同,括號內數字僅為顯示寬度(配合 ZEROFILL),不影響儲存上限。建議統一使用 TINYINT UNSIGNED(0~255)。
TINYINT UNSIGNED。class OrderStatus {
const PENDING = 0;
const PAID = 1;
const SHIPPED = 2;
const DONE = 3;
const CANCEL = 4;
public static function labels() {
return [
self::PENDING => '待付款',
self::PAID => '已付款',
self::SHIPPED => '已發貨',
self::DONE => '已完成',
self::CANCEL => '已取消',
];
}
}
dict_order_status(code, label, sort),透過外鍵關聯,支援動態新增狀態無需改表結構。ENUM 看似方便,實則在弱型別語言環境下埋下巨大隱患。採用 TINYINT + 應用層語意映射,不僅消除 Bug,還獲得更靈活的擴充性與更清晰的程式碼語意。這是現代 MySQL 開發的共識最佳實踐。