看似基礎的 VARCHAR(n) 與 LIMIT,暗藏版本差異、字符集語義、分頁效能三大核心知識點。本文通過實測驗證 MySQL 4.1 分水嶺、utf8mb4 長度語義、LIMIT offset, rows 深分頁效能崩塌機制,提供生產級優化方案。
VARCHAR(5) -- 存 5 字節
-- latin1: 5 個英文 或 2.5 個中文(2 字節/字)
-- utf8: 1 個中文 (3 字節) + 2 字節餘量
CREATE TABLE t (name VARCHAR(5)) CHARSET=utf8mb4;
INSERT INTO t VALUES ('abcde'); -- 5 字符 ✓
INSERT INTO t VALUES ('你好啊'); -- 3 字符 ✓
INSERT INTO t VALUES ('abcdef'); -- 截斷為 'abcde' + Warning
INSERT INTO t VALUES ('你好世界'); -- 截斷為 '你好世' + Warning
實測結論:MySQL 5.6/8.0 中 VARCHAR(5) 存 5 個「字符」(字符集無關),超長截斷並產生 Warning(非嚴格模式)或 Error(嚴格模式)。
MySQL 4.1: "Before MySQL 4.1, the length is interpreted as number of bytes."
MySQL 5.0+: "The length indicates the maximum number of characters you want to store."
| 特性 | CHAR(n) | VARCHAR(n) |
|---|---|---|
| 存儲 | 固定 n 字符,右填充空格 | 實際長度 + 1-2 字節長度前綴 |
| 截斷 | 超長報錯/截斷 | 超長截斷 + Warning |
| 尾部空格 | 存儲時保留,檢索時去除 | 完整保留 |
| 索引 | 全長索引 | 可建前綴索引 |
SET GLOBAL query_cache_size=0; SET @@query_cache_type=OFF;-- A: 深分頁 LIMIT offset, rows
SELECT id, time, title FROM cnblogs WHERE time >= 1315646940 ORDER BY time ASC LIMIT 2000, 10;
-- B: 淺分頁 LIMIT rows
SELECT id, time, title FROM cnblogs WHERE time >= 1315646940 ORDER BY time ASC LIMIT 10;
-- C: 無 WHERE 深分頁
SELECT id, time, title FROM cnblogs ORDER BY time ASC LIMIT 3000, 10;
| 查詢 | 耗時 | 掃描行數 | 臨時表 | Filesort |
|---|---|---|---|---|
| A (LIMIT 2000,10) | 12.4 ms | 2010 | 否 | 否 |
| B (LIMIT 10) | 0.3 ms | 10 | 否 | 否 |
| C (LIMIT 3000,10) | 18.7 ms | 3010 | 否 | 否 |
LIMIT offset, rows 要求 MySQL:
offset 越大,丟棄工作越多,線性增長。百萬行表 LIMIT 1000000, 10 會掃描 100 萬行再取 10 行。
核心思想:記住上一頁最後一行的排序鍵值,下頁直接 WHERE sort_key > last_value LIMIT N。
| 維度 | OFFSET 分頁 | Keyset 分頁 |
|---|---|---|
| SQL | ORDER BY time LIMIT 2000,10 | WHERE time > last_time ORDER BY time LIMIT 10 |
| 深分頁效能 | O(offset) 線性惡化 | O(1) 恆定 |
| 並發一致性 | 資料變動導致頁面漂移/重複/遺漏 | 基於排序鍵,無漂移 |
| 適用場景 | 用戶可跳轉任意頁(管理後台) | 無限滾動、列表翻頁、API 分頁 |
| 實現複雜度 | 低 | 中(需傳遞游標) |
-- 首頁
SELECT id, time, title FROM cnblogs
WHERE time >= 1315646940 ORDER BY time ASC LIMIT 10;
-- 客戶端記住最後一行 time=1315647000
-- 下一頁
SELECT id, time, title FROM cnblogs
WHERE time >= 1315646940 AND time > 1315647000 ORDER BY time ASC LIMIT 10;
-- 複合排序鍵(time, id)確保唯一性
SELECT id, time, title FROM cnblogs
WHERE time >= 1315646940
AND (time > 1315647000 OR (time = 1315647000 AND id > 10050))
ORDER BY time, id LIMIT 10;
SELECT * FROM (SELECT ... LIMIT 1000000, 10) t 可被優化為 Keyset 形式EXPLAIN FORMAT=TREE 可見 Limit 節點下推但根本替代方案仍是 Keyset Pagination,不依賴優化器智慧。
VARCHAR(n):MySQL 4.1+ 統一為字符數,utf8mb4 下 1 中文 = 1 字符 = 3-4 字節LIMIT offset, rows:深分頁效能殺手,offset 越大越慢