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 项目的方法
Jan 02 PHP
攻克CakePHP系列一 连接MySQL数据库
Oct 22 PHP
php set_magic_quotes_runtime() 函数过时解决方法
Jul 08 PHP
php array_map()数组函数使用说明
Jul 12 PHP
PHP代码判断设备是手机还是平板电脑(两种方法)
Oct 19 PHP
关于扩展 Laravel 默认 Session 中间件导致的 Session 写入失效问题分析
Jan 08 PHP
PHP使用PHPExcel删除Excel单元格指定列的方法
Jul 06 PHP
php 读取输出其他文件的实现方法
Jul 26 PHP
谈谈从phpinfo中能获取哪些值得注意的信息
Mar 28 PHP
PHP使用栈解决约瑟夫环问题算法示例
Aug 27 PHP
Laravel中七个非常有用但很少人知道的Carbon方法
Sep 21 PHP
PDO::rollBack讲解
Jan 29 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
在MongoDB中模拟Auto Increment的php代码
2011/03/06 PHP
适用于php-5.2 的 php.ini 中文版[金步国翻译]
2011/04/17 PHP
PHP Mysqli 常用代码集合
2016/11/12 PHP
php 调用ffmpeg获取视频信息的简单实现
2017/04/03 PHP
PHP的mysqli_stat()函数讲解
2019/01/23 PHP
Laravel 不同生产环境服务器的判断实践
2019/10/15 PHP
JavaScript中令你抓狂的魔术变量
2006/11/30 Javascript
jQuery前台数据获取实现代码
2011/03/16 Javascript
jquery自定义属性(类型/属性值)
2013/05/21 Javascript
jQuery中width()方法用法实例
2014/12/24 Javascript
微信中一些常用的js方法汇总
2015/03/12 Javascript
jQuery实现Meizu魅族官方网站的导航菜单效果
2015/09/14 Javascript
javascript如何创建对象
2016/08/29 Javascript
JS中使用mailto实现将用户在网页中输入的内容传递到本地邮件客户端
2016/10/08 Javascript
VUE JS 使用组件实现双向绑定的示例代码
2017/01/10 Javascript
AngularJS使用ng-inlude指令加载页面失败的原因与解决方法
2017/01/19 Javascript
在React中如何优雅的处理事件响应详解
2017/07/24 Javascript
p5.js入门教程和基本形状绘制
2018/03/15 Javascript
Vue使用json-server进行后端数据模拟功能
2018/04/17 Javascript
vue项目中使用百度地图的方法
2018/06/08 Javascript
NodeJS 实现多语言的示例代码
2018/09/11 NodeJs
微信小程序+腾讯地图开发实现路径规划绘制
2019/05/22 Javascript
Vue 一键清空表单的实现方法
2020/02/07 Javascript
解决vuex改变了state的值,但是页面没有更新的问题
2020/11/12 Javascript
Golang与python线程详解及简单实例
2017/04/27 Python
Python matplotlib 画图窗口显示到gui或者控制台的实例
2018/05/24 Python
python实现朴素贝叶斯算法
2018/11/19 Python
Python列表切片操作实例总结
2019/02/19 Python
python实现beta分布概率密度函数的方法
2019/07/08 Python
对Python 中矩阵或者数组相减的法则详解
2019/08/26 Python
对tensorflow中cifar-10文档的Read操作详解
2020/02/10 Python
python实现门限回归方式
2020/02/29 Python
django model object序列化实例
2020/03/13 Python
python代码实现猜拳小游戏
2020/11/30 Python
欧舒丹俄罗斯官方网站:L’OCCITANE俄罗斯
2019/11/22 全球购物
毕业实习指导教师评语
2014/12/31 职场文书