直接拼接字符串调用mysqli_query()极危险,因用户输入会混入SQL导致注入,如$_POST['id']为"1 OR 1=1"可拖库;PHP 5.5+已标记不安全,须改用预处理(mysqli_prepare+bind_param或PDO::prepare)。

mysqli_query() 直接拼接字符串为什么危险
因为用户输入会直接混入 SQL,比如 $_POST['id'] 值为 1 OR 1=1,拼出的语句就变成 SELECT * FROM users WHERE id = 1 OR 1=1,整张表被拖走。这种写法在 PHP 5.5+ 已被明确标记为不安全,很多团队已禁用。
常见错误现象:mysqli_query() 返回 false 却没检查,或用 mysql_real_escape_string()(已废弃)假装防御,其实绕过方式极多。
- 永远不要用
mysqli_query($conn, "SELECT * FROM t WHERE id = " . $_GET['id']) - 即使加了
intval()或(int)强转,也要确认字段类型和业务逻辑是否真允许纯数字 - 字符串类参数(如用户名、邮箱)必须进预处理,不能靠
addslashes()或正则过滤
mysqli_prepare() + bind_param() 的基本写法
预处理本质是「先编译语句模板,再绑定数据」,数据库服务端能区分「结构」和「数据」,天然免疫注入。注意 bind_param() 的第一个参数是类型字符串,顺序和问号占位符严格对应。
示例:查询用户信息
立即学习“PHP免费学习笔记(深入)”;
$stmt = mysqli_prepare($conn, "SELECT id, name FROM users WHERE status = ? AND created_at > ?");
mysqli_stmt_bind_param($stmt, "is", $status, $since);
$status = "active";
$since = "2024-01-01";
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
while ($row = mysqli_fetch_assoc($result)) {
echo $row['name'];
}- 类型字符必须准确:
i(整数)、s(字符串)、d(双精度)、b(BLOB) - 变量必须是引用(
bind_param内部修改值),所以不能传字面量如bind_param("s", "admin"),要先赋给变量 - 如果语句含多个同类型参数,类型字符串可合并,如
"ss"表示两个字符串
PDO::prepare() 更灵活但默认不报错
PDO 的预处理接口更统一,支持多种数据库,但默认错误模式是 PDO::ERRMODE_SILENT,execute() 失败也不抛异常,容易漏掉错误。
必须显式设置:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
$stmt = $pdo->prepare("INSERT INTO logs (msg, level) VALUES (?, ?)");
$stmt->execute([$message, $level]);- 占位符可用
?(位置参数)或:name(命名参数),后者更适合复杂语句,比如WHERE id = :id AND type = :type - 命名参数绑定用
bindValue()或bindParam();前者传值,后者传引用——循环复用同一语句时,bindParam更高效 - 不要混用两种占位符,PDO 不支持
VALUES (?, :name)
旧代码迁移时最容易忽略的三件事
不是把 mysql_query() 换成 mysqli_query() 就算升级完成。真正卡点往往藏在细节里。
-
mysqli_fetch_array()默认返回数字+关联双键数组,而PDOStatement::fetch()默认只返回关联键(因设置了PDO::FETCH_ASSOC),字段访问从$row[0]变成$row['id'],不改会报Undefined offset - 事务控制函数不同:
mysqli_autocommit($conn, false)对应$pdo->beginTransaction(),回滚分别是mysqli_rollback()和$pdo->rollback(),漏调用会导致脏数据 - 连接超时、字符集等配置项位置变了:旧版在
mysql_connect()参数里设,新版需在 DSN(PDO)或mysqli_options()(MySQLi)中单独配置,比如PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"
字符集没对齐时,中文存进去是乱码,查出来是 ???,但错误日志里完全没提示——这是最常被跳过的兼容性盲区。











