年多没碰mysql+php了,这次要用php做个东西,于是重新拾掇拾起来。不幸又碰到了传说中mysql的中文字符问题。这回我算是吧mysql的字符集给彻底弄清了

【问题综述】

$link = mysql_connection("host","usr","pwd");
		if (!$link )
		{
			echo  "connection faile";
		}
		mysql_select_db("libsys");
                $sql =select * from book where bookName='史记'";
		$rs = mysql_query($sql);
		
		if (!$rs) {
			echo "Could not successfully run query ($sql) from DB: " . mysql_error();
			exit ();
		}	
		else {
			while (($row= mysql_fetch_array($rs)))
			{
				echo "bookName:{$row['bookName']},authorName:{$row['authorName']},publisher:{$row['publisher']},ISBN:{$row['ISBN']},bookIndex:{$bookIndex['bookIndex']}";
			}
		mysql_freeresult($rs);
		}

1、无论怎么改动代码,只要进行中文查询返回结果集均为0 ;book 表的字符以 utf8_general_ci 存储的。

2、将book表转换成 gb2312后 出现错误,大意是gb2312 不能同latin1字符进行比较。

网上了解了下,原来  MySQL处理连接时,外部连接发送过来的SQL请求会根据以下顺序进行转换:
character_set_client             //客户连接所采用的字符集
        |
character_set_connection       //MySQL连接字符集
        |
character_set_database         //数据库所采用的字符集(表,列)
        |
character_set_results           //客户机显示所采用的字符集

简单说明下:

[1] 服务器(数据)端的字符集和collation,可以分成四级逐层指定——server, database, table, column。当MySQL存取位于某一列(column)的数据时,如果column的字符集和collation没有指定,就会向上追溯table 的;如果table也没有指定字符集和collation,就以database的字符集和collation作为默认值;如果database仍旧没有 指定,那么就以服务器的字符集和collation作为默认值。

那么server的字符集和collation的默认值又是从哪里来的呢?答案是,配置文件(my.ini)和mysqld(或者mysqld- nt)的命令行参数中都可以指定。如果不幸的,你根本没有在my.ini或者命令行中指定,那么MySQL就会使用编译MySQL时指定的默认字符集—— latin1。

但是,需要注意的是,如果安装MySQL时选择了多语言支持(一般用中文的都会选择吧),安装程序会自动在配置文件中设置default-character-set=utf8

如果没有,你也可以自己在 my.ini添加。

这样,所有创建的数据库、表,除非明确指出使用其它字符集,都会默认的使用utf作为数据的字符集(同时使用utf8_general_ci作为默认collation,因为它是utf8的默认collation)。

相关系统变量


character_set_server:服务器的字符集
collation_server:服务器的collation
character_set_database:数据库字符集
collation_database:数据库的collation


 

[2] 客户端。对于客户端传送来的literal string(例如INSERT,UPDATE语句当中的值),MySQL需要知道它们是什么编码。同时,MySQL返回给客户端的值(例如SELECT语句的返回值),也可以按照指定的编码返回。

相关系统变量


character_set_client:客户端发送过来文字的字符集
character_set_results:发送给客户端的结果所使用的字符集


 

[3] 连接。用于连接的字符集和collation,是指MySQL在接受到客户端发送来的文本之后,转换成何种字符集,用什么规则进行比较。需要注意的是,如果是将文本和数据库中某个column的值比较,将优先使用该column的字符集和collation。

相关系统变量


character_set_connection:用于连接的字符集
collation_connection:用于连接的collation


 

总结下Character set顾名思义,就是字符、以及字符对应的编码的集合。例如简体中文字符集gb2312就包括简体中文中的所有规定汉字,以及每个汉字对应的代码。是字符存储的方式

Collation,是指比较字符的规则的集合。有了比较规则,才能够将一组数据排序——例如按照英文字母顺序排序、汉字按照拼音顺序排序等等。显然,针 对同样一组字符集可以有不同的排序标准、规则。例如汉字可以按照拼音排序,也可以按照笔画多少排序。尤其是Unicode的字符集,由于其可以包含不同种 类的语言,所以可以按照各种语言的排序方法排序。此外,完全按照字符在字符集里的编码进行比较的方式称为binary比较。

 

总得说来server 的字符集 可以通过 mysql的 my.ini(my.cnf)指定

 database的字符集 则是你自己建立数据库和表达时候指定的。

client 的字符集则是每次连接的时候指定的。默认的是Latin1(就是这个很恶心)

 

【问题分析】

前面我们知道,mysql处理查询的时候先转换成相应的Collation规则,然后通过二进制编码进行比较。

查询失败、比较失败和乱码的根本原因就是 mysql处理查询时的转换。

而各个字符的大小来说:  latin1<gb2312<gbk<utf8

即大的字符转换成小弟字符不会丢失数据,而小的字符转换成大的字符则必然丢失数据。也就是为什么查询不出结果了。

通过敲入命令,查看mysql 的字符集状态。

mysql> SHOW VARIABLES LIKE 'collation_%';
+----------------------+-------------------+
| Variable_name        | Value             |
+----------------------+-------------------+
| collation_connection | latin1_swedish_ci |
| collation_database   | gb2312_chinese_ci |
| collation_server     | gb2312_chinese_ci |
+----------------------+-------------------+
3 rows in set (0.00 sec)

mysql> SHOW VARIABLES LIKE 'character_set_%'
    -> /g
+--------------------------+----------------------------------+
| Variable_name            | Value                            |

mysql> SHOW VARIABLES LIKE 'character_set_%';
+--------------------------+----------------------------------+
| Variable_name            | Value                            |
+--------------------------+----------------------------------+
| character_set_client     | latin1                           |
| character_set_connection | latin1                           |
| character_set_database   | utf8                             |
| character_set_filesystem | binary                           |
| character_set_results    | latin1                           |
| character_set_server     | utf8                             |
| character_set_system     | utf8                             |
| character_sets_dir       | /xampplite/mysql/share/charsets/ |
+--------------------------+----------------------------------+
8 rows in set (0.02 sec)
client 的字符集默认为 latin1set character_set_client = gb2312         //客户端使用gb2312格式
set character_set_connection = utf8      //连接字符集使用utf8格式
 set character_set_results = utf8            //结果集字符串。 
set collation_connection = utf8_generic_ci;    //
set collation_database    = utf8_general_ci;
set collation_database    = utf8_general_ci;

mysql 为每次会话都指定一组字符集,所以通过代码更改的只是这次的会话所使用的字符集。

下次重新打开一个mysql连接,依然回到 latin1。

或者开启会话的时候 指定默认字符集如:

mysql -u root -p --default_character_set= utf

虽然可以set global character_set_client = gb2312等设置为全局变量,但是在php程序里面每次连接数据库都是使用新的会话所以,依然使用的是Latin1,说白了,就是通过代码字符集不行,但是对于phpMyAdmin 这种持久会话的情况 则不同了。

【问题解决】

既然通过系统改client的字符集不行还是在程序里面指定查询使用的字符集。

mysql_query("set names 'utf8'");
set names 'utf8'相当于这三条语句:
set character_set_client = utf8;
set character_set_connection = utf8;
set character_set_results = utf8;

个人推荐使用下面的方式。

set character_set_client = gb2312;
set character_set_connection = utf8;
set character_set_results = utf8;

如果使用msyqli的形式连接mysql的:

$mysqli = new mysqli("localhost","root","pwd","LibSys");
$mysqli ->query("set character_set_client = gb2312");
$mysqli ->query("set character_set_connection = utf8");
$mysqli ->query("set character_set_results = utf8");
$rs = $mysqli ->query("select * from book where bookName = 'LPI Linux认证权威指南'");
if ($rs)
{
	$rows = $rs->fetch_array();
	echo  $rows['bookName'];
}

【有待完善】


应该可以更改 mysql 默认会话的指定字符集。但是还未找到。