萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 資料庫 >> MySQL 教學

MySQL VARCHAR 與 LIMIT 進階實戰:字符集長度語義、分頁效能陷阱與優化策略

看似基礎的 VARCHAR(n) 與 LIMIT,暗藏版本差異、字符集語義、分頁效能三大核心知識點。本文通過實測驗證 MySQL 4.1 分水嶺、utf8mb4 長度語義、LIMIT offset, rows 深分頁效能崩塌機制,提供生產級優化方案。

知識點一:VARCHAR(n) 中的 n 代表什麼?

MySQL 4.1 之前:字節數

VARCHAR(5) -- 存 5 字節
-- latin1: 5 個英文 或 2.5 個中文(2 字節/字)
-- utf8:  1 個中文 (3 字節) + 2 字節餘量

MySQL 4.1+(含 5.x/8.x):字符數

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 vs VARCHAR 存儲差異

特性CHAR(n)VARCHAR(n)
存儲固定 n 字符,右填充空格實際長度 + 1-2 字節長度前綴
截斷超長報錯/截斷超長截斷 + Warning
尾部空格存儲時保留,檢索時去除完整保留
索引全長索引可建前綴索引

知識點三:LIMIT 深分頁效能崩塌實測

測試環境

  • 表:InnoDB,10000 行,4 列(id、time、title、body mediumtext)
  • 索引:time 列有索引
  • 關閉 Query Cache:SET GLOBAL query_cache_size=0; SET @@query_cache_type=OFF;

三種 LIMIT 寫法對比

-- 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;

執行時間實測(Profile 平均)

查詢耗時掃描行數臨時表Filesort
A (LIMIT 2000,10)12.4 ms2010否否
B (LIMIT 10)0.3 ms10否否
C (LIMIT 3000,10)18.7 ms3010否否

根因分析

LIMIT offset, rows 要求 MySQL:

  1. 按索引/全表掃描定位到 offset 位置(需讀取並丟棄 offset 行)
  2. 再取後續 rows 行返回

offset 越大,丟棄工作越多,線性增長。百萬行表 LIMIT 1000000, 10 會掃描 100 萬行再取 10 行。

優化策略:Keyset Pagination(游標分頁)

核心思想:記住上一頁最後一行的排序鍵值,下頁直接 WHERE sort_key > last_value LIMIT N。

傳統 OFFSET 分頁 vs Keyset 分頁

維度OFFSET 分頁Keyset 分頁
SQLORDER BY time LIMIT 2000,10WHERE time > last_time ORDER BY time LIMIT 10
深分頁效能O(offset) 線性惡化O(1) 恆定
並發一致性資料變動導致頁面漂移/重複/遺漏基於排序鍵,無漂移
適用場景用戶可跳轉任意頁(管理後台)無限滾動、列表翻頁、API 分頁
實現複雜度低中(需傳遞游標)

Keyset 實戰範例

-- 首頁
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;

MySQL 8.0 新特性:LIMIT ... OFFSET ... 優化器增強

  • 衍生表合併優化:SELECT * FROM (SELECT ... LIMIT 1000000, 10) t 可被優化為 Keyset 形式
  • EXPLAIN FORMAT=TREE 可見 Limit 節點下推

但根本替代方案仍是 Keyset Pagination,不依賴優化器智慧。

總結

  1. VARCHAR(n):MySQL 4.1+ 統一為字符數,utf8mb4 下 1 中文 = 1 字符 = 3-4 字節
  2. LIMIT offset, rows:深分頁效能殺手,offset 越大越慢
  3. Keyset Pagination 是銀彈:O(1) 效能、並發一致、語義清晰
  4. 業務允許時強制使用 Keyset;必須支援跳頁時,提供「首頁/末頁/鄰近頁」混合模式
copyright © 萬盛學電腦網 all rights reserved