PHP MySQL 預處理語句
預處理語句是什麼
預處理語句(也稱為參數化語句)只是一個SQL查詢模板,其中包含佔位符而不是實際參數值。在執行語句時,這些佔位符將被實際值替換。
MySQLi支持使用匿名位置佔位符(?),如下所示:
INSERT INTO persons (first_name, last_name, email) VALUES (?, ?, ?);
而PDO支持匿名位置佔位符(?)和命名佔位符。命名佔位符以冒號(:)開頭,後跟標識符,如下所示:
INSERT INTO persons (first_name, last_name, email)
VALUES (:first_name, :last_name, :email);
預處理語句執行包括兩個階段:準備和執行。
準備 – 在準備階段,將創建一個SQL語句模板並將其發送到數據庫服務器。服務器解析語句模板,執行語法檢查和查詢優化,並將其存儲以備後用。
執行 – 執行期間,參數值將發送到服務器。服務器從語句模板和這些值創建一個語句以執行它。
預處理語句非常有用,尤其是當您多次使用不同的值(例如一系列語句)多次執行一條特定的INSERT語句時。以下部分描述了使用它的一些主要優點。
使用預處理語句的優點
一個預處理語句可以高效地重複執行同一條語句,因為該語句僅被再次解析一次,而它可以多次執行。由於每次執行時僅需要將佔位符值傳輸到數據庫服務器,而不是傳輸完整的SQL語句,因此它還可以最大程度地減少帶寬使用。
預處理語句還提供了強大的保護,可防止SQL注入,因為參數值未直接嵌入在SQL查詢字符串中。使用不同的協議將參數值與查詢分開發送到數據庫服務器,因此不會干擾它。在解析語句模板之後,服務器直接在執行時使用這些值。這就是為什麼預處理語句不太容易出錯的原因,因此被認為是數據庫安全性中最關鍵的元素之一。
以下示例將向您展示預準備語句的實際工作方式:
示例:面向過程方式
<?php
/* 嘗試MySQL服務器連接。假設您正在運行MySQL。
具有默認設置的服務器(沒有密碼的用戶“root”) */
$link = mysqli_connect(“localhost”, “root”, “”, “demo”);
//檢查連接
if($link === false){
die(“錯誤:無法連接。 ” . mysqli_connect_error());
}
//使用預處理語句
$sql = “INSERT INTO persons (first_name, last_name, email) VALUES (?, ?, ?)”;
if($stmt = mysqli_prepare($link, $sql)){
//將變量作為參數綁定到預處理語句
mysqli_stmt_bind_param($stmt, “sss”, $first_name, $last_name, $email);
/* 設置參數值並執行,該語句再次插入另一行 */
$first_name = “Hermione”;
$last_name = “Granger”;
$email = “[email protected]”;
mysqli_stmt_execute($stmt);
/* 設置參數值並執行插入行的語句 */
$first_name = “Ron”;
$last_name = “Weasley”;
$email = “[email protected]”;
mysqli_stmt_execute($stmt);
echo “記錄插入成功。”;
} else{
echo “錯誤:無法準備查詢: $sql. ” . mysqli_error($link);
}
//關閉語句
mysqli_stmt_close($stmt);
//關閉連接
mysqli_close($link);
?>
示例:面向對象方式
<?php
/* 嘗試MySQL服務器連接。假設您正在運行MySQL。
具有默認設置的服務器(沒有密碼的用戶“root”) */
$mysqli = new mysqli(“localhost”, “root”, “”, “demo”);
//檢查連接
if($mysqli === false){
die(“錯誤:無法連接。 ” . $mysqli->connect_error);
}
// 使用預處理語句
$sql = “INSERT INTO persons (first_name, last_name, email) VALUES (?, ?, ?)”;
if($stmt = $mysqli->prepare($sql)){
// 將變量作為參數綁定到預處理語句
$stmt->bind_param(“sss”, $first_name, $last_name, $email);
/* 設置參數值並執行。
再次執行該語句以插入另一行 */
$first_name = “Hermione”;
$last_name = “Granger”;
$email = “[email protected]”;
$stmt->execute();
/* 設置參數值並執行
要插入行的語句 */
$first_name = “Ron”;
$last_name = “Weasley”;
$email = “[email protected]”;
$stmt->execute();
echo “已成功插入記錄。”;
} else{
echo “錯誤:無法準備查詢:$sql. ” . $mysqli->error;
}
//關閉語句
$stmt->close();
//關閉連接
$mysqli->close();
?>
示例:PDO方式
<?php
/* 嘗試MySQL服務器連接。假設您正在運行MySQL。
具有默認設置的服務器(沒有密碼的用戶“root”) */
try{
$pdo = new PDO(“mysql:host=localhost;dbname=demo”, “root”, “”);
// 將PDO錯誤模式設置為異常
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e){
die(“錯誤:無法連接。 ” . $e->getMessage());
}
//嘗試執行插入查詢
try{
//使用預處理語句
$sql = “INSERT INTO persons (first_name, last_name, email) VALUES (:first_name, :last_name, :email)”;
$stmt = $pdo->prepare($sql);
//將參數綁定到語句
$stmt->bindParam(‘:first_name’, $first_name, PDO::PARAM_STR);
$stmt->bindParam(‘:last_name’, $last_name, PDO::PARAM_STR);
$stmt->bindParam(‘:email’, $email, PDO::PARAM_STR);
/* 設置參數值並執行,
再次執行該語句以插入另一行 */
$first_name = “Hermione”;
$last_name = “Granger”;
$email = “[email protected]”;
$stmt->execute();
/* 設置參數值並執行
要插入行的語句 */
$first_name = “Ron”;
$last_name = “Weasley”;
$email = “[email protected]”;
$stmt->execute();
echo “記錄插入成功。”;
} catch(PDOException $e){
die(“錯誤:無法準備/執行查詢: $sql. ” . $e->getMessage());
}
// 關閉語句
unset($stmt);
//關閉連接
unset($pdo);
?>
如您在上面的示例中看到的,我們INSERT僅準備了一次語句,但是通過傳遞不同的參數集多次執行了該語句。
代碼的用法(程序風格)
在上面示例的SQL INSERT語句中,問號用作first_name,last_name和email字段值的佔位符。
mysqli_stmt_bind_param()函數將變量綁定到佔位符(?)在SQL語句模板中。佔位符(?)將替換為執行時變量中保存的實際值。作為第二個參數提供的類型定義字符串,即“sss”字符串指定每個綁定變量的數據類型為string(字符串)。
類型定義字符串指定相應綁定變量的數據類型,參數有以下四種類型:
i – integer(整型)
d – double(雙精度浮點型)
s – string(字符串)
b – BLOB(binary large object:二進制大對象)
類型定義字符串中的綁定變量數和字符數必須與SQL語句模板中的佔位符數匹配。
使用通過Web表單接收的輸入
如果您還記得上一章,我們已經創建了一個HTML表單,用於將數據插入數據庫。在這裡,我們將通過執行預處理語句來擴展該示例。您可以使用相同的HTML表單來測試以下插入腳本示例,但只需確保action在表單的屬性中使用了正確的文件名即可。
這是用於插入數據的更新的PHP代碼。如果仔細查看示例,您會發現我們沒有mysqli_real_escape_string()像上一章中的示例那樣使用來轉義用戶輸入。由於在預處理語句中,用戶輸入永遠不會直接替換為查詢字符串,因此不需要正確地對它們進行轉義。
示例:面向過程方式
<?php
/* 嘗試MySQL服務器連接。假設您正在運行MySQL。
具有默認設置的服務器(沒有密碼的用戶“root”) */
$link = mysqli_connect(“localhost”, “root”, “”, “demo”);
//檢查連接
if($link === false){
die(“錯誤:無法連接。 ” . mysqli_connect_error());
}
//使用預處理語句
$sql = “INSERT INTO persons (first_name, last_name, email) VALUES (?, ?, ?)”;
if($stmt = mysqli_prepare($link, $sql)){
//將變量綁定到準備好的語句作為參數
mysqli_stmt_bind_param($stmt, “sss”, $first_name, $last_name, $email);
//設定參數
$first_name = $_REQUEST[‘first_name’];
$last_name = $_REQUEST[‘last_name’];
$email = $_REQUEST[’email’];
//嘗試執行預處理語句
if(mysqli_stmt_execute($stmt)){
echo “記錄插入成功。”;
} else{
echo “錯誤:無法執行查詢: $sql. ” . mysqli_error($link);
}
} else{
echo “錯誤:無法執行查詢: $sql. ” . mysqli_error($link);
}
// 關閉語句
mysqli_stmt_close($stmt);
//關閉連接
mysqli_close($link);
?>
示例:面向對象方式
<?php
/* 嘗試MySQL服務器連接。假設您正在運行MySQL。
具有默認設置的服務器(沒有密碼的用戶“root”) */
$mysqli = new mysqli(“localhost”, “root”, “”, “demo”);
//檢查連接
if($mysqli === false){
die(“錯誤:無法連接。 ” . $mysqli->connect_error);
}
//使用預處理語句
$sql = “INSERT INTO persons (first_name, last_name, email) VALUES (?, ?, ?)”;
if($stmt = $mysqli->prepare($sql)){
//將變量作為參數綁定到預處理語句
$stmt->bind_param(“sss”, $first_name, $last_name, $email);
//設置參數
$first_name = $_REQUEST[‘first_name’];
$last_name = $_REQUEST[‘last_name’];
$email = $_REQUEST[’email’];
//嘗試執行預處理語句
if($stmt->execute()){
echo “記錄插入成功。”;
} else{
echo “錯誤:無法執行查詢: $sql. ” . $mysqli->error;
}
} else{
echo “錯誤:無法執行查詢: $sql. ” . $mysqli->error;
}
//關閉語句
$stmt->close();
//關閉連接
$mysqli->close();
?>
示例:PDO方式
<?php
/* 嘗試MySQL服務器連接。假設您正在運行MySQL。
具有默認設置的服務器(沒有密碼的用戶“root”) */
try{
$pdo = new PDO(“mysql:host=localhost;dbname=demo”, “root”, “”);
//將PDO錯誤模式設置為異常
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e){
die(“錯誤:無法連接。 ” . $e->getMessage());
}
//嘗試執行插入查詢
try{
//使用預處理語句
$sql = “INSERT INTO persons (first_name, last_name, email) VALUES (:first_name, :last_name, :email)”;
$stmt = $pdo->prepare($sql);
// 將參數綁定到語句
$stmt->bindParam(‘:first_name’, $_REQUEST[‘first_name’], PDO::PARAM_STR);
$stmt->bindParam(‘:last_name’, $_REQUEST[‘last_name’], PDO::PARAM_STR);
$stmt->bindParam(‘:email’, $_REQUEST[’email’], PDO::PARAM_STR);
// 執行預處理語句
$stmt->execute();
echo “記錄插入成功。”;
} catch(PDOException $e){
die(“錯誤:無法準備/執行查詢 $sql. ” . $e->getMessage());
}
//關閉語句
unset($stmt);
//關閉連接
unset($pdo);
?>
注意:儘管在預處理語句中不需要轉義用戶輸入,但是您應始終驗證從外部源接收到的數據的類型和大小,並實施適當的限制以防止系統資源的利用。