每一個可以努力的日子,都是一份厚禮。
【暴走漫畫】坑爹的UTF8和UTF-8網頁編碼
一、遇到的問題
曾經被字符集間複雜的轉換搞怕了,正好新項目要求國際化,需要能夠顯示多種語言,於是一開始就規定統統使用 UTF-8 編碼。
- 所有代碼文件使用 UTF-8 編碼存盤
- MySQL數據庫所有表,所有字段設置 Collation (中文翻譯為“整理”?)屬性為 “utf8_general_ci”
- 所有頁面輸出
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
即便是這樣,PHP 從數據庫中讀取內容,顯示到網頁上,還是出現了亂碼,英文沒問題,中文統統都是?問號。這樣也行?艱苦卓絕的 debug 開始了……
二、調查原因
MySQL 的字符集以繁多而著名,而其默認又是 latin1 的瑞典語編碼,數據導入導出的時候一不留神就亂碼了。
參考以上鏈接的官方文檔,總結之:MySQL 對於字符集的支持細化到四個層次:
- server 服務器級
- database 數據庫級
- table 表級
- connection 連接級
確保每一個級別都是使用的 UTF-8 編碼。檢查了一下,貌似我沒有設置 connection 連接級。前三種字符集級別只是規定了數據存儲在 MySQL 中的編碼格式,客戶端讀出數據後完全可以按照自己的意願來解讀數據。最後的 connection 連接級就是規定了客戶端以什麼編碼來解析讀取到的數據。也就是說,不論是 php 代碼還是 DB 管理軟件,在從 MySQL 讀取數據之前都需要設定自己作為客戶端的編碼格式。
好吧,那麼,在任何查詢執行之前,先執行一句 set names utf-8。(使用框架進行開發的話,大多數框架應該會自動完成這一步,程序員一般只需要改配置文件)
$conn = mysql_connect($db_host, $db_user, $db_password); if(!$conn) die("Could not connect to mysql."); mysql_select_db($db_name); mysql_query("set names 'utf-8'"); |
刷新頁面,仍舊亂碼。
三、解決
只好再繼續調查。¥#$…&%=^*&+%-#!@_@ 苦逼地一天就這樣過去了……
咦,等等,官方文檔里寫的是 set names utf8 哦,和 set names utf-8 有啥區別么?趕緊試一下。
刷新頁面,我擦,正常了。
搜了一下,發現被坑的人還真不少。UTF-8應該是標準的寫法,在大多數場合都是有中間那個橫杠的,只是MySQL這裡偏偏就非主流去掉了橫杠使用UTF8。
遇到同樣問題,而本文未能幫你解決的,這篇亂碼總結可能會幫到你。
這篇文章由lovelucy於2012-01-02 22:48發表在編程。你可以訂閱RSS 2.0 也可以發表評論或引用到你的網站。除特殊說明外文章均為本人原創,並遵從署名-非商業性使用-相同方式共享創作協議,轉載或使用請註明作者和來源,尊重知識分享。 |
批評不自由
則讚美無意義
Google Chrome 8.0.552.224 Windows 7 大約12年前
Collation 譯為 “排序規則” 。
Google Chrome 18.0.1025.168 Ubuntu Linux 大約12年前
沒試過,不知道是不是因為 utf-8 不符合普通標識符規定的原因(哈,不了解,猜的
Google Chrome 15.0.874.120 Windows 7 大約12年前
應該使用 mysql_ set_ charset(); 不要使用sql query來設置,有風險。
Google Chrome 17.0.963.56 Windows 7 大約12年前
多謝指點,我查到 PHP 手冊上也這麼說了
但是為何有風險?願聞其詳
Google Chrome 17.0.963.56 Windows 7 大約12年前
http://webcache.googleusercontent.com/search?q=cache:SrXTiWGT1qcJ:www.mirecle.com/2010/04/13/php-in-the-set-names-and-mysql_set_charset.html+mysql_set_charset&cd=2&hl=zh-TW&ct=clnk&gl=hk&lr=lang_en%7Clang_zh-CN%7Clang_zh-TW
隨便搜了下,搜到前同事的blog了- -.
Google Chrome 19.0.1049.3 Windows 7 大約12年前
這就是 Goolge 的社會化搜索,我是搜不到那個網頁的。你應該是訂閱了他的blog
Google Chrome 15.0.874.120 Windows 7 大約12年前
囧- -。
今天看到大家在討論,發現這是個很嚴重而又容易疏忽的問題,我以前也一直是用set names,遂記錄下來,也提醒自己一把。
1.set names與mysql_set_charset有什麼區別?
一般情況下, 使用”SET NAMES”就足夠了, 也是可以保證正確的. 那麼為什麼手冊又要說推薦使用 mysqli_set_charset(PHP>=5.0.5)呢。手冊裡面也沒有明確說明。我們可以看下php擴展的源代碼:
//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;
}
可以看到php的mysql擴展是直接調用了mysql的mysql_set_character_set函數,接下來看看mysql的代碼
//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) charset= cs;
}
}
//以下省略
可以看到,除了調用real_query設置set names,還設置了mysql的charset變量。
2.這樣有什麼影響?
mysql_real_escape_string會受到影響,它與mysql_escape_string的區別就 是, 它會考慮”當前”字符集。如果僅僅使用set names,mysql_real_escape_string可能會失效。
例子:
$mysqli = new mysqli(“localhost”, “user”, “pass”, “test”, 3306);
/* check connection */
if (mysqli_connect_errno()) {
printf(“Connect failed: %s\n”, mysqli_connect_error());
exit();
}
$mysqli->query(‘SET NAMES gbk’); //使用set names設置字符集
$city = chr(0xbf).chr(0x5c); //0xbf5c是個有效的gbk字符,模擬用戶輸入
$city = $mysqli->real_escape_string ($city);//使用real_escape進行過濾
/* this query will fail, cause we didn’t escape $city */
if (!$mysqli->query(“INSERT into myCity(name) VALUES (‘$city’)”)) {
print “INSERT into myCity (name) VALUES (‘$city’)\n”;
printf(“Error: %s\n”, $mysqli->error);
}
var_dump($city);
var_dump($mysqli->client_encoding());
$mysqli->close();
3.解決方案
mysqli_set_charset函數對PHP和Mysql有版本要求,必須當mysql版本大於5,PHP版本大於5.0.5時,此函數才有效。至於另一個mysql_set_charset函數,則更要求PHP版本大於5.2.3時才能有效。對於mysql4.1以上版本,使用”SET character_set_client=binary;”
推薦使用mysql_set_charset設置字符集的方案,只有在環境不允許的情況下,我們才推薦使用第二種binary編碼的方案。但是無論在什麼情況下,都禁止使用”SET NAMES”來作為設置字符集的操作。