為什麼需要 SQL 級預處理?
- 無編程接口環境(如純 SQL 腳本、客戶端手工測試)
- 測試預處理行為、構建 Bug 重現用例
- 舊版客戶端庫連接新版 Server 時利用新功能
三大核心語句
PREPARE stmt_name FROM preparable_stmt;
EXECUTE stmt_name [USING @var_name [, @var_name] ...];
{DEALLOCATE | DROP} PREPARE stmt_name;
完整流程範例
範例 1:字符串字面量預處理
PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
SET @a = 3; SET @b = 4;
EXECUTE stmt1 USING @a, @b;
-- 結果:hypotenuse = 5
DEALLOCATE PREPARE stmt1;
範例 2:用戶變量持有 SQL 文本
SET @s = 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
PREPARE stmt2 FROM @s;
SET @a = 6; SET @b = 8;
EXECUTE stmt2 USING @a, @b;
-- 結果:hypotenuse = 10
DEALLOCATE PREPARE stmt2;
範例 3:LIMIT 動態分頁
SET @skip = 1; SET @num = 5;
PREPARE stmt3 FROM 'SELECT * FROM tbl LIMIT ?, ?';
EXECUTE stmt3 USING @skip, @num; -- 返回第 2-6 行
DEALLOCATE PREPARE stmt3;
關鍵規則與限制
| 規則 | 說明 |
| 參數標記 | 僅能用 ? 表示數據值位置,不可用於標識符/關鍵字 |
| 單語句 | preparable_stmt 必須是單條 SQL,不允許分號分隔多條 |
| 隱式釋放 | 同名 PREPARE 會自動 DEALLOCATE 舊語句 |
| 作用域 | 會話級,斷開連接自動釋放 |
| 支援語句 | CREATE TABLE, DELETE, DO, INSERT, REPLACE, SELECT, SET, UPDATE, 多數 SHOW |
| 不支援 | DDL 中的某些子句、事務控制、鎖語句等 |
| 嵌套禁止 | 預處理語句本身不能是 PREPARE/EXECUTE/DEALLOCATE |
| 存儲程序限制 | 可用於存儲過程,**不可用於存儲函數/觸發器** |
與編程接口(C API / JDBC)的區別
- SQL 介面走文本協議,編程接口走二進制協議(效能更高)
- SQL 介面無法使用
mysql_stmt_prepare() 等 C API 函數
- 編程接口提供綁定參數緩衝區、結果集元數據等高級功能
實戰場景
- 動態建表/改表: 表名作為變量拼接
- 批量參數化執行: 循環調用 EXECUTE,避免重複解析
- SQL 注入防範: 參數與代碼分離,原生防注入
- 測試驗證: 快速驗證執行計劃、參數綁定行為
最佳實踐
- 用完即
DEALLOCATE PREPARE,避免佔用 prepared_statement 資源
- 配合
max_prepared_stmt_count 監控預處理語句數量
- 生產代碼優先使用驅動層預處理(JDBC
PreparedStatement、Go sql.Prepare)
- SQL 介面預處理作為補充:運維腳本、臨時測試、舊客戶端兼容