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版(4)
Oct 09 PHP
初级的用php写的采集程序
Mar 16 PHP
PHP的几个常用数字判断函数代码
Apr 24 PHP
解决php接收shell返回的结果中文乱码问题
Jan 23 PHP
PHP中的常见魔术方法功能作用及用法实例
Jul 01 PHP
PHP生成树的方法
Jul 28 PHP
php构造方法中析构方法在继承中的表现
Apr 12 PHP
CodeIgniter 完美解决URL含有中文字符串
May 13 PHP
PHP+Ajax实现验证码的实时验证
Jul 20 PHP
ThinkPHP 在阿里云上的nginx.config配置实例详解
Oct 11 PHP
php中输出json对象的值(实现方法)
Mar 07 PHP
php定期拉取数据对比方法实例
Sep 22 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实现快速排序法函数代码
2012/08/27 PHP
php获取excel文件数据
2017/04/21 PHP
使用PHP+MySql实现微信投票功能实例代码
2017/09/29 PHP
JavaScript 全面解析各种浏览器网页中的JS 执行顺序
2009/02/17 Javascript
jquery select(列表)的操作(取值/赋值)
2009/08/06 Javascript
非常强大的 jQuery.AsyncBox 弹出对话框插件
2011/08/29 Javascript
Kibo 用于处理键盘事件的Javascript工具库
2011/10/28 Javascript
node.js中的fs.appendFileSync方法使用说明
2014/12/17 Javascript
JavaScript为事件句柄绑定监听函数实例详解
2015/12/15 Javascript
浅析JavaScript中的对象类型Object
2016/05/26 Javascript
js重写方法的简单实现
2016/07/10 Javascript
AngularJS基础 ng-show 指令简单示例
2016/08/03 Javascript
简单实现js倒计时功能
2017/02/13 Javascript
浅谈node的事件机制
2017/10/09 Javascript
JavaScript伪数组用法实例分析
2017/12/22 Javascript
vue一个页面实现音乐播放器的示例
2018/02/06 Javascript
基于vue2.0动态组件及render详解
2018/03/17 Javascript
JavaScript设计模式之构造器模式(生成器模式)定义与用法实例分析
2018/07/26 Javascript
elementUI 动态生成几行几列的方法示例
2019/07/11 Javascript
微信小程序订阅消息(java后端实现)开发
2020/06/01 Javascript
vue3.0封装轮播图组件的步骤
2021/03/04 Vue.js
[01:45]亚洲邀请赛互动指南虚拟物品介绍
2015/01/30 DOTA
[49:18]2018DOTA2亚洲邀请赛 3.31 小组赛 A组 OG vs TNC
2018/04/01 DOTA
python模拟登录百度代码分享(获取百度贴吧等级)
2013/12/27 Python
使用go和python递归删除.ds store文件的方法
2014/01/22 Python
浅谈python jieba分词模块的基本用法
2017/11/09 Python
Python+OpenCV感兴趣区域ROI提取方法
2019/01/10 Python
浅析Python与Mongodb数据库之间的操作方法
2019/07/01 Python
这可能是最好玩的python GUI入门实例(推荐)
2019/07/19 Python
PyTorch之nn.ReLU与F.ReLU的区别介绍
2020/06/27 Python
三星印度官网:Samsung印度
2019/08/03 全球购物
Strathberry苏贝瑞中国官网:西班牙高级工匠手工打造
2020/10/19 全球购物
物流管理专业职业生涯规划书
2014/01/06 职场文书
副乡长群众路线教育实践活动个人对照检查材料
2014/09/19 职场文书
党员学习群众路线教育实践活动对照检查材料
2014/09/23 职场文书
JVM的类加载器和双亲委派模式你了解吗
2022/03/13 Java/Android