必须显式设置charset=utf8mb4和pdo::attr_errmode=>pdo::errmode_exception,否则90%乱码和静默失败;dsn中漏charset则后续exec("set names")无效;禁用模拟预处理以保障原生预处理防注入。

直接用 PDO 连 MySQL,不设 charset=utf8mb4 和 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,基本等于裸奔——中文变 ???、错误静默失败、SQL注入风险照旧。
DSN里必须显式写 charset=utf8mb4
只在数据库建表时设 utf8mb4 不够,PHP 连接层也得对齐,否则插入 Emoji 或生僻字会截断或乱码。DSN 字符串里漏掉 charset=utf8mb4,后续执行 $pdo->exec("SET NAMES utf8mb4") 是无效的——存在竞态:两条语句之间如果有并发请求,可能拿到错误编码的结果。
-
$dsn = "mysql:host=localhost;dbname=test;charset=utf8mb4"—— 正确,字符集绑定在连接建立瞬间 - 不要写成
"mysql:host=localhost;dbname=test"再单独exec,也不要用utf8(不支持四字节) - 如果用 Docker 或远程云数据库,注意 host 不能写
localhost(可能走 Unix socket),改用127.0.0.1更可控
错误模式必须设为 PDO::ERRMODE_EXCEPTION
默认的 PDO::ERRMODE_SILENT 会让所有错误返回 false,你得手动检查每条语句的返回值;PDO::ERRMODE_WARNING 只打日志,程序继续跑,极难定位问题。只有 PDO::ERRMODE_EXCEPTION 能让连接失败、查询语法错、字段不存在等全部变成可捕获的 PDOException。
- 必须在构造
PDO实例时通过第四个参数$options传入,或构造后立刻调用$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) - 别信“先连上再设”,有些错误(如 DNS 解析失败)发生在构造阶段,设晚了没机会生效
- 生产环境别用
die()暴露错误详情,但开发阶段至少要确保异常能抛出来
禁用模拟预处理:PDO::ATTR_EMULATE_PREPARES => false
开启模拟预处理(默认 true)会让 PDO 把占位符替换成实际值后拼 SQL 发给 MySQL,绕过服务端预处理,失去类型校验和部分防注入能力。关掉它,才能真正用上 MySQL 原生预处理。
- 加上
PDO::ATTR_EMULATE_PREPARES => false后,:name或?占位符由 MySQL 服务端解析,参数严格按类型绑定 - 某些老版本 MySQL(JSON_EXTRACT)可能不兼容,遇到报错再临时打开,但需评估风险
- 配合命名占位符(
:email)比问号更易读,也避免参数顺序错位
用户名密码不能是裸词,必须是字符串或变量
写成 new PDO($dsn, username, password) 是典型错误:PHP 把 username 当作常量,未定义就报 Use of undefined constant 警告,并把字面量 'username' 当密码传给 MySQL,结果必现 Access denied for user 'username'@'localhost'。
- 正确写法是
new PDO($dsn, 'root', 'your_pass')或用已赋值变量:$user = 'root'; $pass = 'xxx'; new PDO($dsn, $user, $pass) - 密码为空时传
''(空字符串),不是null或省略参数 - 敏感信息务必从
$_ENV或配置文件加载,禁止硬编码在代码里
最易被忽略的是 DSN 中 charset 和错误模式的组合生效时机——它们必须在 PDO 实例化那一行就到位,少一个字符、晚一次 setAttribute,都可能让整个连接链路处于不可靠状态。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











