【茶包射手日記】超詭異 Oracle Unicode 難字問題
0 |
Oracle 遇難字出錯不算新鮮事,現象不外乎中文字變空白變方格變問號變亂碼,老司機們一眼便知,該怎麼做心裡有數,但這回我遇到超不一樣的變種。(這樣算有吸引詭異茶包的特殊體質嗎?)
碰到一個神奇案例,資料寫入 Oracle NVARCHAR2 時結尾會多出一個 \u0000 (ASCII 0) 字元,且只有某筆資料出錯。寫入資料庫時多出 \0 結尾字元,我還是生平第一次遇到,優先懷疑資料傳遞過程被加料,由於傳輸路上涉及 WebAPI、Dapper、Managed ODP.NET,先在好幾處加上 Log,確認 WebAPI 接收的資料是正常的,鎖定問題出在 Dapper + ODP.NET 將文字寫入 Oracle 這段。另一方面,比對問題資料與正常資料差異也有新發現,問題資料包含一個罕用字 - 沗,至此案情逐漸明朗,改朝 Oracle Unicode 難字方向偵辦,但已耗掉大半天時間。只是,難字為什麼跟 \0 有關?
試著用 Managed ODP.NET 寫一小段程式寫入「沗」並不會出錯,加上 Dapper 才重現問題。至此幾乎可確定是已知的 Dapper + ODP.NET Unicode 問題,呼叫 FixOdpNetDbTypeStringMapping() 問題就能解決。(參考:Hacking 樂無窮:修正 Dapper + ODP.NET 無法寫入 Unicode 問題) 但第一時間沒能察覺跟難字有關,錯失快速破案的機會,讓我有些扼腕。
回頭調查這個神祕的難字錯誤,只會發生用 OracleDbType.Varchar2 型別傳送「沗」字給 NVarChar2 欄位,而 Oracle 資料庫未採 AL32UTF8 編碼的情境,而「沗」字造成的現象特別到讓人印象深刻 - 不是出現空白、問號、方格或亂碼,而是「重複前一個字元,加上字串結尾多一個 \0」。
我用一個範例重現這個神奇的難字現象,不想為了測試在資料庫新增資料表,我宣告了一個變數 nc NVARCHAR2,用一個 :pIn 傳入中文字串,用 :pOut 取出 (這個技巧在 ODP.NET 練習 - 執行 PL/SQL 將結果寫入暫存資料表傳回有示範過),分別傳入不同難字組合看結果。
<%@Page Language="C#"%>
<%@Import Namespace="Dapper"%>
<%@Import Namespace="Oracle.ManagedDataAccess.Client"%>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
using (var cn = DataHelper.GetConnection())
{
cn.Open();
var cmd = cn.CreateCommand();
cmd.CommandText = @"
DECLARE
nc NVARCHAR2(16);
BEGIN
nc := :pIn;
:pOut := nc;
END;";
var pIn = cmd.Parameters.Add("pIn", OracleDbType.Varchar2);
var pOut = cmd.Parameters.Add("pOut", OracleDbType.Varchar2);
pOut.Direction = System.Data.ParameterDirection.Output;
pOut.Size = 128;
Action<string> test = (t) => {
pIn.Value = t;
cmd.ExecuteNonQuery();
string v = pOut.Value.ToString();
Response.Write("<li>" + t + " = " + v + "(" + BitConverter.ToString(Encoding.UTF8.GetBytes(v)) + ")</li>");
};
Response.Write("<ul>");
test("A沗");
test("Z沗");
test("#沗A");
test("沗");
test("沗字");
test("是沗字");
test("Z犇");
test("D堃");
Response.Write("</ul>");
}
}
</script>
- A沗 = AA(41-41-00) 重複一次A,結尾出現 \0
- Z沗 = ZZ(5A-5A-00) 重複一次Z,結尾出現 \0
- #沗A = ##A(23-23-41-00) 重複一次#,後方接著的 A 字元後方多出 \0
- 沗 = ?(EF-BC-9F) 若前面無字元,傳回 ?(UTF8 = EF-BC-9F),沒出現 \0
- 沗字 = ?字(EF-BC-9F-E5-AD-97) 若前面無字元,傳回?,後方中文字正常,無 \0
- 是沗字 = 是是字(E6-98-AF-E6-98-AF-E5-AD-97) 前後方都是中文,傳回?,後方字元正常,無 \0
- Z犇 = Z(5A-EE-9B-95) 犇字顯示為不可見文字
- D堃 = D(44-EE-83-86) 堃字顯示為不可見文字
夠奇特吧?誤將 Unicode 視為 BIG5 處理,結果不可預期這點我可以接受,但從沒想過「前字元重複,字串最尾端出現\0」也是出現難字的跡象,這回長了見識,下次再遇到就不用走冤枉路了。
A very strange Unicode char behavior on ODP.NET.
Comments
Be the first to post a comment