Overview
To understand the difference between MySQL, MySQLi and PDO first we need to understand all of it individually.
There are two or three PHP APIs for accessing the MySQL database depending on PHP version. The user who uses PHP 5, they can choose MySQL which is deprecated, Mysqli or PDO. The user who uses PHP 7, they have only two options either MySQL or PDO.
MySQL
This is the main original extension designed to develop PHP applications that interact with a MySQL database. It provides procedural interface but it is deprecated as of PHP 5.5.0 and has been removed as of PHP 7.0.0. So, I suggest to use either MySQLi or PDO extension.
MySQLi
The i stands for Improved. The MySQLi is an extension to work with MySQL version 4.1.13 or newer. MySQLi is introduced with PHP Version 5.0 and MySQLi takes advantage of the newer features of MySQL 5.
PDO
PDO stands for PHP Data Objects. It is a database access layer providing a uniform method of access to multiple databases. PDO supports twelve different databases which are as below:
- CUBRID
- MS SQL Server
- Firebird/Interbase
- IBM
- Informix
- MySQL
- MS SQL Server
- Oracle
- ODBC and DB2
- PostgreSQL
- SQLite
- 4D
PDO sadly has one disadvantage, it does not allow you to use all of the advanced features that are available in the latest versions of MySQL server. Consider example, PDO does not allow you to use MySQL’s support for Multiple Statements.
Difference between MySQL, MySQLi and PDO
The main differences of three extensions are as below.
MySQL | MySQLi | PDO | |
---|---|---|---|
Introduced with PHP version | 2.0 | 5.0 | 5.1 |
Development status | Maintenance only in 5.x; removed in 7.x | Yes | Yes |
Lifecycle | Deprecated in 5.x; Removed in 7.x | Active | Active |
Recommended for new projects | No | Yes | Yes |
OOP Interface | No | Yes | Yes |
Procedural Interface | Yes | Yes | No |
API supports non-blocking, asynchronous queries with mysqlnd | No | Yes | No |
Persistent Connections | Yes | Yes | Yes |
API supports Charsets | Yes | Yes | Yes |
API supports server-side Prepared Statement | No | Yes | Yes |
API supports client-side Prepared Statement | No | No | Yes |
API supports Stored Procedures | No | Yes | Yes |
API supports Multiple Statements | No | Yes | Most |
API supports Transactions | No | Yes | Yes |
Transaction can be controlled with SQL | Yes | Yes | Yes |
Supports all MySQL 5.1+ functionality | No | Yes | Most |
Now, let’s discuss some functional differences of above three extensions.
Connection to MySQL database
In MySQL function we can connect to the database by following code:
<?php $connection_link = mysql_connect("host.domain.com", "username", "password"); mysql_select_db("database_name", $connection_link); mysql_set_charset('UTF-8', $connection_link); ?>
In MySQLi to create a connection just instantiating a new instance of MySQLi and using a username with a password connecting to the database, as follow:
<?php $mysqli_db = new mysqli(‘host.domain.com’, 'username', 'password', 'database_name'); ?>
In PDO We need to create a new PDO object. The constructor accepts four parameters for specifying the database source which are DSN (data source name) and optionally username, password. DSN is basically a string of options of driver to use and connection details of database.
<?php $pdo = new PDO('mysql:host=host.domain.com;dbname=database_name;charset=utf8','username','password'); ?>
You can pass fourth parameter as of several driver options as an array. Some PDO drivers do not support native prepared statements, so I recommend pass the parameter which put PDO into exception mode, so PDO performs emulation of the prepare. You can also manually enable this emulation. To use the native server-side prepared statements, you should explicitly set it false. Prepare emulation should be turned off to use PDO safely.
It will throw PDOException object if there is any connection error, we can catch it to handle Exception. So we can set attributes after PDO construction with the setAttribute method:
<?php $db = new PDO('mysql:host=localhost.com;dbname=data;charset=utf8', 'user', 'pass'); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); ?>
Error Handling
In MySQL
<?php $my_result = mysql_query("SELECT * FROM table", $connection_link) or die(mysql_error($connection_link)); ?>
We cannot handle the thing in die but die is not a good approach to handle error. Die will end the script abruptly and then echo the error to the screen which you usually don’t want to show to your users, and let hackers to discover your schema.
In MySQLi
mysqli::$error – mysqli_error : Returns a string description of the last error.
<?php if (!$mysqli->query("SET a=1")) { printf("Errormessage: %s\n", $mysqli->error); } ?>
In PDO
In PDO a better solution is exceptions. Anything in PDO should be wrapped in a try-catch block. Also we can force PDO into error mode by setting the error mode attribute from following error modes.
PDO::ERRMODE_SILENT: By setting this error code you must check each result then check $db->errorInfo() to get error details.
PDO::ERRMODE_WARNING: It provides Run-time warnings (E_WARNING) not fatal errors. It’s execution does not halt the script.
PDO::ERRMODE_EXCEPTION: It throw exceptions that shows an error raised by PDO. You should not throw a PDOException with your own code. It acts much like or die(mysql_error()), when it isn’t caught. But we can catch these PDOException and handle as we want.
We can set these error modes as follow:
<?php $db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); $db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING ); $db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); ?>
You can add a try/catch block as like below:
<?php try { $db->query('hello'); //Invalid query } catch (PDOException $ex) { //$ex->getMessage(); echo "An Error has occurred"; } ?>
You can set a user friendly error message instead of exception message. You should use try/catch block for your PDO part also you can use it outside the function that has the PDO functions.
<?php function get_data($db) { $stmt = $db->query("SELECT * FROM `data_table`"); return $stmt->fetchAll(PDO::FETCH_ASSOC); } try { get_data($db); } catch(PDOException $ex) { echo "An Error has occurred"; //$ex->getMessage(); // you can log these errors } ?>
Fetching Data from database
In MySQL
Using loop on results the username for each row will be the out as following:
<?php $my_result = mysql_query('SELECT * from data') or die(mysql_error()); $num_rows = mysql_num_rows($my_result); while($row = mysql_fetch_assoc($my_result)) { echo $row['field1']; } ?>
In MySQLIi
Using loop on results the username for each row will be the out as following:
<?php while($row = $my_result->fetch_assoc()){ echo $row['username'] . '\n'; } ?>
In PDO
PDOStatement::fetchAll() – Returns an array containing all of the resultset rows PDOStatement::fetchColumn() – Returns a single column from the next row of a resultset. PDOStatement::fetchObject() – Fetches the next row and returns it as an object. PDOStatement::setFetchMode() – Set the default fetch mode for this statement
query() returns a PDOStatement object by that you can fetch the data directly by using foreach and for loop also.
<?php $stmt = $db->query('SELECT * FROM `data_table`'); $my_results = $stmt->fetchAll(PDO::FETCH_ASSOC); ?>
Getting the Row Count
Instead of using mysql_num_rows() in MySQL and mysqli_num_rows() or $my_result->num_rows in MySQLi to get the number of returned rows, you can get a PDOStatement and do rowcount, like:
<?php $stmt = $db->query('SELECT * FROM `data_table`'); $row_count = $stmt->rowCount(); echo $row_count.' rows selected'; ?>
Update statements
We use mysql_affected_rows() in MySQL and mysqli_affected_rows() or $my_result->affected_rows in MySQLi. In PDO, we can do the same by:
<?php $affected_rows = $db->exec("UPDATE table SET field='value'"); echo $affected_rows; ?>
There are some functionalities that are not supported by MySQL and supported by remaining both MySQLi and PDO.
Prepared Statements
A prepared statement is a precompiled SQL statement that can be executed multiple times by sending the data to the server.
The flow of the prepared statement is as follows:
- Prepare: The statement template is created by the application and sent to the DBMS. Some parameters are left unspecified by placeholders “?” like as below:
INSERT INTO data (fname, lname) VALUES (?, ?)
- Bind: The DBMS parses, compiles, and performs query optimization on statement template and stores the result without executing it.
- Execute: The application binds values for the parameters and the DBMS executes the statement. The application may execute the statement as many times as it wants with different values.
Named placeholders: You can use a descriptive names preceded by a colon, instead of question marks. We don’t care about position or order of value in named placeholder:
$stmt->bindParam(':foo', $foo);
Unnamed placeholders: In unnamed placeholders we must follow the proper order of the elements.
In MySQLi
<?php $id = '2'; $stmt = $db->prepare("SELECT `name` FROM `users` WHERE `id` = ?"); $stmt->bind_param('i', $id); $stmt->execute(); $stmt->bind_result($out_name); while($stmt->fetch()){ echo $out_name . '\n'; } ?>
In PDO
<?php $id = '2'; $stmt = $db->prepare("SELECT `name` FROM `users` WHERE `id` = ?"); $stmt->bind_param('i', $id); $stmt->execute(); $stmt->bind_result($out_name); while($stmt->fetch()){ echo $out_name . '\n'; } ?>
API Support
Both PDO and MySQLi provides an object-oriented approach, but MySQLi provides a procedural way also like old mysql extension. New users may prefer MySQLi because of procedural interface which is similar to old mysql extension, So migrating from the old mysql extension is easier. But after mastering in PDO, you can use it with any database you like.
Security
Both PDO and MySQLi provide SQL injection security, as long as developers follow them with features like escaping and parameter binding with prepared statements.
For example a hacker is trying to inject some malicious SQL through the ‘name’ HTTP query parameter (POST):
$_POST['name'] = "'; DELETE FROM users; /*"
If we fail to escape, it will be added in query “as it is”, it will delete all rows from users table as PDO and MySQLi support multiple queries.
In PDO, “manual” escaping
$name = PDO::quote($_POST['name']); $pdo->query("SELECT * FROM users WHERE name = $name");
In MySQLi, “manual” escaping
$name = mysqli_real_escape_string($_POST['name']); $mysqli->query("SELECT * FROM users WHERE name = '$name'");
PDO::quote() not only escapes the string, but it also quotes it. But mysqli_real_escape_string() will only escape the string, you will need to apply the quotes manually.
Conclusion
There are only two possibilities to use extension because MySQL is deprecated as of PHP 5.5.0 and removed as of PHP 7.0.0.
- MySQLi
- PDO
As per my opinion, if there are possibilities to use more than one database drivers or another than MySQL in your project then you can use PDO, otherwise use MySQLi because PDO not support latest functionalities of MySQL.