FlawOpen

flawopen.com/SQL Injection in PHP/mysqli vs PDO

MySQLi vs PDO: which is safer against SQL injection?

Reference page
Short answer

A prepared statement keeps SQL instructions fixed and sends input values separately, like filling labeled blanks on a form. PDO and MySQLi can both do this. The safety comes from binding values correctly—not the library name.

The same query, prepared with either API

UNSAFE — SQL assembled with request data
$userId = $_GET['id'];
$sql = "SELECT id, email FROM users WHERE id = " . $userId;

// Both calls execute the already-built SQL string:
$rows = $mysqli->query($sql);
$rows = $pdo->query($sql);
SAFE — bind the value in either library
// PDO
$stmt = $pdo->prepare(
    "SELECT id, email FROM users WHERE id = :id"
);
$stmt->execute(["id" => $userId]);

// MySQLi
$stmt = $mysqli->prepare(
    "SELECT id, email FROM users WHERE id = ?"
);
$stmt->bind_param("i", $userId);
$stmt->execute();

Where the APIs differ

Database portability is PDO’s main distinction

PDO exposes a common interface across database drivers; MySQLi targets MySQL and MariaDB. PDO does not make SQL portable by itself: SQL dialects, driver behavior, and supported features still differ.

Placeholder styles differ

PDO can use named placeholders such as :id or positional ? placeholders, subject to driver rules; do not mix styles in one statement. MySQLi uses positional ? placeholders. Both bind values, and neither can bind table names, column names, keywords, or sort directions.

PDO prepare mode depends on the driver

PDO drivers may use native or emulated prepares. For MySQL, PDO::ATTR_EMULATE_PREPARES controls emulation when supported; drivers may emulate when native prepares are unavailable or fall back if a query cannot be prepared natively. Follow the driver’s documented behavior. This setting does not make interpolated SQL safe—keep query text fixed and bind data either way.

Rules that prevent SQL injection in either library

Use prepare() and bind every untrusted value. Keep SQL syntax fixed; choose dynamic identifiers from a strict server-side map or build a finite set of query variants. A prepared statement does not authorize access to a row, so scope queries to the authenticated user where required. For LIKE, binding prevents SQL syntax injection but % and _ remain wildcard characters unless escaped for literal matching.

Choosing between PDO and MySQLi

FAQ

Are PDO named placeholders safer than MySQLi question marks?

No. Both can safely bind values when the query is prepared and values are bound. Named placeholders may improve readability; they do not bind SQL identifiers or change authorization requirements.

Should I always disable emulated prepares in PDO?

Not as a blanket rule. PDO driver support and fallback behavior vary. Check the driver documentation and application requirements. Regardless of mode, keep SQL structure fixed and pass values as parameters.

Can a prepared statement bind a table or column name?

No. Bind parameters represent values, not SQL identifiers or keywords. Map a client choice to a finite set of trusted SQL fragments on the server.

References

See also SQL Injection (CWE-89): How It Works and How to Prevent It in Every Language — flawopen.com Is NHibernate Safe from SQL Injection? MongoDB NoSQL Injection: Safe Query Filters Why String Formatting Risks Numeric ID Queries What Is a Parameterized Query? Is Prisma $queryRaw Safe from SQL Injection? Is Rails where() String Interpolation Safe?