PDO引數化查詢
引數化查詢(Parameterized Query或Parameterized Statement)是指在設計與資料庫連結並訪問資料時,在需要填入數值或資料的地方,使用引數(Parameter)來給值,這個方法目前已被視為最有效可預防SQL注入攻擊的攻擊手法的防禦方式。
除了安全因素,相比起拼接字串的SQL語句,引數化的查詢往往有效能優勢。因為引數化的查詢能讓不同的資料通過引數到達資料庫,從而公用同一條SQL語句。大多數資料庫會快取解釋SQL語句產生的位元組碼而省下重複解析的開銷。如果採取拼接字串的SQL語句,則會由於操作資料是SQL語句的一部分而非引數的一部分,而反覆大量解釋SQL語句產生不必要的開銷。
原理
在使用引數化查詢的情況下,資料庫伺服器不會將引數的內容視為SQL指令的一部分來處理,而是在資料庫完成SQL指令的編譯後,才應用引數執行,因此就算引數中含有具破壞性的指令,也不會被資料庫所執行。
PDO
PDO用於PHP之內。在使用PDO驅動時,引數查詢的使用方法一般為:
// 例項化資料抽象層物件 $db = new PDO('pgsql:host=127.0.0.1;port=5432;dbname=testdb'); // 對SQL語句執行prepare,得到PDOStatement物件 $stmt = $db->prepare('SELECT * FROM "myTable" WHERE "id" = :id AND "is_valid" = :is_valid'); // 繫結引數 $stmt->bindValue(':id', $id); $stmt->bindValue(':is_valid', true); // 查詢 $stmt->execute(); // 獲取資料 foreach($stmt as $row) { var_dump($row); }
對於MySQL的特定驅動,也可以這樣使用:
$db = new mysqli("localhost", "user", "pass", "database"); $stmt = $db -> prepare("SELECT priv FROM testUsers WHERE username=? AND password=?"); $stmt -> bind_param("ss", $user, $pass); $stmt -> execute();
值得注意的是,以下方式雖然能有效防止SQL注入(歸功於mysql_real_escape_string函式的轉義),但並不是真正的引數化查詢。其本質仍然是拼接字串的SQL語句。
$query = sprintf("SELECT * FROM Users where UserName='%s' and Password='%s'", mysql_real_escape_string($Username), mysql_real_escape_string($Password)); mysql_query($query);使用事例
1、準備語句
重複執行一個SQL查詢,通過每次迭代使用不同的引數,這種情況使用預處理語句執行效率最高。使用預處理語句,首先需要在資料庫伺服器中先準備好“一個SQL語句”,但並不需要馬上執行。PDO支援使用“佔位符”語法,將變數繫結到這個預處理的SQL語句中。對於一個準備好的SQL語句,如果在每次執行時都要改變一些列值,這種情況必須使用“佔位符號”而不是具體的列值。在PDO中有兩種使用佔位符的語法:“命名引數”和“問號引數”,使用哪一種語法要看個人的喜好。
使用命名引數作為佔位符的INSERT插入語句:
$dbh->prepare(“insert into contactinfo(name,address,phone) values(:name,:address,:phone)”);
需要自定義一個字串作為“命名引數”,每個命名引數需要冒號(:)開始,引數的命名一定要有意義,最好和對應的欄位名稱相同。
使用問號(?)引數作為佔位符的INSERT插入語句:
複製程式碼程式碼如下:
$dbh->prepare(“insert into contactinfo(name,address,phone) values(?,?,?)”);
問號引數一定要和欄位的位置順序對應。不管是使用哪一種引數作為佔位符構成的查詢,或是語句中沒有用到佔位符,都需要使用PDO物件中的prepare()方法,去準備這個將要用於迭代執行的查詢,並返回PDOStatement類物件。
2、繫結引數
當SQL語句通過PDO物件中的prepare()方法在資料庫伺服器端準備好了以後,如果使用了佔位符,就需要在每次執行時替換輸入的引數。可以通過PDOStatement物件中的bindParam()方法,把引數變數繫結到準備好的佔位符上(位置或名字要對應)。方法bindParame()的原型如下所示:
bool PDOStatement::bindParam ( mixed $parameter , mixed &$variable [, int $data_type = PDO::PARAM_STR [, int $length [, mixed $driver_options ]]] )
第一個引數parameter是必選項,如果在準備好的查詢中佔位符語法使用名字引數,那麼將名字引數字串作為bindParam()方法的第一個引數提供。如果佔位符語法使用問號引數,那麼將準備好的查詢中列值佔位符的索引偏移量,作為該方法的第一個引數。
第二個引數variable也是可選項,提供供給第一個引數所指定佔位符的值。因為該引數是按引用傳遞的,所以只能提供變數作為引數,不能直接提供數值。
第三個引數data_type是可選項,為當前被繫結的引數設定資料型別。可以為以下值。
PDO::PARAM_BOOL 代表boolean資料型別。
PDO::PARAM_NULL 代表SQL中的NULL型別。
PDO::PARAM_INT 代表SQL中的INTEGER資料型別。
PDO::PARAM_STR 代表SQL中的CHAR、VARCHAR和其他字串資料型別。
PDO::PARAM_LOB 代表SQL中大物件資料型別。
第四個引數length是可選項,用於指定資料型別的長度。
第五個引數driver_options是可選項,通過該引數提供任何資料庫驅動程式特定的選項。
使用命名引數作為佔位符的引數繫結示例:
<?php
//...省略PDO連線資料庫程式碼
$query = "insert into contactinfo (name,address,phone) values(:name,:address,:phone)";
$stmt = $dbh->prepare($query); //呼叫PDO物件中的prepare()方法
$stmt->blinparam(':name',$name); //將變數$name的引用繫結到準備好的查詢名字引數":name"中
$stmt->blinparam(':address',$address);
$stmt->blinparam(':phone',phone);
//...
?>
使用問號(?)作為佔位符的引數繫結示例:
<?php
//...省略PDO連線資料庫程式碼
$query = "insert into contactinfo (name,address,phone) values(?,?,?)";
$stmt = $dbh->prepare($query); //呼叫PDO物件中的prepare()方法
$stmt->blinparam(1,$name,PDO::PARAM_STR); //將變數$name的引用繫結到準備好的查詢名字引數":name"中
$stmt->blinparam(2,$address,PDO::PARAM_STR);
$stmt->blinparam(3,phone,PDO::PARAM_STR,20);
//...
?>
3、執行準備語句
當準備語句完成,並綁定了相應的引數後,就可以通過呼叫PDOStatement類物件中的execute()方法,反覆執行在資料庫快取區準備好的語句了。在下面的示例中,向前面提供的contactinfo表中,使用預處理方式連續執行同一個INSERT語句,通過改變不同的引數新增兩條記錄。如下所示:
<?php
try {
$dbh = new PDO('mysql:dbname=testdb;host=localhost', $username, $passwd);
}catch (PDOException $e){
echo '資料庫連線失敗:'.$e->getMessage();
exit;
}
$query = "insert into contactinfo (name,address,phone) values(?,?,?)";
$stmt = $dbh->prepare($query);
$stmt->blinparam(1,$name);
$stmt->blinparam(2,$address);
$stmt->blinparam(3,phone);
$name = "趙某某";
$address = "海淀區中關村";
$phone = "15801688348";
$stmt->execute(); //執行引數被繫結後的準備語句
?>
如果你只是要傳遞輸入引數,並且有許多這樣的引數要傳遞,那麼你會覺得下面所示的快捷方式語法非常有幫助。是通過在execute()方法中提供一個可選引數,該引數是由準備查詢中的命名引數佔位符組成的陣列,這是第二種為預處理查詢在執行中替換輸入引數的方式。此語法使你能夠省去對$stmt->bindParam()的呼叫。將上面的示例做如下修改:
複製程式碼程式碼如下:
<?php
//...省略PDO連線資料庫程式碼
$query = "insert into contactinfo (name,address,phone) values(?,?,?)";
$stmt = $dbh->prepare($query);
//傳遞一個數組為預處理查詢中的命名引數繫結值,並執行一次。
$stmt->execute(array("趙某某","海淀區","15801688348"));
?>
另外,如果執行的是INSERT語句,並且資料表中有自動增長的ID欄位,可以使用PDO物件中的lastinsertId()方法獲取最後插入資料表中的記錄ID。如果需要檢視其他DML語句是否執行成功,可以通過PDOStatement類物件中的rowCount()方法獲取影響記錄的行數。