PDO更好,因其更贴近现代PHP设计逻辑和实际维护需求;支持多数据库切换、统一预处理安全机制、异常驱动错误处理及代码可测性与可扩展性优势。

直接说结论:PDO 更好,但不是因为“高级”,而是因为它更贴近现代 PHP 的设计逻辑和实际维护需求。如果你只做单数据库、小项目、不换库、不写测试,mysqli 也能跑通;但只要涉及多环境、多数据库类型、或需要可测性、可扩展性,PDO 的抽象层优势立刻体现出来。
为什么 PDO 的预处理默认更安全
PDO 在启用 PDO::ATTR_EMULATE_PREPARES => false 时,会把参数真正交给 MySQL 服务端做预编译,SQL 结构和数据完全分离。而 mysqli 的 prepare() 默认行为取决于配置,容易误以为“用了 prepare 就一定防注入”,其实如果 mysqli.reconnect=On 或连接中断后重连,某些旧版本会退化成模拟预处理,导致绕过。
-
PDO的绑定写法统一:$stmt->bindParam(':name', $name, PDO::PARAM_STR) -
mysqli要手动数参数类型:$stmt->bind_param('s', $name),类型字符错一位就静默失败 - 批量插入时,
PDO可复用同一PreparedStatement多次execute(),mysqli的bind_param()每次都要重绑
mysqli_real_escape_string 不能替代预处理
这是老项目里最常踩的坑:用 mysqli_real_escape_string() 拼 SQL 字符串,自以为防住了注入。但它只对字符串有效,对表名、字段名、ORDER BY 子句、LIMIT 偏移量等**无法转义的位置完全无效**,且在字符集不一致(如连接用 utf8mb4,但客户端声明为 latin1)时可能失效。
- 错误示例:
"SELECT * FROM user WHERE id = " . mysqli_real_escape_string($conn, $_GET['id'])——id是数字,不该用字符串转义,应强制(int)或用预处理 - 正确做法:所有动态值一律走
prepare + bind,哪怕只是LIMIT ?, ? -
mysqli不提供对标识符(如表名)的安全插值机制,PDO 同样没有——这类内容必须白名单校验,不能靠“转义”
PDO 切换数据库几乎零成本
当项目需要从 MySQL 迁到 PostgreSQL,或测试时想用 SQLite 内存库,PDO 只需改一行 DSN:mysql:host=localhost;dbname=test → sqlite:/tmp/test.db,其余 prepare/execute/fetch 代码全都不动。而 mysqli 是 MySQL 专属,函数名、错误码、事务控制方式、甚至 last_insert_id() 的行为都不同。
立即学习“PHP免费学习笔记(深入)”;
-
PDO统一异常模式:new PDO($dsn, $u, $p, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]),出错直接抛PDOException -
mysqli默认是静默失败,要手动mysqli_error()或设mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT)才接近 PDO 行为 -
PDO::quote()虽不推荐用于变量插值,但在动态生成 SQL 片段(如IN (?)列表)时比手写引号拼接更可靠
$pdo = new PDO('mysql:host=localhost;dbname=test', $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$stmt = $pdo->prepare("SELECT id, name FROM user WHERE status = ? AND created_at > ?");
$stmt->execute(['active', '2024-01-01']);
$rows = $stmt->fetchAll();真正容易被忽略的是:无论选 mysqli 还是 PDO,**连接复用、长连接配置、字符集声明(SET NAMES utf8mb4)、以及事务边界是否显式控制**,这些对稳定性和性能的影响远大于 API 差异。别花时间争论“哪个好”,先确保你的 init_connect 或连接建立后第一句执行了正确的字符集设置。











