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