存儲過程定義示例
DELIMITER //
CREATE PROCEDURE `sp_get_employee_list`(
IN p_dept_id INT,
OUT p_total_count INT
)
BEGIN
SELECT COUNT(*) INTO p_total_count FROM employees WHERE dept_id = p_dept_id;
SELECT id, name, salary, hire_date
FROM employees
WHERE dept_id = p_dept_id
ORDER BY hire_date DESC;
END //
DELIMITER ;
mysqli 面向對象調用
<?php
$mysqli = new mysqli("localhost", "root", "pass", "company");
$mysqli->set_charset("utf8mb4");
$deptId = 10;
// 1. 調用存儲過程(多結果集:第一個是 OUT 參數 SELECT,第二個是業務數據)
$sql = "CALL sp_get_employee_list($deptId, @total)";
if ($mysqli->multi_query($sql)) {
// 結果集 1:OUT 參數(這裡通過 SELECT @total 獲取)
do {
if ($result = $mysqli->store_result()) {
// 業務數據結果集
while ($row = $result->fetch_assoc()) {
echo "ID: {$row['id']}, Name: {$row['name']}, Salary: {$row['salary']}
";
}
$result->free();
}
} while ($mysqli->more_results() && $mysqli->next_result());
// 獲取 OUT 參數值
$outResult = $mysqli->query("SELECT @total AS total_count");
$outRow = $outResult->fetch_assoc();
echo "部門總人數: " . $outRow['total_count'];
$outResult->free();
} else {
echo "調用失敗: " . $mysqli->error;
}
$mysqli->close();
PDO 調用(推薦,參數綁定更安全)
<?php
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false, // 真實預處理
]);
$deptId = 10;
// 1. 註冊 OUT 參數
$stmt = $pdo->prepare("CALL sp_get_employee_list(:dept_id, @total_count)");
$stmt->bindParam(':dept_id', $deptId, PDO::PARAM_INT);
$stmt->execute();
// 2. 獲取業務結果集
do {
if ($stmt->columnCount() > 0) { // 有列信息說明是數據結果集
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
print_r($row);
}
}
} while ($stmt->nextRowset());
// 3. 獲取 OUT 參數
$outStmt = $pdo->query("SELECT @total_count AS total");
$total = $outStmt->fetchColumn();
echo "總記錄數: $total";
僅有 OUT/INOUT 參數的簡化調用
-- 存儲過程
CREATE PROCEDURE sp_calc_bonus(
IN p_emp_id INT,
IN p_rate DECIMAL(5,2),
OUT p_bonus DECIMAL(10,2)
)
BEGIN
SELECT salary * p_rate INTO p_bonus FROM employees WHERE id = p_emp_id;
END;
-- PHP (mysqli)
$mysqli->query("SET @bonus = 0");
$mysqli->query("CALL sp_calc_bonus(101, 0.15, @bonus)");
$res = $mysqli->query("SELECT @bonus AS bonus");
$bonus = $res->fetch_assoc()['bonus'];
echo "獎金: $bonus";
參數類型對應表
| MySQL 類型 | PDO::PARAM_* | mysqli 綁定類型 |
| INT / BIGINT | PARAM_INT | 'i' |
| VARCHAR / TEXT | PARAM_STR | 's' |
| DECIMAL / FLOAT | PARAM_STR | 'd' |
| DATE / DATETIME | PARAM_STR | 's' |
| BOOL / TINYINT(1) | PARAM_BOOL | 'i' |
| BLOB / BINARY | PARAM_LOB | 'b' |
常見錯誤與解決
| 錯誤 | 原因 | 解決 |
| PROCEDURE ... can't return a result set | mysqli 默認不支持存儲過程返回結果集 | 啟用 mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT) 或使用 multi_query |
| Commands out of sync | 未處理完所有結果集 | 循環 next_result() 直到 false |
| OUT 參數為 NULL | 未在 SQL 中聲明用戶變量 @xxx | 調用前 SET @out = 0,調用後 SELECT @out |
| 參數綁定失效 | PDO::ATTR_EMULATE_PREPARES = true | 設置 false 使用真實預處理 |
最佳實踐建議
- 業務邏輯下沉存儲過程:複雜事務、批量計算、報表統計適合;簡單 CRUD 保持在應用層。
- 版本控制:存儲過程代碼納入 Git,遷移腳本管理。
- 權限最小化:應用用戶僅授予
EXECUTE 權限,無直接表操作權限。
- 參數化防注入:嚴禁拼接 CALL 語句,全部使用預處理綁定。
- 結果集處理:預期可能有多結果集,統一用
multi_query / nextRowset() 循環處理。