MySQL 基於行存儲,頁面級 I/O 意味著單行體積越小,每頁容納行數越多,I/O 次數越少,緩存命中率越高。本文系統梳理數據類型選擇原則、字符集策略、大字段拆分、適度冗余等 Schema 級優化手法。
NULL 需額外標誌位、復雜化索引統計、組合索引效率降低,優先 NOT NULL DEFAULT '' 或特殊值SELECT *:尤其含 TEXT/BLOB 時會污染 Buffer PoolINDEX(name(20)),平衡區分度與索引大小pt-duplicate-key-checker 檢測 idx(a,b) 覆蓋 idx(a)latin1,多語言用 utf8mb4| 類型 | 存儲字節 | 適用場景 |
|---|---|---|
| TINYINT | 1 | 布爾、枚舉、小計數(0-255/±127) |
| SMALLINT | 2 | 中等計數、年份、狀態碼 |
| MEDIUMINT | 3 | 較大計數、IP 段編號 |
| INT | 4 | 通用自增主鍵、外鍵、計數器 |
| BIGINT | 8 | 巨大計數、雪花算法 ID、時間戳毫秒 |
| FLOAT | 4 | 單精度浮點,科學計算 |
| DOUBLE | 8 | 雙精度浮點,**避免金融金額** |
| DECIMAL(M,D) | M+2 近似 | 精確金融、比率,**建議整數分存儲(分/厘)** |
| DATE | 3 | 日期(1000-01-01 ~ 9999-12-31) |
| DATETIME | 8 | 日期時間,**無時區**,記錄創建時間 |
| TIMESTAMP | 4 | 日期時間,**有時區**,記錄最後修改時間(自動更新) |
| TIME | 3 | 時間段 |
| YEAR | 1 | 年份 |
| CHAR(N) | N 字節 | 定長:MD5、UUID、郵編、手機號 |
| VARCHAR(N) | 實際長度+1/2 | 變長:名稱、描述、JSON 片段 |
| TEXT/BLOB | 外部存儲+1-4 字節指針 | **強烈不建議**,大文本/二進制請用對象存儲 |
| ENUM | 1-2 字節 | 固定集合:狀態、類型、性別 |
| SET | 1-8 字節 | 多選集合:標籤、權限掩碼 |
UNSIGNED(雙倍正數範圍)INT UNSIGNED AUTO_INCREMENT 或 BIGINT(分庫分表)INT UNSIGNED + INET_ATON()/INET_NTOA()INT(11) 僅影響客戶端顯示,**不限制存儲範圍**amount_cents BIGINT),避免浮點誤差、DECIMAL 開銷DOUBLE(內部以 DOUBLE 計算)DECIMAL(19,4) 等,僅在無法整數化時使用CHAR(MD5、UUID、國家代碼)VARCHAR(N),N 設為業務實際上限VARBINARY/BLOB,同樣建議外部存儲ENUM('a','b','c')(內部存整數,極其緊湊)SET('a','b','c') + FIND_IN_SET() 或位掩碼 INT| 類型 | 範圍 | 時區感知 | 自動更新 | 典型用途 |
|---|---|---|---|---|
| DATETIME | 1000-9999 | 無 | 無 | 創建時間、業務時間 |
| TIMESTAMP | 1970-2038 | 有 | 可 DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 最後修改時間、版本戳 |
| INT (Unix Timestamp) | 1970-2106 | 應用層處理 | 無 | 高性能範圍查詢、分區鍵 |
latin1(1 字節/字符,節省 50-75% 空間 vs utf8mb4)utf8mb4(4 字節/字符,完整 Unicode)CREATE TABLE t (c VARCHAR(100) CHARSET utf8mb4) CHARSET latin1;SET NAMES utf8mb4 / JDBC characterEncoding=utf8mb4場景:用戶表含 bio TEXT、avatar BLOB,95% 查詢不需要這兩列。
-- 拆分前 CREATE TABLE users ( id BIGINT PRIMARY KEY, name VARCHAR(50), email VARCHAR(100), bio TEXT, -- 大字段 avatar BLOB -- 大字段 ) ENGINE=InnoDB; -- 拆分後 CREATE TABLE users ( id BIGINT PRIMARY KEY, name VARCHAR(50), email VARCHAR(100) ) ENGINE=InnoDB; CREATE TABLE users_profile ( user_id BIGINT PRIMARY KEY, bio TEXT, avatar BLOB, FOREIGN KEY (user_id) REFERENCES users(id) ) ENGINE=InnoDB;
收益:
場景:訂單表頻繁 JOIN 用戶表只為取 user_name、user_phone,用戶表 5000 萬行,JOIN 代價極高。
-- 冗余設計 CREATE TABLE orders ( id BIGINT PRIMARY KEY, user_id BIGINT, user_name VARCHAR(50), -- 冗余 user_phone VARCHAR(20), -- 冗余 amount DECIMAL(10,2), created_at DATETIME, INDEX idx_user_id (user_id) ) ENGINE=InnoDB;
一致性保障:
AFTER UPDATE ON users FOR EACH ROW UPDATE orders SET user_name=NEW.name WHERE user_id=OLD.id;適用判斷:冗余列**讀頻極高、寫頻極低、長度短、業務容許最終一致**
| 優化點 | 檢查命令 | 行動 |
|---|---|---|
| 主鍵非自增 | SHOW CREATE TABLE t; | 改為 BIGINT AUTO_INCREMENT |
| 字符集過大 | SHOW TABLE STATUS LIKE 't'; | 純拉丁改 latin1,多語言 utf8mb4 |
| VARCHAR 過長 | SELECT MAX(LENGTH(c)) FROM t; | 縮小 N 到實際上限+10% |
| TEXT/BLOB 在主表 | SHOW COLUMNS FROM t WHERE Type LIKE '%text%' OR Type LIKE '%blob%'; | 垂直拆分 |
| 重複索引 | pt-duplicate-key-checker | 刪除被覆蓋的索引 |
| 低基數單列索引 | SELECT COUNT(DISTINCT c)/COUNT(*) FROM t; | 改為組合索引或刪除 |
| 缺少覆蓋索引 | EXPLAIN SELECT ... 觀察 Using index | 添加包含所有查詢列的組合索引 |
Schema 優化是「一次設計,長期受益」的高杠桿活動。核心心法:**小而精的類型、按需的字符集、垂直拆分大字段、克制的適度冗余**。每個字節的節省,在億級數據下都會轉化為可量化的 I/O 減少、緩存命中提升、查詢加速。建議在表設計評審階段引入「Schema 體檢清單」,將優化前置到開發周期。