mysql 自從 v4.1 開始近乎歇斯底里的在所有能加上語系設定的地方都加上了語系設定選項,
大至全系統設定,小至單一欄位,
全部的地方都可以個別設置不同的語系設定。
而且除了靜態的設定之外還可以動態的在每一次連線的時候指定不同的語系,

這樣的好處是你可以在一個表單裡面塞很多不同語系的欄位,
或者是在一個資料庫裡面有很多不同編碼的表單,
個別都用正確的格式儲存完整的資料,
而不會發生因為資料庫或表單是 latin1 所以某一欄 utf8 的資料就亂掉這種事。

不過壞處則是:只要有一個地方沒設定對整個資料就會爆掉...

由於 mysql 預設的 compile option 是把 default character set 設定為 latin1 ,
而大多數人在 compile 的時候不會特別加上參數去把這個預設值改掉,
更何況是外國的主機商,根本就不會想到該把 latin1 改成 utf8 以增加相容性,
所以一旦你沒有將你的 mysqld 設定正確,
在把 utf8 encoded 的中文倒進 v4.1+ mysql 的時候就會出包囉...

最著名的例子就是之前 DreamHost 把 mysql 從 4.0 升級成 4.1 的時候,
因為預設是用 latin1 connect 所以很多人的 wordpress 資料庫那時候就爆炸了...

而類似的問題也曾經讓 gslin 大長輩掉過資料, blog 整個從零開始...

而這幾天剛好我在弄手上某一台機器的 filesystem 編碼,
把它從 Big5 升級到 UTF-8 ,弄完想說打鐵趁熱,
趁著自己對 codepage conversion 還有點概念的時候順便把手上幾台機器資料庫的編碼都改對好了,
看了一下幾台機器的狀況, ls04 因為現在 RAID1 還沒完全復原,還放在家裡,
在沒有 FQDN 的狀況下懶得去改 server 跟 webpage 的 server URL configuration ,
所以就決定先丟著不管,看了一下另一台 nthusoc ,
發現裡面的資料庫都中了這個毒...
通通把應該是 utf8 的資料用 latin1 存了 orz

所以在谷歌上翻了一下 "mysql utf8 亂碼" 的資料,
最後根據 AlanSung 大長輩這幾篇

[encoding] MySQL 4.1.x SET NAMES UTF8
[Note] 4.11 -> 6.1-R
MySQL 4.1.x 記得要加在 my.cnf 的東西

的說法以及 d.CAT 這幾篇

wordpress 1.5升級注意事項 - 中文亂碼篇
mysql 4.1中文亂碼第二擊
【保証成功】的-mysql-中文亂碼解決方案

和月夜火這一篇

MySQL 5 使用 UTF-8 配合 WordPress 的問題

的說法,交互參照了 mysql 5.0 的 refernce manual 之後,
發現如果用 root 連線的話一定會因為擁有 SUPER 權限所以無法用 init_connect 指定連線的時候一定要用 utf8 編碼,
而連線編碼除了在個別的程式之中用 mysql_query("SET NAMES 'utf8'"); 來指定之外,
唯一可以在 server side 設定的 directive 就只有利用 init_connect 強迫所有 connection 都走 utf8 encoding 。
但是我新增了一個沒有 SUPER 權限的一般帳號後,
照 d.CAT 的改法,在 /etc/my.cnf 裡面加上

[client]
init_connect='SET NAMES utf8'

卻完全無效,沒有做過 wp-db dirty hack 的 wordpress 還是會用 latin1 開啟連線,
後來又在一次仔細檢視 mysql reference manual 裡面的說明之後才赫然發現,
init_connect 的指令是要下在 [mysqld] 下面的...
也就是說 d.CAT 給的解法根本就是錯誤的 orz

而 mysql reference manual 裡面又說
server side 最好用 character-set-server 而不要用 default-character-set 設定 mysqld 的 codepage ,
而且這個 directive 最好配合 collation-server 服用;
client 為了預防萬一可以指定 default-character-set
所以結論就是,在 /etc/my.cnf 裡面要用

[mysqld]
character-set-server=utf8
collation-server=utf8_unicode_ci
init-connect='SET NAMES utf8'

[client]
default-character-set=utf8

(參照 mysqld 5.0 options & variable reference ,指令跟相對應的變數名稱不一樣;有些指令可以下在 cli 跟設定檔,有些只能在 cli 跑,不過雖然它說 init_connect 是變數名稱,指令要下 init-connect ,但是其實二者在設定檔裡有一樣的效果...)

基本上用一個沒有 SUPER 權限的帳號連線,
加上這樣設定的 my.cnf 就可以搞定惱人的 mysql 連線編碼問題了...

所以最終我就用 >mysqldump -u root -p --default-character-set=latin1 database > database.sql 把存成 latin1 的 utf8 資料反著倒出來,
以 less 確定內容是 utf8 無誤之後,
再用 vim 把 sql 檔頭的 SET NAMES latin1 改成 utf8 ,
存檔後靠 >mysql -u root -p database < database.sql 把 utf8 的資料用 default utf8 的 connection 倒回去,這樣一切就搞定啦~~~

f囧" 弄了一整晚這個,真是...



UPDATE: 2006/12/13 晚間補記

後來又東看西看,看到了這篇文章
覺得寫的相當不錯,清楚又有條理,
推薦有 mysql 編碼疑問的人去瞧一瞧。

全文摘要如下:
mysqld 在跟 client 溝通的時候有三個地方要設定編碼:
client, connection 跟 result ,
mysql client 的編碼設定是告訴 mysqld data 會用什麼編碼送過來,
mysql connection 則是告訴 mysqld data 要用哪種編碼存進 database 裡,
mysql result 則是告訴 mysqld data 吐出來的時候要吐成哪種編碼。

作者說 mysqld 在這三者不同的時候會做「轉換」,
不過這一點我覺得怪怪的: mysql 在 client, connection, server, result, 編碼不同的時候應該沒有做內部轉換編碼的動作,
如果有的話那 client 把 UTF-8 用 latin1 connection 送進去應該會掉資料才對,
因為 UTF-8 是 2byte(16bit) 的資料,而 latin1 只有 8bit 不到的資料而已...
但是我們實際上 try 的結果是,如果 client, result 設定成同一個編碼,
connection 設定不一樣時並不會掉資料。
所以這三者如果不同的話 mysql 並不會自動幫你做 conversion ...
差別只是存在電腦裡的資料要被怎樣斷句(7 bit or 16bit)解釋而已。

    全站熱搜

    origin2 發表在 痞客邦 留言(1) 人氣()