是否使用“SET NAMES”
在阅读O'Reilly的《高性能MySQL》时,我遇到了以下内容:
另一个常见的错误查询是SET NAMES UTF8,这种做法本身就是错误的(它并不会改变客户端库的字符集;它只影响服务器)。
我有点困惑,因为我以前总是在每个脚本的开头加上“SET NAMES utf8”,以便让数据库知道我的查询是utf8编码的。
有没有人能对上面的引用做些评论,或者更正式地说,有什么建议或最佳实践可以确保我的数据库工作流程能够支持unicode?
我的目标语言是php和python,如果这有关系的话。
3 个回答
我不太确定Python的情况,但在PHP中,现在有一个叫做 mysql_set_charset
的函数。这个函数说明了这是“更改字符集的推荐方法”,而使用mysql_query()来执行SET NAMES是不推荐的。需要注意的是,这个函数是从MySQL 5.0.7版本开始引入的,所以在更早的版本中是无法使用的。
mysql_set_charset('utf8', $link);
这里的$link是通过 mysql_connect
创建的连接。
简要说明
// 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的用户在不知情的情况下使用了 模拟预处理语句,因为这已经是pdo:mysql驱动的默认设置很长时间了。模拟预处理语句并没有使用mysql api提供的真正的原生mysql预处理语句;相反,php会对你所有的值调用 PDO::quote()
,并将所有的占位符替换为已转义的值。
因为你无法正确转义字符串,除非你知道正在使用的字符集,所以如果你通过 set names
更改了某些字符集,这些模拟预处理语句就容易受到sql注入的攻击。不管sql注入的可能性如何,如果你使用了针对不同字符集的转义方案,你的字符串也可能会被破坏。
对于pdo mysql驱动,你可以在连接时指定字符集,通过 在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
(注意这个变音符号),你通过set names从 latin
切换到 utf8
,然后尝试用 $stmt->bindColumn('ütube', $var);
绑定这个列名,而这个列名是utf8编码的字符串,因为你的php文件是utf8编码的。这是行不通的,你需要将字符串编码为latin1变体……这样一来,就会出现各种奇怪的问题。
mysql_set_charset()
这个函数可以用来设置字符集,但它只适用于 ext/mysql
。如果你使用的是 ext/mysqli
,那么你需要用 mysqli_set_charset
。而如果你用的是 PDO
::mysql
,你还需要指定一个连接参数。
使用这个函数会调用 MySQL 的 API,所以它的速度比发出查询要快很多。
为了确保你的脚本和 MySQL 服务器之间能够快速地使用 UTF-8 进行通信,最有效的方法是正确设置 MySQL 服务器。SET NAMES x
的效果相当于
SET character_set_client = x;
SET character_set_results = x;
SET character_set_connection = x;
而 SET character_set_connection = x
这个命令在内部还会执行 SET collation_connection = <<default_collation_of_character_set_x>>
。你也可以在你的 my.ini/cnf
文件中静态地设置这些服务器变量。
请注意,如果同一个 MySQL 服务器上还有其他应用程序在运行,并且它们需要使用不同的字符集,可能会出现一些问题。