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中使用ExcelFileParser处理excel获得数据(可作批量导入到数据库使用)
Aug 21 PHP
PHP类与对象中的private访问控制的疑问
Nov 01 PHP
PHP中判断变量为空的几种方法小结
Nov 12 PHP
php中的路径问题与set_include_path使用介绍
Feb 11 PHP
一个显示效果非常不错的PHP错误、异常处理类
Mar 21 PHP
php使用Cookie控制访问授权的方法
Jan 21 PHP
php自动提交表单的方法(基于fsockopen与curl)
May 09 PHP
PHP进行批量任务处理不超时的解决方法
Jul 11 PHP
php禁用函数设置及查看方法详解
Jul 25 PHP
PHP 二维关联数组根据其中一个字段排序(推荐)
Apr 04 PHP
利用phpexcel对数据库数据的导入excel(excel筛选)、导出excel
Apr 27 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 数组二分法查找函数代码
2010/02/16 PHP
phpadmin如何导入导出大数据文件及php.ini参数修改
2013/02/18 PHP
深入PHP运行环境配置的详解
2013/06/04 PHP
php socket通信(tcp/udp)实例分析
2016/02/14 PHP
php array_values 返回数组的值实例详解
2016/11/17 PHP
关于laravel 数据库迁移中integer类型是无法指定长度的问题
2019/10/09 PHP
PHP实现限制域名访问的实现代码(本地验证)
2020/09/13 PHP
Js组件的一些写法
2010/09/10 Javascript
js调用webservice中的方法实现思路及代码
2013/02/25 Javascript
location.href用法总结(最主要的)
2013/12/27 Javascript
jQuery中Dom的基本操作小结
2014/01/23 Javascript
JS实现的在线调色板实例(附demo源码下载)
2016/03/01 Javascript
jQuery实现鼠标经过购物车出现下拉框代码(推荐)
2016/07/21 Javascript
JavaScript-html标题滚动效果的简单实现
2016/09/08 Javascript
ES6中箭头函数的定义与调用方式详解
2017/06/02 Javascript
解决Layui数据表格显示无数据提示的问题
2019/11/14 Javascript
[49:40]2018DOTA2亚洲邀请赛小组赛 A组加赛 TNC vs Newbee
2018/04/03 DOTA
django 发送邮件和缓存的实现代码
2018/07/18 Python
Python查找数组中数值和下标相等的元素示例【二分查找】
2019/02/13 Python
Python画图高斯分布的示例
2019/07/10 Python
python实现人工智能Ai抠图功能
2019/09/05 Python
Python range、enumerate和zip函数用法详解
2019/09/11 Python
简单了解python中的与或非运算
2019/09/18 Python
Python绘制二维曲线的日常应用详解
2019/12/04 Python
python matplotlib模块基本图形绘制方法小结【直线,曲线,直方图,饼图等】
2020/04/26 Python
浅谈keras中自定义二分类任务评价指标metrics的方法以及代码
2020/06/11 Python
Beach Bunny Swimwear官网:设计师泳装和性感比基尼
2019/03/13 全球购物
后勤自我鉴定
2013/10/13 职场文书
自考自我鉴定范文
2013/10/30 职场文书
应届生学校辅导员求职信
2013/11/07 职场文书
图书室管理制度
2014/01/19 职场文书
2015年销售助理工作总结
2015/05/11 职场文书
2015年国培研修感言
2015/08/01 职场文书
中国梦党课学习心得体会
2016/01/05 职场文书
一文帮你理解PReact10.5.13源码
2021/04/03 Javascript
利用Python+OpenCV三步去除水印
2021/05/28 Python