必须先确认mysqli扩展是否启用,因旧项目若仅用废弃的mysql_*函数则不支持预处理语句;需通过phpinfo()或extension_loaded('mysqli')检查,并注意CLI与Web环境的php.ini可能不同。

旧项目没PDO,mysqli扩展又没启用,先确认能不能用
很多 PHP 5.6 项目还在用废弃的 mysql_* 函数(如 mysql_query),这类函数根本**不支持预处理语句**,强行改写参数化查询会直接报错或静默失败。必须先确认底层是否可用 mysqli 扩展。
执行 phpinfo() 或运行以下代码检查:
if (extension_loaded('mysqli')) {
echo "mysqli 已启用";
} else {
echo "mysqli 未启用,请检查 php.ini 或重装扩展";
}常见坑:
-
mysqli扩展名是mysqli,不是mysql或mysqlnd; - 某些老旧环境启用了
mysql扩展但禁用了mysqli,需手动开启; - CLI 和 Web SAPI 的
php.ini可能不同,php -m | grep mysqli和网页中phpinfo()要分别验证。
mysqli_connect() 后必须显式设置字符集,否则宽字节注入复活
PHP 5.6 + MySQL 环境下,如果只调用 mysqli_connect() 而不设置字符集,连接默认使用 latin1,一旦数据库或表用的是 gbk、gb2312 等双字节编码,%df%5c 这类宽字节绕过就会生效——addslashes() 加的反斜杠会被“吞掉”,单引号逃逸。
立即学习“PHP免费学习笔记(深入)”;
正确做法是连接后立刻执行:
$conn = mysqli_connect($host, $user, $pass, $db);
if (!$conn) die("连接失败");
// 必须加这行,且字符集要和数据库/表一致
mysqli_set_charset($conn, 'utf8mb4'); // 推荐;若旧库用 gbk,则写 'gbk'注意:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
mysqli_set_charset()是函数,不是 SQL 语句,不能用mysqli_query($conn, "SET NAMES utf8mb4")替代(后者在某些版本下不生效); - 如果数据库实际是
gbk编码,却设成utf8mb4,会导致中文乱码或查询失败; - 字符集不匹配时,
mysqli_prepare()可能返回false且无明确错误提示。
mysqli_prepare() 绑定参数必须匹配类型,字符串不能省略 "s"
PHP 5.6 中 mysqli_prepare() 要求严格指定参数类型,漏写或写错类型标识符(如该用 "s" 却写了 "i")会导致绑定失败、查询空结果,甚至触发 mysqli_stmt::execute() 返回 false 但不报错。
典型安全写法:
$stmt = mysqli_prepare($conn, "SELECT * FROM users WHERE username = ? AND status = ?");
if (!$stmt) {
error_log("Prepare failed: " . mysqli_error($conn));
die("系统错误");
}
// 注意:两个参数,第一个是字符串,第二个是整数 → "si"
mysqli_stmt_bind_param($stmt, "si", $username, $status);
mysqli_stmt_execute($stmt);易错点:
-
"s"表示字符串,"i"是整数,"d"是浮点,"b"是 blob; - 变量必须传引用(
&$username),PHP 5.6 不支持传值绑定; - 如果参数是动态拼接的字段名或表名(如
ORDER BY ?),?占位符**不适用**,必须用白名单校验后硬编码。
无法全局替换时,优先修复登录、搜索、ID 查询三类高危接口
旧项目代码量大,不可能一夜之间全改成预处理。应按风险等级排序,优先动手:
-
登录接口:用户名/密码直拼 SQL 是万能密码(
' OR 1=1 --)的温床,必须立即用mysqli_prepare()改造; -
搜索功能:用户输入进
LIKE '%?%'的地方,容易被%' OR 1=1 --绕过,且常被忽略; -
ID 类查询:如
user.php?id=123,看似数字型,但若没用intval()或类型绑定,攻击者可传123' UNION SELECT ...。
这些接口一旦修复,就能拦截 90% 的自动化扫描器和初级渗透行为。剩下的低频、后台管理类接口可排期逐步改造。
最后提醒:别信 mysqli_real_escape_string() 能替代预处理——它只是转义,不是分离,遇上宽字节、多字节编码、嵌套引号等场景照样失效。参数化查询不是“可选项”,是 PHP 5.6 环境下唯一靠谱的防线。


















