最近公司組織了個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?STDCALL?mysql_set_character_set(MYSQL?*mysql,?const?char?*cs_name) ?{? ??struct?charset_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.phpstring(3) "慭\"string(3) "慭\"string(3) "慭\"string(2) "慭 大家現在很清楚了吧?
轉載于:https://blog.51cto.com/jiedushi/482071
總結
以上是生活随笔為你收集整理的深入理解SET NAMES和mysql(i)_set_charset的区别的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。