紀念抓了個把小時的茶包。

同事回報一個詭異狀況,某個使用者登入時遇到異常,由於是線上系統,只能靠 Log 推敲還原事發經過,但卻有個矛盾之處:Log 顯示使用者只登入過一次,但登入後的初始化作業 Log 卻有兩次記錄。為了找出第二次沒有登入卻觸發初始化的來源,翻箱倒櫃清查過所有相關程式碼,一一推敲被執行的可能性,不料所有嫌犯都亮出完美的不在場證明,除了見鬼,我們想破頭也找不到合理解釋。

當案情陷入膠著,同事倒發現一處可疑,登入初始化作業的 Log 記載的客戶帳號,結尾多了一個空白。這段 Log 我們反覆看了 N 遍,結尾空白八成被人類認知模組給自動過濾了,白繞了一大圈才發現。

有了這條線索,很快就拼湊出我們的推論 - 使用者第二次登入時在帳號結尾多敲了空白,而前後端程式未加攔阻,八成是心想:反正帳號沒輸對 SQL 比對帳號密碼就不會正確,因此未對帳號字數或格式進行檢核。不料,SQL WHERE 比對時會自動略過結尾空白,多了空白結尾的帳號就這麼通過了密碼檢核,但要寫登入 Log 時帳號被當成資料夾名稱,結尾空白導致名稱不合法失效,NLog 忽略寫 Log 錯誤,程式繼續往下執行初始化,直到其他地方因帳號結尾多出空白出錯。登入 Log 只有一筆,初始化卻有兩筆,終於有了合理的解釋。

用以下這段程式驗證 SQL WHERE 比對行為:

如上圖所示,用 WHERE N = "Jeffrey " 可以查得 "Jeffrey",但取回至 C# 比對二者並不相等,形成奇妙的「在 SQL 端相等,但 C# 不相等」的奇特結果,也證明 SQL WHERE 比對會自動忽略結尾空白。而這個行為遇到固定寬度欄位 CHAR(N) 時也很精彩,一樣會造成要 SQL WHERE 找回相等的字串,在 C# 卻不相對的矛盾狀況。

至於結尾空白導致寫 Log 失敗的問題,依據 MSDN 文件 空白或句點不宜做為檔案或資料夾結尾:(要硬幹也成,但實在沒必要庸人自擾)

Do not end a file or directory name with a space or a period. Although the underlying file system may support such names, the Windows shell and user interface does not. However, it is acceptable to specify a period as the first character of a name. For example, ".temp".

NLog 預設會忽略 Log 寫入例外 以避免因寫 Log 出錯干擾主要作業,在 NLog.config 加上 throwExceptions="true" 可觀察到因路徑名帶有空白結尾出錯的訊息,證實我們的猜測。

又學個經驗:SQL WHERE 忽略結尾空白、沒看到 Log 不一定代表事情沒發生。

The trailing space of string will be ignored by SQL server and this invisible char cannot used as last part of Windows filename, this bug took me one hour to find out.


Comments

# by JerryH

原來SQL = 會忽略結尾空白,另外發現用 like 不會忽略結尾空白,真神奇

Post a comment