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 模板高级篇总结
Dec 21 PHP
PHP curl_setopt()函数实例代码与参数分析
Jun 02 PHP
用Json实现PHP与JavaScript间数据交换的方法详解
Jun 20 PHP
PHP输出当前进程所有变量/常量/模块/函数/类的示例
Nov 07 PHP
在html文件中也可以执行php语句的方法
Apr 09 PHP
php使用COPY函数更新配置文件的方法
Jun 18 PHP
PHP生成条形码大揭秘
Sep 24 PHP
PHP的PDO操作简单示例
Mar 30 PHP
ThinkPHP框架里隐藏index.php
Apr 12 PHP
详解PHP用substr函数截取字符串中的某部分
Dec 03 PHP
PHP实现基于PDO扩展连接PostgreSQL对象关系数据库示例
Mar 31 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实现网站插件机制的方法
2009/11/10 PHP
php自定义函数之递归删除文件及目录
2010/08/08 PHP
PHP中删除变量时unset()和null的区别分析
2011/01/27 PHP
php连接Access数据库错误及解决方法
2013/06/20 PHP
ThinkPHP中I(),U(),$this-&gt;post()等函数用法
2014/11/22 PHP
最近项目写了一些js,水平有待提高
2009/01/31 Javascript
一个简单的JavaScript 日期计算算法
2009/09/11 Javascript
Extjs4.0设置Ext.data.Store传参的请求方式(默认为GET)
2013/04/02 Javascript
js 时间格式与时间戳的相互转换示例代码
2013/12/25 Javascript
一个JavaScript处理textarea中的字符成每一行实例
2014/09/22 Javascript
JS转换HTML转义符的方法
2016/08/24 Javascript
Vue方法与事件处理器详解
2016/12/01 Javascript
Vue下路由History模式打包后页面空白的解决方法
2018/06/29 Javascript
sortable+element 实现表格行拖拽的方法示例
2019/06/07 Javascript
使用vue-router在Vue页面之间传递数据的方法
2019/07/15 Javascript
原生js基于canvas实现一个简单的前端截图工具代码实例
2019/09/10 Javascript
详解Vue+elementUI build打包部署后字体图标丢失问题
2020/07/13 Javascript
vant-ui AddressEdit地址编辑和van-area的用法说明
2020/11/03 Javascript
vue中配置scss全局变量的步骤
2020/12/28 Vue.js
pycharm 配置远程解释器的方法
2018/10/28 Python
python3.6 tkinter实现屏保小程序
2019/07/30 Python
python获取Pandas列名的几种方法
2019/08/07 Python
Python Web项目Cherrypy使用方法镜像
2020/11/05 Python
python线程优先级队列知识点总结
2021/02/28 Python
CSS3实现超酷的黑猫警长首页
2016/04/26 HTML / CSS
详解webapp页面滚动卡顿的解决办法
2018/12/26 HTML / CSS
Kathmandu澳洲户外商店:新西兰户外运动品牌
2017/11/12 全球购物
美国婴儿和儿童家具网上商店:ABaby.com
2018/07/02 全球购物
澳大利亚100%丝绸多彩度假装商店:TheSwankStore
2019/09/04 全球购物
采用怎样的方法保证数据的完整性
2013/12/02 面试题
90后毕业生的求职信范文
2013/09/21 职场文书
计算机专业毕业生自荐信
2013/12/31 职场文书
公益广告宣传方案
2014/02/28 职场文书
材料员岗位职责
2015/02/10 职场文书
《爬天都峰》教学反思
2016/02/23 职场文书
导游词之日本富士山
2020/01/06 职场文书