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 相关文章推荐
PHP脚本的10个技巧(7)
Oct 09 PHP
php设置编码格式的方法
Mar 05 PHP
解析PHP中数组元素升序、降序以及重新排序的函数
Jun 20 PHP
php实现按指定大小等比缩放生成上传图片缩略图的方法
Dec 15 PHP
php生成圆角图片的方法
Apr 07 PHP
PHP读取汉字的点阵数据
Jun 22 PHP
PHP实现链式操作的原理详解
Sep 16 PHP
php获取当前月与上个月月初及月末时间戳的方法
Dec 05 PHP
PHP简单获取随机数的常用方法小结
Jun 07 PHP
CI框架(CodeIgniter)公共模型类定义与用法示例
Aug 10 PHP
laravel项目利用twemproxy部署redis集群的完整步骤
May 11 PHP
PHPExcel实现表格导出功能示例【带有多个工作sheet】
Jun 13 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
php高性能日志系统 seaslog 的安装与使用方法分析
2020/02/29 PHP
javascript 传统事件模型构造的事件监听器实现代码
2010/05/31 Javascript
javascript 延迟加载技术(lazyload)简单实现
2011/01/17 Javascript
JQuery获取各种宽度、高度(format函数)实例
2013/03/04 Javascript
网页前端优化之滚动延时加载图片示例
2013/07/13 Javascript
使用GruntJS构建Web程序之安装篇
2014/06/04 Javascript
滚动条响应鼠标滑轮事件实现上下滚动的js代码
2014/06/30 Javascript
浅析Node.js中的内存泄漏问题
2015/06/23 Javascript
使用Angular和Nodejs、socket.io搭建聊天室及多人聊天室
2015/08/21 NodeJs
jQuery实现商品活动倒计时
2015/10/16 Javascript
jQuery操作基本控件方法实例分析
2015/12/31 Javascript
Yarn的安装与使用详细介绍
2016/10/25 Javascript
解决vue页面DOM操作不生效的问题
2018/03/17 Javascript
微信小程序中的列表切换功能实例代码详解
2020/06/09 Javascript
JS实现选项卡插件的两种写法(jQuery和class)
2020/12/30 jQuery
[00:12]DAC2018 Miracle-站上中单舞台,他能否再写奇迹?
2018/04/06 DOTA
python中ConfigParse模块的用法
2014/09/29 Python
Python装饰器实现几类验证功能做法实例
2017/05/18 Python
python 对txt中每行内容进行批量替换的方法
2018/07/11 Python
Python注释、分支结构、循环结构、伪“选择结构”用法实例分析
2020/01/09 Python
pytorch实现建立自己的数据集(以mnist为例)
2020/01/18 Python
python爬虫模块URL管理器模块用法解析
2020/02/03 Python
Python新手学习装饰器
2020/06/04 Python
Marc Jacobs官方网站:美国奢侈品牌
2017/08/29 全球购物
DJI美国:消费类无人机领域的领导者
2018/04/27 全球购物
后勤人员自我鉴定
2013/10/20 职场文书
毕业生找工作的求职信范文
2013/12/24 职场文书
财务主管自我鉴定
2014/01/17 职场文书
树转促学习心得体会
2014/09/10 职场文书
项目负责人岗位职责
2015/02/15 职场文书
2015年共青团工作总结
2015/05/15 职场文书
离婚起诉书范本
2015/05/18 职场文书
申论不会写怎么办?教您掌握这6点思维和原则
2019/07/17 职场文书
祝福语集锦:给满月宝宝的祝福语
2019/11/20 职场文书
用Python爬取某乎手机APP数据
2021/06/15 Python
Python基础之条件语句详解
2021/06/16 Python