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新手上路(十四)
Oct 09 PHP
PHP 超链接 抓取实现代码
Jun 29 PHP
PHP set_time_limit(0)长连接的实现分析
Mar 02 PHP
php木马webshell扫描器代码
Jan 25 PHP
laravel 4安装及入门图文教程
Oct 29 PHP
php使用指定编码导出mysql数据到csv文件的方法
Mar 31 PHP
PHPExcel简单读取excel文件示例
May 26 PHP
php检查函数必传参数是否存在的实例详解
Aug 28 PHP
php微信开发之关注事件
Jun 14 PHP
php获取是星期几的的一些常用姿势
Dec 15 PHP
PHP 加密 Password Hashing API基础知识点
Mar 02 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求圆周率的简单实现方法
2016/05/30 PHP
yii2.0整合阿里云oss删除单个文件的方法
2017/09/19 PHP
setAttribute 与 class冲突解决
2008/02/17 Javascript
DOM 脚本编程中的兄弟节点
2009/10/31 Javascript
FileUpload上传图片(图片不变形)
2010/08/05 Javascript
JQuery中关于jquery.js与jquery.min.js的比较探讨
2013/05/15 Javascript
javascript自动改变文字大小和颜色的效果的小例子
2013/08/02 Javascript
javascript操作referer详细解析
2014/03/10 Javascript
js跨域问题浅析及解决方法优缺点对比
2014/11/08 Javascript
javascript中eval函数用法分析
2015/04/25 Javascript
详解JS正则replace的使用方法
2016/03/06 Javascript
BootStrap下拉菜单和滚动监听插件实现代码
2016/09/26 Javascript
详解Jquery的事件操作和文档操作
2016/12/19 Javascript
微信小程序 基础组件与导航组件详细介绍
2017/02/21 Javascript
使用jquery datatable和bootsrap创建表格实例代码
2017/03/17 Javascript
如何通过非数字与字符的方式实现PHP WebShell详解
2017/07/02 Javascript
JS实现的按钮点击颜色切换功能示例
2017/10/19 Javascript
webpack-dev-server自动更新页面方法
2018/02/22 Javascript
JavaScript高级函数应用之分时函数实例分析
2018/08/03 Javascript
python的Template使用指南
2014/09/11 Python
Python使用django获取用户IP地址的方法
2015/05/11 Python
Python中的异常处理相关语句基础学习笔记
2016/07/11 Python
python实现发送form-data数据的方法详解
2019/09/27 Python
Python 实现训练集、测试集随机划分
2020/01/08 Python
HTML5 文件域+FileReader 分段读取文件并上传到服务器
2017/10/23 HTML / CSS
智利最大的网上商店:Linio智利
2016/11/24 全球购物
世界顶级足球门票网站:Live Football Tickets
2017/10/14 全球购物
马来西亚网上美容店:Hermo.my
2017/11/25 全球购物
Joseph官网:英国小众奢侈品牌
2019/05/17 全球购物
应用电子技术专业个人求职信
2013/09/21 职场文书
小溪流的歌教学反思
2014/02/13 职场文书
党员干部群众路线个人整改措施
2014/09/18 职场文书
2015年勤工助学工作总结
2015/04/29 职场文书
2015年党员个人工作总结
2015/05/13 职场文书
幼儿教师继续教育培训心得体会
2016/01/19 职场文书
python实现会员信息管理系统(List)
2022/03/18 Python