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 文件上传代码(限制jpg文件)
Jan 05 PHP
php下通过伪造http头破解防盗链的代码
Jul 03 PHP
让PHP以ROOT权限执行系统命令的方法
Feb 10 PHP
PHP源代码数组统计count分析
Aug 02 PHP
PHP生成不重复标识符的方法
Nov 21 PHP
yii实现CheckBox复选框在同一行显示的方法
Dec 03 PHP
分享下php5类中三种数据类型的区别
Jan 26 PHP
php使用json_decode后数字对象转换成了科学计数法的解决方法
Feb 20 PHP
PHP实现创建微信自定义菜单的方法示例
Jul 14 PHP
PHP 的Opcache加速的使用方法
Dec 29 PHP
laravel 解决路由除了根目录其他都404的问题
Oct 18 PHP
PHP程序员简单的开展服务治理架构操作详解(一)
May 14 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
《心理测量者3》剧场版动画预告
2020/03/02 日漫
php中AES加密解密的例子小结
2014/02/18 PHP
PHP获取页面执行时间的方法(推荐)
2016/12/10 PHP
PHP实现深度优先搜索算法(DFS,Depth First Search)详解
2017/09/16 PHP
JavaScript开发时的五个注意事项
2007/12/08 Javascript
Js 弹出框口并返回值的两种常用方法
2010/12/30 Javascript
js隐藏与显示回到顶部按钮及window.onscroll事件应用
2013/01/25 Javascript
表格奇偶行设置不同颜色的核心JS代码
2013/12/24 Javascript
jQuery中detach()方法用法实例
2014/12/25 Javascript
javascript实现checkbox复选框实例代码
2016/01/10 Javascript
深入理解JavaScript程序中内存泄漏
2016/03/17 Javascript
BootStrap3中模态对话框的使用
2017/01/06 Javascript
js实现三级联动效果(简单易懂)
2017/03/27 Javascript
JS实现AES加密并与PHP互通的方法分析
2017/04/19 Javascript
Vue.js实现一个todo-list的上移下移删除功能
2017/06/26 Javascript
微信小程序出现wx.getLocation再次授权问题的解决方法分析
2019/01/16 Javascript
wx-charts 微信小程序图表插件的具体使用
2019/08/18 Javascript
javascript实现获取中文汉字拼音首字母
2020/05/19 Javascript
[01:00:52]2018DOTA2亚洲邀请赛 4.4 淘汰赛 EG vs LGD 第一场
2018/04/05 DOTA
浅谈python 四种数值类型(int,long,float,complex)
2016/06/08 Python
Python连接DB2数据库
2016/08/27 Python
修改默认的pip版本为对应python2.7的方法
2018/11/06 Python
python 多线程将大文件分开下载后在合并的实例
2018/11/09 Python
Python标准库使用OrderedDict类的实例讲解
2019/02/14 Python
Python selenium根据class定位页面元素的方法
2019/02/26 Python
Tensorflow不支持AVX2指令集的解决方法
2020/02/03 Python
解析python 类方法、对象方法、静态方法
2020/08/15 Python
MANGO官方网站:西班牙芒果服装品牌
2017/01/15 全球购物
加拿大的标志性百货公司:Hudson’s Bay(哈得逊湾)
2019/09/03 全球购物
教师岗位聘任书范文
2014/03/29 职场文书
政工例会汇报材料
2014/08/26 职场文书
2014领导干部学习焦裕禄同志先进事迹思想汇报
2014/09/19 职场文书
靠谱准确的求职信
2019/04/02 职场文书
如何利用map实现Nginx允许多个域名跨域
2021/03/31 Servers
MySQL悲观锁与乐观锁的实现方案
2021/11/02 MySQL
PostgreSQL基于pgrouting的路径规划处理方法
2022/04/18 PostgreSQL