欄位型別不僅決定儲存格式,更直接決定查詢執行計畫、索引效率、I/O 開銷、CPU 計算量、記憶體使用。本文針對高效能查詢場景,從定長 vs 變長、長度適配、NULL 消除、ENUM 數值化、BLOB 隔離、碎片整理、合成索引、大欄位延遲載入八大維度,提供可落地的優化規則與實戰策略。
| 規則 | 適用場景 | 效能收益 | 代價/風險 |
|---|---|---|---|
| 優先定長型別 | 高頻 WHERE/JOIN/ORDER BY 欄位、頻繁 UPDATE 表 | 無碎片、固定位移快速定位、排序緩衝區效率高 | 空間浪費(右補空格)、超長無定長選項 |
| 長度剛好夠用 | 所有定長/變長欄位定義 | 減少磁盤 I/O、縮小索引、加快排序/暫存表 | 未來擴充需 ALTER TABLE |
| 定義 NOT NULL | 所有可確定非空的欄位 | 省 1 bit/列、簡化優化器判斷、允許早期版本索引 | 應用層需保證非空 |
| 固定值集用 ENUM | 狀態、類型、分類等低基數固定集合 | 內部數值比較、體積極小、排序依定義順序 | 修改成員需 DDL、大量成員鎖表久 |
| PROCEDURE ANALYSE() | 現有表結構優化、遷移評估 | 自動推薦最佳型別、發現過長/過大定義 | 僅供參考,需人工審核 |
| BLOB 大物件策略 | 含大型文字/二進位欄位的表 | 主表定長化、減少碎片、避免 SELECT * 拖回大資料 | 應用層需管理副表關聯 |
| 定期 OPTIMIZE TABLE | 高頻 DELETE/UPDATE 的變長/BLOB 表 | 回收碎片空間、重建索引統計資訊 | 鎖表操作、需維護窗口 |
| 合成雜湊索引 | BLOB/長字串精確查詢 | 索引體積極小、比較極快 | 僅支援等值查詢、需維護雜湊同步 |
| 延遲載入大欄位 | BLOB/TEXT 不常讀取 | 減少網路傳輸、查詢緩衝區壓力 | 需兩次查詢或應用層快取 |
Header + N × Row_Length,無需掃描長度前綴UPDATE 不改變記錄長度,原地修改,不產生空洞-- 善用 PROCEDURE ANALYSE 找出過長定義
SELECT * FROM `member` PROCEDURE ANALYSE(16, 256);
-- 建議輸出:VARCHAR(40) 而非 VARCHAR(255)
-- 主表:定長核心欄位
CREATE TABLE article (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
author_id INT UNSIGNED NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status ENUM('draft','published','archived') NOT NULL DEFAULT 'draft',
-- 無 BLOB/TEXT,全定長/短變長
KEY idx_author_status (author_id, status)
) ENGINE=InnoDB;
-- 副表:大內容獨立
CREATE TABLE article_content (
article_id INT UNSIGNED PRIMARY KEY,
content LONGTEXT NOT NULL,
FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE
) ENGINE=InnoDB;
收益:主表掃描/排序/連線極快、無碎片、緩衝區命中率高;內容表僅在需閱讀全文時 JOIN。
ALTER TABLE document ADD content_hash CHAR(32) NOT NULL;
UPDATE document SET content_hash = MD5(content);
CREATE UNIQUE INDEX ux_content_hash ON document(content_hash);
-- 查詢
SELECT * FROM document WHERE content_hash = MD5('search_content');
適用:重複內容去重、精確內容查找、版本控制系統。
限制:不支援範圍查詢、LIKE 模糊比對;MD5 碰撞極低但理論存在。
將半結構化資料(問卷答案、設定 JSON、標籤陣列)序列化為 JSON 存入單一 JSON/LONGTEXT 欄位:
JSON 型別支援虛擬欄位索引、JSON_EXTRACT 等函數,部分緩解查詢痛點-- 字串版
CREATE TABLE orders_str (status VARCHAR(20));
-- ENUM 版
CREATE TABLE orders_enum (status ENUM('pending','paid','shipped','cancelled','refunded'));
-- 測試:100萬筆資料,WHERE status='paid'
-- ENUM 版通常快 2-5 倍,索引體積縮減 80%+
排序優勢:ORDER BY status 依定義順序(pending→paid→shipped...),非字母順,符合業務流程直覺。
NULL 佔 1 bit/欄位/列,NOT NULL 省下空間WHERE col = 'val' 不需考慮 NULL 分支,簡化執行計畫COUNT(col) 忽略 NULL,COUNT(*) 計入,語意易混淆NOT NULL;新版雖允許但 NULL 佔用索引條目實務建議:除非業務真正需要「未知/不適用」語意,否則一律 NOT NULL DEFAULT 'default_val'。
| 表類型 | 觸發條件 | 頻率建議 | 線上操作 |
|---|---|---|---|
| MyISAM | Data_free > 100MB 或 > 10% 表大小 | 每日/每週維護窗口 | 鎖表,需離峰 |
| InnoDB | 大量 DELETE/UPDATE 變長欄位 | 每月或配合 pt-online-schema-change | 5.6+ 支援 Online DDL,較輕量 |
監控指標:SHOW TABLE STATUS LIKE 'tbl_name' 觀察 Data_free、Avg_row_length 變化趨勢。
-- 錯誤:SELECT * 拖回所有 BLOB
SELECT * FROM article WHERE id = 123;
-- 正確:分兩階段
-- 1. 列表/搜尋:僅核心欄位
SELECT id, title, author_id, status FROM article WHERE status='published' LIMIT 20;
-- 2. 詳細頁:按需載入內容
SELECT content FROM article_content WHERE article_id = 123;
ORM 框架支援:Hibernate @Basic(fetch=LAZY)、JPA @Lob + @Basic(fetch=LAZY)、Entity Framework Table Splitting。
NOT NULL?ENUM?OPTIMIZE TABLE 定期維護排程?SELECT *,明確列出所需欄位?PROCEDURE ANALYSE() 驗證無明顯過大定義?欄位型別是查詢效能的「隱形決定因子」。透過定長化核心路徑、垂直拆分大物件、ENUM 數值化低基數欄位、消除不必要的 NULL、合成雜湊索引支援精確查詢、定期碎片整理,可在不改變 SQL 邏輯前提下,獲得數倍到數十倍的查詢效能提升。建議將上述檢查清單納入資料庫設計評審(Design Review)與上線檢核(Go-live Checklist)標準流程,從源頭鎖定高效能。