mysqli_set_charset和SET NAMES使用抉择及优劣分析


Posted in PHP onJanuary 13, 2013

最近公司组织了个PHP安全编程的培训, 其中涉及到一部分关于Mysql的”SET NAMES”和mysql_set_charset (mysqli_set_charset)的内容:
说到, 尽量使用mysqli_set_charset(mysqli:set_charset)而不是”SET NAMES”, 当然, 这个内容在PHP手册中也有叙及, 但是却没有解释为什么.

最近有好几个朋友问我这个问题, 到底为什么?
问的人多了, 我也就觉得可以写篇blog, 专门介绍下这部分的内容了.
首先, 很多人都不知道”SET NAMES”到底是做了什么,
我之前的文章深入MySQL字符集设置中, 曾经介绍过character_set_client/character_set_connection/character_set_results这三个MySQL的”环境变量”, 这里再简单介绍下,
这三个变量, 分别告诉MySQL服务器, 客户端的编码集, 在传输给MySQL服务器的时候的编码集, 以及期望MySQL返回的结果的编码集.
比如, 通过使用”SET NAMES utf8″, 就告诉服务器, 我用的是utf-8编码, 我希望你也给我返回utf-8编码的查询结果.

一般情况下, 使用”SET NAMES”就足够了, 也是可以保证正确的. 那么为什么手册又要说推荐使用mysqli_set_charset(PHP>=5.0.5)呢?
首先, 我们看看mysqli_set_charset到底做了什么(注意星号注释处, mysql_set_charset类似):

//php-5.2.11-SRC/ext/mysqli/mysqli_nonapi.c line 342 
PHP_FUNCTION(mysqli_set_charset) 
{ 
MY_MYSQL*mysql; 
zval*mysql_link; 
char *cs_name = NULL; 
unsigned int len; 
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis() 
, "Os", &mysql_link, mysqli_link_class_entry, &cs_name, &len) == FAILURE) { 
return; 
} 
MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL*, &mysql_link, "mysqli_link" 
, MYSQLI_STATUS_VALID); 
if (mysql_set_character_set(mysql->mysql, cs_name)) { 
//** 调用libmysql的对应函数 
RETURN_FALSE; 
} 
RETURN_TRUE; 
}

那mysql_set_character_set又做了什么呢?
//mysql-5.1.30-SRC/libmysql/client.c, line 3166: 
int STDCALLmysql_set_character_set(MYSQL*mysql, const char *cs_name) 
{ 
structcharset_info_st *cs; 
const char *save_csdir= charsets_dir; 
if (mysql->options.charset_dir) 
charsets_dir= mysql->options.charset_dir; 
if (strlen(cs_name) < MY_CS_NAME_SIZE && 
(cs= get_charset_by_csname(cs_name, MY_CS_PRIMARY, MYF(0)))) 
{ 
char buff[MY_CS_NAME_SIZE + 10]; 
charsets_dir= save_csdir; 
/* Skip execution of "SET NAMES" for pre-4.1 servers */ 
if (mysql_get_server_version(mysql) < 40100) 
return 0; 
sprintf(buff, "SET NAMES %s", cs_name); 
if (!mysql_real_query(mysql, buff, strlen(buff))) 
{ 
mysql->charset= cs; 
} 
} 
//以下省略

我们可以看到, mysqli_set_charset除了做了”SET NAMES”以外, 还多做了一步:
sprintf(buff, "SET NAMES %s", cs_name); 
if (!mysql_real_query(mysql, buff, strlen(buff))) 
{ 
mysql->charset= cs; 
}

而对于mysql这个核心结构的成员charset又有什么作用呢?
这就要说说mysql_real_escape_string()了, 这个函数和mysql_escape_string的区别就是, 它会考虑”当前”字符集. 那么这个当前字符集从哪里来呢?
对了, 你猜的没错, 就是mysql->charset.
mysql_real_string在判断宽字符集的字符的时候, 就根据这个成员变量来分别采用不同的策略, 比如如果是utf-8, 那么就会采用libmysql/ctype-utf8.c.
看个实例, 默认mysql连接字符集是latin-1, (经典的5c问题):
<?php 
$db = mysql_connect('localhost:3737', 'root' ,'123456'); 
mysql_select_db("test"); 
$a = "\x91\x5c";//"?\"的gbk编码, 低字节为5c, 也就是ascii中的"\" 
var_dump(addslashes($a)); 
var_dump(mysql_real_escape_string($a, $db)); 
mysql_query("set names gbk"); 
var_dump(mysql_real_escape_string($a, $db)); 
mysql_set_charset("gbk"); 
var_dump(mysql_real_escape_string($a, $db)); 
?>

因为, “?\”的gbk编码低字节为5c, 也就是ascii中的”\”, 而因为除了mysql(i)_set_charset影响mysql->charset以外, 其他时刻mysql->charset都为默认值, 所以, 结果就是:
$ php -f 5c.php 
string(3) "?\\" 
string(3) "?\\" 
string(3) "?\\" 
string(2) "?\"大家现在很清楚了吧?
PHP 相关文章推荐
杏林同学录(三)
Oct 09 PHP
php面向对象全攻略 (九)访问类型
Sep 30 PHP
Php output buffering缓存及程序缓存深入解析
Jul 15 PHP
CodeIgniter框架URL路由总结
Sep 03 PHP
让ThinkPHP支持大小写url地址访问的方法
Oct 31 PHP
php通过function_exists检测函数是否存在的方法
Mar 18 PHP
PHP+MYSQL中文乱码问题
Jul 01 PHP
PHP简单操作MongoDB的方法(安装及增删改查)
May 26 PHP
PHP在线打包下载功能示例
Oct 15 PHP
PHP实现多关键字加亮功能
Oct 21 PHP
php实现的中文分词类完整实例
Feb 06 PHP
在Laravel中实现使用AJAX动态刷新部分页面
Oct 15 PHP
PHP读取xml方法介绍
Jan 12 #PHP
用PHP编写和读取XML的几种方式
Jan 12 #PHP
php图片的裁剪与缩放生成符合需求的缩略图
Jan 11 #PHP
浏览器预览PHP文件时顶部出现空白影响布局分析原因及解决办法
Jan 11 #PHP
php判断上传的Excel文件中是否有图片及PHPExcel库认识
Jan 11 #PHP
PHP中header和session_start前不能有输出原因分析
Jan 11 #PHP
PHP跨时区(UTC时间)应用解决方案
Jan 11 #PHP
You might like
饭制《星际争霸》Mod:优化游戏机制 增加新单位
2017/07/02 星际争霸
《PHP边学边教》(02.Apache+PHP环境配置――下篇)
2006/12/13 PHP
PHP 图片文件上传实现代码
2010/12/29 PHP
全面解读PHP的Yii框架中的日志功能
2016/03/17 PHP
thinkphp关于简单的权限判定方法
2017/04/03 PHP
Symfony2针对输入时间进行查询的方法分析
2017/06/28 PHP
通过event对象的fromElement属性解决热区设置主实体的一个bug
2008/12/22 Javascript
js输入框邮箱自动提示功能代码实现
2013/12/10 Javascript
jQuery调用RESTful WCF示例代码(GET方法/POST方法)
2014/01/26 Javascript
js实现鼠标经过时图片滚动停止的方法
2015/02/16 Javascript
ECMAScript6函数剩余参数(Rest Parameters)
2015/06/12 Javascript
jquery自定义表格样式
2015/11/23 Javascript
jQuery实现导航回弹效果
2017/02/27 Javascript
View.post() 不靠谱的地方你知道多少
2017/08/29 Javascript
Vue2 轮播图slide组件实例代码
2018/05/31 Javascript
Vue-cli3项目配置Vue.config.js实战记录
2018/07/29 Javascript
nodejs实现获取本地文件夹下图片信息功能示例
2019/06/22 NodeJs
element 动态合并表格的步骤
2020/12/31 Javascript
[02:14]DOTA2英雄基础教程 修补匠
2013/12/23 DOTA
Django框架中数据的连锁查询和限制返回数据的方法
2015/07/17 Python
在主机商的共享服务器上部署Django站点的方法
2015/07/22 Python
Windows中安装使用Virtualenv来创建独立Python环境
2016/05/31 Python
Django 前后台的数据传递的方法
2017/08/08 Python
Python绘制七段数码管实例代码
2017/12/20 Python
python 实现ping测试延迟的两种方法
2020/12/10 Python
linux面试题参考答案(10)
2016/10/26 面试题
药品促销活动方案
2014/02/14 职场文书
超市商业计划书
2014/05/04 职场文书
医学求职自荐信
2014/06/21 职场文书
班子查摆四风个人对照检查材料思想汇报
2014/10/04 职场文书
工会工作个人总结
2015/03/03 职场文书
教师调动申请报告
2015/05/18 职场文书
律政俏佳人观后感
2015/06/09 职场文书
小学语文课《掌声》教学反思
2016/03/03 职场文书
关于办理居住证的介绍信模板
2019/11/27 职场文书
导游词之日本富士山
2020/01/06 职场文书