業務系統普遍面臨分頁查詢需求,但每張表寫專用分頁 SQL 既繁瑣又難維護。MySQL 5.0+ 支援預存程序與動態 SQL,讓我們能封裝一個通用分頁預存程序,支援任意表、任意欄位、任意條件、任意排序、任意頁碼/頁大小。本文提供生產級完整實現與調用範例。
DELIMITER $$
DROP PROCEDURE IF EXISTS `prc_page_result` $$
CREATE PROCEDURE `prc_page_result`(
IN `p_currpage` INT, -- 當前頁碼(從 1 開始)
IN `p_columns` VARCHAR(500), -- 查詢欄位,如 "id, name, created_at" 或 "*"
IN `p_tablename` VARCHAR(500), -- 目標表名
IN `p_condition` VARCHAR(500), -- WHERE 條件(不含 WHERE 關鍵字),如 "status=1 AND type=2"
IN `p_order_field` VARCHAR(100), -- 排序欄位
IN `p_asc_field` TINYINT, -- 1=DESC,0=ASC
IN `p_primary_field` VARCHAR(100),-- 主鍵欄位(用於游標分頁)
IN `p_pagesize` INT -- 每頁筆數
)
BEGIN
DECLARE v_sql VARCHAR(4000);
DECLARE v_order VARCHAR(1000);
DECLARE v_temp VARCHAR(100);
-- 構建 ORDER BY 子句
IF p_asc_field = 1 THEN
SET v_order = CONCAT(' ORDER BY ', p_order_field, ' DESC ');
SET v_temp = ' < (SELECT MIN ';
ELSE
SET v_order = CONCAT(' ORDER BY ', p_order_field, ' ASC ');
SET v_temp = ' > (SELECT MAX ';
END IF;
-- 第一頁:直接 LIMIT
IF p_currpage = 1 THEN
IF p_condition <> '' THEN
SET v_sql = CONCAT('SELECT ', p_columns, ' FROM ', p_tablename,
' WHERE ', p_condition, v_order, ' LIMIT ?');
ELSE
SET v_sql = CONCAT('SELECT ', p_columns, ' FROM ', p_tablename,
v_order, ' LIMIT ?');
END IF;
ELSE
-- 非第一頁:游標式分頁(避免 OFFSET 深分頁效能崩塌)
IF p_condition <> '' THEN
SET v_sql = CONCAT('SELECT ', p_columns, ' FROM ', p_tablename,
' WHERE ', p_condition,
' AND ', p_primary_field, v_temp,
'(', p_primary_field, ') FROM (SELECT ',
p_primary_field, ' FROM ', p_tablename, v_order,
' LIMIT ', (p_currpage-1)*p_pagesize, ') AS tmp',
v_order, ' LIMIT ?');
ELSE
SET v_sql = CONCAT('SELECT ', p_columns, ' FROM ', p_tablename,
' WHERE ', p_primary_field, v_temp,
'(', p_primary_field, ') FROM (SELECT ',
p_primary_field, ' FROM ', p_tablename, v_order,
' LIMIT ', (p_currpage-1)*p_pagesize, ') AS tmp',
v_order, ' LIMIT ?');
END IF;
END IF;
-- 準備並執行動態 SQL
SET @page_size = p_pagesize;
SET @stmt_sql = v_sql;
PREPARE stmt FROM @stmt_sql;
EXECUTE stmt USING @page_size;
DEALLOCATE PREPARE stmt;
END $$
DELIMITER ;
OFFSET,百萬級資料深分頁效能不退化LIMIT ? 配合 USING @page_sizeMIN/MAX 子查詢自動處理mysql -u root -p < paging_proc.sql
-- 第 1 頁,按 id 降序,每頁 25 筆
CALL prc_page_result(1, '*', 'articles', 'status=1', 'id', 1, 'id', 25);
-- 第 3 頁,多條件,按 created_at 升序
CALL prc_page_result(3, 'id,title,created_at', 'articles',
'status=1 AND category_id=5', 'created_at', 0, 'id', 20);
public List<Map> queryPage(int page, int size, String table,
String columns, String where,
String orderBy, String pk, boolean desc) {
String sql = "{CALL prc_page_result(?, ?, ?, ?, ?, ?, ?, ?)}";
// 使用 CallableStatement 綁定參數
}
| 指標 | OFFSET 分頁 | 游標分頁(本方案) |
|---|---|---|
| 第 1 頁 | 快 | 快 |
| 第 100 頁 | 掃描 100*size 行 | 索引範圍掃描 |
| 第 10,000 頁 | 極慢/逾時 | 穩定毫秒級 |
| 適用場景 | 小資料量/淺分頁 | 大資料量/深分頁/高併發 |
WITH RECURSIVE CTE 實現更優雅的游標分頁這個通用分頁預存程序以 動態 SQL + 游標分頁 + 預處理 三大技術,解決了 MySQL 分頁查詢的通用性與效能雙重難題。部署一次,全系統複用,是中大型 MySQL 應用的標準化基建組件。