曲径通幽论坛

标题: SET NAMES 分析详解 [打印本页]

作者: beyes    时间: 2012-7-4 17:14
标题: SET NAMES 分析详解
了解 SET NAMES 之前,需要先了解 3 个和字符集相关的变量:
character_set_client ,  character_set_connection 和 character_set_results
实际上,SET NAMES 命令正好是一次性将这 3 个值进行修改,下面会提到。现在查看它们的默认值,可以通过下面的命令:
[Plain Text] 纯文本查看 复制代码
show variables like "%char%";

[attach]732[/attach]


这几个变量是和客户端与服务器之间的连接相关的。那么什么是一个连接?
当客户端向服务器发送 SQL 语句时,比如查询,那么就会连接到服务器,然后服务器通过连接发送响应给客户端,如查询的结果集。
对于客户端连接,这样会导致一些关于连接的字符集和 校对规则的问题,这些问题均能够通过系统变量来解决,而这些变量就是上面提到的 3 个变量。


这里有两个概念:一是“字符集”,二是“
校对规则
” 。


字符集是众所周知的,比如西欧用的 latin1,简体中文的 GB2312, GBK 等,如下所示:
[attach]733[/attach][attach]734[/attach]



还可以用 SHOW CHARACTER SET 命令列出当前可用的字符集。


那什么是“校验规则” 呢?
校对规则就是一套对编码比较的规则。最简单的校对规则就是二元(binary)校对规则,即逐字节相等,比如上图中的 gb2312_bin , gbk_bin ,latin1_bin 。


任何一个给定的字符集至少会有一个校对规则,也可能有几个,可以使用 SHOW COLLATION 命令列出一个字符集的校对规则,比如:
[attach]735[/attach]

上面的 SHOW COLLATION LIKE 'latin1%'; 命令那些名字以 latin1 开头的校对规则。比如
latin1_swedish_ci
代表的是 “瑞典/芬兰” ,“
latin1_danish_ci
” 代表的是 "丹麦/挪威" 。可以简单的着么认为:在一个字符集中,应用一个校对规则,可以相应的识别出一个或几个国家所使用的语言。


“校对规则“ 有以下 2 个特征:
(1) 两个不同的字符集不能有相同的校对规则。
(2) 每一个默认的字符集有一个默认的校对规则,例如 latin1 默认的校对规则是 latin1_swedish_ci 。


”校对规则“ 的命名约定为: _bin 表示”二元“, _ci 表示大小写不敏感,_cs 表示大小写敏感。


现在回过来看一下上面所提到的 3 个变量。


character_set_client 变量是指
当客户端发出查询后,
服务器使用它来
作为客户端发送的查询中使用的字符集。比如上面使用的是 utf8 。 在服务器收到查询后,服务器会把
character_set_client
字符集转换到
character_set_connection 所指定的字符集。在转换完毕后,服务器会发送结果集或错误信息到客户端,那么在此之前需要转换为那种字符集呢?是的,就是
character_set_results 这个变量所指定的。


下面做一下实验。


先假设上述的几个变量分别使用的是 utf8 字符集。


接下来要查询一个表,表中的某个字段里有中文,查询语句如下:
[Plain Text] 纯文本查看 复制代码
select topic_title from phpbb3.phpbb_topics;

[attach]736[/attach]

可以正常显示中文。


现在运行 SET NAMES 语句改变上面 3 个值:
[Plain Text] 纯文本查看 复制代码
SET NAMES GBK;
show variables like "%char%";

[attach]737[/attach]


由上图可见,上面提到的 3 个变量的值都从原来的 utf8 改变为 gbk 了。接下来再像上面的同样查询一次:

[Plain Text] 纯文本查看 复制代码
select topic_title from phpbb3.phpbb_topics;

[attach]738[/attach]

原来正常显示的中文乱码了。


为什么呢?
从头开始分析。首先对于运行 SQL 命令的终端就是”客户端“。假设现在 character_set_client 是 GBK ,那么客户端就会要求使用 GBK 去查询,那么服务器收到查询请求后(建立连接后),它会先去查看 character_set_connection 的值,并将 character_set_client 的值转换成它的字符集,查询完毕后,根据 character_set_results 所设置的字符集返回。实际上,上面的乱码和 character_set_client 和 character_set_connection 都没有关系,问题是出在 character_set_results 上。因为对于 Linux 系统本身,它是使用 utf8 编码的,也就是说可以在终端(shell) 上能看到正常的 utf8 编码的中文汉字,而不能看到 GB2312,GBK 这种编码的中文形式 -- 也就是说,在这种情况下你看到的就是乱码。


对此,可以将 character_set_results 这个变量设置为 utf8 ,来验证我们上面的所述:
[Plain Text] 纯文本查看 复制代码
SET character_set_results = utf8;

[attach]739[/attach]



再次和上面的同样查询,可以看到:
[attach]740[/attach]

由上图可以看到,中文在终端下又恢复了显示。


下面,我们先将上面的 3 个值还原为 utf8 ,接着可以再做一个试验。这次我们将在 php 文件里做同样的事情。查询数据库的 PHP 代码如下:
[Plain Text] 纯文本查看 复制代码
<?php
        require_once("dbfuncs.php");


        $link = create_connection();


        $sql = "SELECT `topic_title` FROM `phpbb_topics` WHERE `topic_id` = 13";


        $result = execute_sql("phpbb3", $sql, $link);


        $row = mysql_fetch_assoc($result);


        echo $row["topic_title"];
?>

上面代码中的 dbfuncs.php 的代码为:
[Plain Text] 纯文本查看 复制代码
<?php
function create_connection()
{
    $link = mysql_connect("localhost", "root", "www.groad.net")
        or die("Can not connect to database<br><br>" . mysql_error());


    mysql_query("SET NAMES utf8");
    return $link;
}


function execute_sql($database, $sql, $link)
{
    $db_selected = mysql_select_db($database, $link)
        or die("Select database failed<br><br>" . mysql_error($link));


    $result = mysql_query($sql, $link);


    return $result;
}
?>

打开该网页后发现乱码了:
[attach]741[/attach]

道理很简单,返回是按照 utf8 的编码返回的,如果你想看到正常的结果显示,那么可以在浏览器里选择 UTF8 编码。这是因为,在简体中文的系统下,浏览器若是从 http header 中无法知道 content-type 里所指出的编码时,默认使用的是 GB2312 或 GBK 编码来显示网页。所以,你如果叫客户手动去选择浏览器的”编码“来正常显示你的网页这是一种多么不友好的一种做法。假如你确定浏览你改网页的用户一定是简体中文用户,那么可以在上面的 PHP 代码中添加一句:
[Plain Text] 纯文本查看 复制代码
mysql_query("SET NAMES GBK");

这样做之后,简体中文用户显示结果就是正常的了。当然,即使不用上面的那条语句,那么可以使用header()  函数来通知浏览器使用 UTF8 编码来显示网页,比如:
[Plain Text] 纯文本查看 复制代码
header(”Content-type: text/html;charset=utf-8″);



注意,上面的那条语句,不会改变服务器里所里设置的变量。








欢迎光临 曲径通幽论坛 (http://www.groad.net/bbs/) Powered by Discuz! X3.2