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的类 功能齐全的发送邮件类
Oct 09 PHP
php中常用编辑器推荐
Jan 02 PHP
PHP 的异常处理、错误的抛出及回调函数等面向对象的错误处理方法
Dec 07 PHP
PHP中把stdClass Object转array的几个方法
May 08 PHP
PHP批量去除BOM头代码分享
Jun 26 PHP
PHP网站建设的流程与步骤分享
Sep 25 PHP
Zend Framework教程之Zend_Config_Xml用法分析
Mar 23 PHP
PHP中如何判断exec函数执行成功?
Aug 04 PHP
PHP简单计算两个时间差的方法示例
Jun 20 PHP
Laravel学习教程之本地化模块
Aug 18 PHP
Thinkphp5+uploadify实现的文件上传功能示例
May 26 PHP
Laravel框架基于ajax和layer.js实现无刷新删除功能示例
Jan 17 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
phpmyadmin 3.4 空密码登录的实现方法
2010/05/29 PHP
PHP冒泡排序算法代码详细解读
2011/07/17 PHP
php生成随机密码自定义函数代码(简单快速)
2014/05/10 PHP
PHP实现远程下载文件到本地
2015/05/17 PHP
Laravel接收前端ajax传来的数据的实例代码
2017/07/20 PHP
PHP生成随机数的方法总结
2018/03/01 PHP
jQuery学习笔记之jQuery的动画
2010/12/22 Javascript
用js来定义浏览器中一个左右浮动元素相对于页面主体宽度的位置的函数
2012/01/21 Javascript
JS获取下拉列表所选中的TEXT和Value的实现代码
2014/01/11 Javascript
使用vue编写一个点击数字计时小游戏
2016/08/31 Javascript
JS实现自动阅读单词(有道单词本添加功能)
2016/11/14 Javascript
AngularJS实现表单元素值绑定操作示例
2017/10/11 Javascript
解决vue build打包之后首页白屏的问题
2018/03/06 Javascript
使用Sonarqube扫描Javascript代码的示例
2018/12/26 Javascript
微信小程序实现获取准确的腾讯定位地址功能示例
2019/03/27 Javascript
基于Vue el-autocomplete 实现类似百度搜索框功能
2019/10/25 Javascript
JavaScript监听键盘事件代码实现
2020/06/03 Javascript
Vue axios 跨域请求无法带上cookie的解决
2020/09/08 Javascript
JavaScript大数相加相乘的实现方法实例
2020/10/18 Javascript
[00:13]天涯墨客二技能展示
2018/08/25 DOTA
如何用itertools解决无序排列组合的问题
2017/05/18 Python
Python Cookie 读取和保存方法
2018/12/28 Python
python查询文件夹下excel的sheet名代码实例
2019/04/02 Python
python实现两个一维列表合并成一个二维列表
2019/12/02 Python
Python3.9新特性详解
2020/10/10 Python
加拿大知名的国际儿童品牌:Hatley
2016/11/09 全球购物
麦当劳印度网上订餐:McDelivery
2020/03/16 全球购物
大学生活学习的自我评价
2013/12/03 职场文书
西式婚礼主持词
2014/03/13 职场文书
家长学校实施方案
2014/03/15 职场文书
爱祖国演讲稿
2014/05/04 职场文书
2014七年级班主任工作总结
2014/12/05 职场文书
2014高三学生考试作弊检讨书
2014/12/14 职场文书
2015年环境整治工作总结
2015/05/22 职场文书
个人工作失误的保证书怎么写?
2019/06/21 职场文书
导游词之唐山景点
2019/12/18 职场文书