TLDR
// The key is the "charset=utf8" part.
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8';
$dbh = new PDO($dsn, 'user', 'pass');
这个答案强调 php 的 pdo 库,因为它无处不在。
一个简短的提醒 - mysql 是一个客户端-服务器架构。这很重要,因为不仅有实际数据库所在的 mysql 服务器,而且还有单独的 mysql 客户端驱动程序,这是与 mysql 服务器通信的东西(它们是单独的实体)。你可以说 mysql 客户端和 pdo 混合在一起。
当您使用set names utf8 时,您向mysql 发出标准sql 查询。虽然 sql 查询确实通过 pdo,然后通过 mysql 客户端库,最后到达 mysql 服务器,但只有 mysql 服务器解析和解释该 sql 查询。这很重要,因为 mysql 服务器不会将任何消息发送回 pdo 或 mysql 客户端让它知道字符集和编码已更改,因此 mysql 客户端和 pdo 都完全不知道它发生的事实。
不要这样做很重要,因为如果客户端库不知道当前字符集,它就无法正确处理字符串。大多数常见操作都可以在客户端不知道正确字符集的情况下正常工作,但字符串转义(例如PDO::quote)则不会。您可能认为您不需要担心这种手动原始字符串转义,因为您使用准备好的语句,但事实是绝大多数 pdo:mysql 用户在不知不觉中使用emulated prepared statements,因为它是 pdo:mysql 的默认设置司机很长时间了。模拟的预处理语句不使用 mysql api 提供的真正的原生 mysql 预处理语句;相反,php 相当于在您的所有值上调用 PDO::quote(),并为您用引用的值替换所有占位符。
除非您知道所使用的字符集,否则您无法正确转义字符串,因此如果您通过set names 更改为某些字符集,这些模拟的预处理语句很容易受到 sql 注入的影响。不管 sql 注入的可能性如何,如果您使用针对不同字符集的转义方案,您仍然可以破坏您的字符串。
对于pdo mysql驱动,可以在连接时指定字符集,通过specifying it in the DSN。如果你这样做,客户端库和服务器都会知道字符集,所以事情会像他们应该的那样工作。
// The key is the "charset=utf8" part.
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8';
$dbh = new PDO($dsn, 'user', 'pass');
但不正确的字符串转义并不是唯一的问题。例如,您在使用PDO::bindColumn 时也会遇到问题,因为列名被指定为字符串,因此编码也很重要。一个示例可能是名为ütube(注意变音符号)的列名,您通过集合名称从latin 切换到utf8,然后您尝试使用$stmt->bindColumn('ütube', $var); 转换为$stmt->bindColumn('ütube', $var);,其中ütube 是一个utf8 编码字符串,因为你的 php 文件是 utf8 编码的。它行不通,你需要将字符串编码为 latin1 变体......现在你有各种疯狂的事情发生。