1. 程式人生 > >PDO引數化查詢

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()方法獲取影響記錄的行數。