mysql_*函數已通過時,至關一段時間以來,mysql_*函數在其餘SQL數據庫編程接口方面已經有所差異;它不支持預處理,存儲過程,事務等一些現代數據庫設計思想,SQL語句字符串轉義函數 mysql_real_escape_string()
和 拼接SQL語句的編程方法 已通過時而且很容易出錯。最近一段時間裏,它缺少開發者的關注,缺乏維護將可能致使一些安全問題不能被即時修復,或者在適配新版本的MySQL的時候不能 正常工做,這成爲mysql_*函數面臨的的另外一個問題。PHP社區最近也對mysql_*函數給出不建議使用的建議,也有可能在將來的版本中最終被棄用 (不過不用過於擔憂,這可能還須要很長一段時間)。php
PDO擁有更好的編程接口,你可使用它寫出更加簡潔,高效,安全的代碼。PDO還爲不一樣的SQL數據庫提供了不一樣的驅動,方便你 使用新的數據庫而不用再學習不一樣的編程接口。與拼接SQL語句構造查詢語句不一樣,綁定參數能夠簡潔方便的構造出更加安全的查詢語句,使用綁定參數的方法在 屢次類似語句查詢(僅僅某個參數不一樣)中也能夠提升很多性能。PDO在錯誤處理方面也提供了多種方法。mysql_*函數缺少一致的處理,與PDO的異常 模式相比,或者說沒有處理異常,使用PDO,你能夠獲得一致的錯誤處理,這將節省您大量的時間來跟蹤問題。mysql
在當前的PHP版本中,PDO模塊是默認安裝啓用的,可是在使用PDO前你還須要安裝另外兩個軟件包,一個是pdo_mysql數據庫驅動程序,另一個是相似php-mysql的mysql驅動程序。sql
$link = mysql_connect('localhost', 'user', 'pass');mysql_select_db('testdb', $link);mysql_set_charset('UTF-8', $link);
* 建立一個PDO對象,參數包括 DSN, username, password 和 一個驅動選項的數組(可忽略)。
* DSN其實就是一個告訴PDO該使用哪種數據庫驅動 和 一些鏈接信息的字符串,瞭解更多 PDO MYSQL DSN .數據庫
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');
注意:確保DSN中設置了字符編碼信息,不然將可能返回字符編碼設置錯誤的信息,出於安全考慮,DSN最好包括字符編碼信息設置。 編程
你也能夠在第四個參數數組裏填寫一些驅動選項,建議將 PDO異常模式
(下文講解) 和 關閉預處理模擬
(默認打開的,僅對於舊版本MySQL有用)兩個參數加入到第四個參數數組中。數組
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password', array(PDO::ATTR_EMULATE_PREPARES => false,PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
你也能夠在建立PDO對象後再經過setAttribute方法設置相應選項。安全
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
//connected to mysql$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));
OR die()
是個不錯的錯誤處理方法,可是會所以結束頁面,將錯誤信息呈現到用戶面前,這多是咱們不想看到的結果。
PDO有三種錯誤處理模式:服務器
PDO::ERRMODE_SILENT # 和 mysql_*函數相似,檢查代碼並查看 $db->errorInfo();
獲取詳細信息。app
PDO::ERRMODE_WARNING # 拋出PHP警告。數據庫設計
PDO::ERRMODE_EXCEPTION #拋出 PDOException
異常,在我認爲,這是咱們應該使用的模式, 這和 die(mysql_error());
相似,可是它能夠捕獲並拋出具體異常信息。
try { //connect as appropriate as above $db->query('hi'); //invalid query!} catch(PDOException $ex) { echo "An Error occured!"; //user friendly message some_logging_function($ex->getMessage());}
注意:你能夠不用當即執行並捕獲異常,你能夠在任何合適的時候隨時捕獲。
function getData($db) { $stmt = $db->query("SELECT * FROM table"); return $stmt->fetchAll(PDO::FETCH_ASSOC);}//then much latertry { getData($db);} catch(PDOException $ex) { //handle me.}
若是你不想使用try/catch
來處理異常,就像使用OR die()
那樣處理,在production模式下關閉display_errors
選項便可。
SELECT
)$result = mysql_query('SELECT * from table') or die(mysql_error());$num_rows = mysql_num_rows($result);while($row = mysql_fetch_assoc($result)) { echo $row['field1'].' '.$row['field2']; //etc...}
foreach($db->query('SELECT * FROM table') as $row) { echo $row['field1'].' '.$row['field2']; //etc...}
query()
方法返回了一個 PDOStatement
對象,你能夠經過以下方法獲取結果:
$stmt = $db->query('SELECT * FROM table');while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { echo $row['field1'].' '.$row['field2']; //etc...}
或者
$stmt = $db->query('SELECT * FROM table');$results = $stmt->fetchAll(PDO::FETCH_ASSOC);//use $results
注意 fetch()
和 fetchAll()
代碼中的PDO::FETCH_ASSOC
,它高速 PDO 以關聯數組的形式返回 鍵,值;其餘好比PDO::FETCH_NUM
模式,則返回數值鍵值的數組,默認模式是 PDO::FETCH_BOTH
則返回前面二者的集合,既有數值鍵值的數組,又有關聯數組。PDO也能夠獲取數據返回對象PDO::FETCH_OBJ
,PDO::FETCH_CLASS
,PDO::FETCH_BOUND
,bindColumn
方法等更多內容,請閱讀: PDOStatement Fetch documentation。
代替 mysql_num_rows
方法,你可使用 PDOStatement
對象的rowCount();
方法。
$stmt = $db->query('SELECT * FROM table');$row_count = $stmt->rowCount();echo $row_count.' rows selected';
注意:官方文檔稱此函數僅適用於返回 `UPDATE`, `INSERT`, `DELETE`操做的`affected rows`,而 `SELECT`操做,僅對於`PDO_MYSQL` 驅動,此函數一樣適用(謹記),在操做其餘數據庫的時候尤爲注意。
mysql_*代碼:
$result = mysql_query("INSERT INTO table(firstname, lastname) VALUES('John', 'Doe')") or die("Insert Failed ".mysql_error());$insert_id = mysql_insert_id();
$result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')");$insertId = $db->lastInsertId();
INSERT
, UPDATE
, DELETE
操做$results = mysql_query("UPDATE table SET field='value'") or die(mysql_error());$affected_rows = mysql_affected_rows($result);echo $affected_rows.' were affected';
$affected_rows = $db->exec("UPDATE table SET field='value'");echo $affected_rows.' were affected'
DELETE
, INSERT
操做一樣適用。
對於 不攜帶任何參數的查詢語句,咱們可使用 query
方法處理SELECT
操做,使用exec
方法處理 INSERT
,UPDATE
,INSERT
操做,而對於攜帶查詢參數的語句,你應該使用綁定參數的方法來安全的處理這些操做。
$results = mysql_query(sprintf("SELECT * FROM table WHERE id='%s' AND name='%s'", mysql_real_escape_string($id), mysql_real_escape_string($name))) or die(mysql_error());$rows = array();while($row = mysql_fetch_assoc($results)){ $rows[] = $row;}
$stmt = $db->prepare("SELECT * FROM table WHERE id=? AND name=?");$stmt->execute(array($id, $name));$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
prepare方法將查詢語句發送到服務器,以「?」做爲參數佔位符進行編譯,execute方法將查詢參數發送到服務器,運行以前編譯好的查詢語句。由於 查詢語句 和 查詢參數 是分開發送的,因此在參數裏的SQL語句是不可能被執行的,因此不會發生 SQL注入,這是一種比鏈接字符串構造SQL語句更加安全的解決方法。
注意: 當你使用**綁定參數**的時候,不要對"?"佔位符使用引號(SQL語句原來是對參數使用引號的),由於參數類型是在execute方法的時候肯定的,因此在prepare的時候沒必要對佔位符使用引號。
還有一些綁定參數的方法,bindValue方法能夠分別綁定每一個參數來代替execute方法的數組方式,同時還分別設置每一個參數的類型。
$stmt = $db->prepare("SELECT * FROM table WHERE id=? AND name=?");$stmt->bindValue(1, $id, PDO::PARAM_INT);$stmt->bindValue(2, $name, PDO::PARAM_STR);$stmt->execute();$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
若是你有許多參數須要綁定,不要使用問號佔位符
,以防混淆出錯,你可使用命名佔位符
代替問號佔位符
。
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");$stmt->bindValue(':id', $id, PDO::PARAM_INT);$stmt->bindValue(':name', $name, PDO::PARAM_STR);$stmt->execute();$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
你也可使用execute方法,以數組的方式綁定參數:
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");$stmt->execute(array(':name' => $name, ':id' => $id));$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
INSERT
, DELETE
, UPDATE
預處理語句的使用和SELECT
相似,咱們舉幾個例子:
$stmt = $db->prepare("INSERT INTO table(field1,field2,field3,field4,field5) VALUES(:field1,:field2,:field3,:field4,:field5)");$stmt->execute(array(':field1' => $field1, ':field2' => $field2, ':field3' => $field3, ':field4' => $field4, ':field5' => $field5));$affected_rows = $stmt->rowCount();
$stmt = $db->prepare("DELETE FROM table WHERE id=:id");$stmt->bindValue(':id', $id, PDO::PARAM_STR);$stmt->execute();$affected_rows = $stmt->rowCount();
$stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");$stmt->execute(array($name, $id));$affected_rows = $stmt->rowCount();
=>無效方法:
//THIS WILL NOT WORK!$time = 'NOW()';$name = 'BOB';$stmt = $db->prepare("INSERT INTO table(`time`, `name`) VALUES(?, ?)");$stmt->execute(array($time, $name));
=>正確方法
$name = 'BOB';$stmt = $db->prepare("INSERT INTO table(`time`, `name`) VALUES(NOW(), ?)");$stmt->execute(array($name));
=>你也能夠在SQL函數裏綁定參數:
$name = 'BOB';$password = 'badpass';$stmt = $db->prepare("INSERT INTO table(`hexvalue`, `password`) VALUES(HEX(?), PASSWORD(?))");$stmt->execute(array($name, $password));
=>可是不能做爲LIKE的參數:
//THIS DOES NOT WORK$stmt = $db->prepare("SELECT field FROM table WHERE field LIKE %?%");$stmt->bindParam(1, $search, PDO::PARAM_STR);$stmt->execute();
=>正確使用LIKE並綁定參數的方法:
$stmt = $db->prepare("SELECT field FROM table WHERE field LIKE ?");$stmt->bindValue(1, "%$search%", PDO::PARAM_STR);$stmt->execute();
注意:這裏使用的是bindValue而不是bindParam,不然會發生PDOException或致命錯誤。
預處理語句能夠一次設置,屢次調用,由於僅在第一次傳入的時候編譯,所以在後來的屢次調用中提升了很多效率。
典型的應用就是bindParam
,bindParam
與bindValue
的不一樣之處在於,它不是綁定了參數的值,而是綁定參數變量自己,所以,若是參數變量變化了,那麼在execute處理的時候,查詢也將相應變化。
$values = array('bob', 'alice', 'lisa', 'john');$name = '';$stmt = $db->prepare("INSERT INTO table(`name`) VALUES(:name)");$stmt->bindParam(':name', $name, PDO::PARAM_STR);foreach($values as $name) { $stmt->execute();}
注意:調用`beginTransaction()`方法即自動關閉了`自動提交`。
try { $db->beginTransaction(); $db->exec("SOME QUERY"); $stmt = $db->prepare("SOME OTHER QUERY?"); $stmt->execute(array($value)); $stmt = $db->prepare("YET ANOTHER QUERY??"); $stmt->execute(array($value2, $value3)); $db->commit();} catch(PDOException $ex) { //Something went wrong rollback! $db->rollBack(); echo $ex->getMessage();}