The two most important days in your life are the day you were born and the day you find out why. -Mark Twain

#18 資料庫的表格裡有關 nchar 和 nvarchar 的選擇

今天正在思考要來寫個不學術的事情,也就是希望能寫個跟產品相關程度較高的主題,想著想著,突然有個回憶就突然閃過腦子.

大約在十年前,當時還在台灣念碩士班,有位在台灣 IBM 工作的朋友剛好介紹了一個很短期的工作,因為他們急著做一個專案,但臨時抽不出過多的人手來完成這些功能,所以就經由朋友的介紹去參與這項專案的開發,賺一點零用錢,只需要做 5 個 tasks 就行了.當時,我記得是用 Javva 開發,然後資料庫產品是 DB2.不過,當時我做的短暫工作跟資料庫並沒有直接關係.

有一天,我到專案辦公室時,剛好看到 PM 正在輸入一些新的 database schema,我好奇在他旁邊看著那些 schema 資料,然後我就發現了一件頗為特別的事情,所以字串相關的資料欄位都是設計為 nchar,比如 nchar(50),nchar(100)之類的.於是,我就好奇一問 PM 為什麼所有的字串類的資料都是用 nchar 呢 ? PM 就馬上回答說,DBA 說這樣比較快.當我聽到這回答時,我似乎沒有完全明白為什麼這樣比較快.就這樣這個 nchar 與 nvarchar 的快慢問題就一直放在腦裡,沒特別去找答案,一切看何時有機會能遇到答案.

過了幾年後,我到了美國念書,某一個學期修了一門資料庫的進階課程,而當時的授課教授是一位印度人,他的實驗室有一個拼裝版資料庫引擎.所謂的拼裝版的資料庫引擎就是許多的功能都是由他的學生們經年累月的拼裝而成,有些是自己開發,有些是透過一些 open source 的引用,而當時我要拼裝的功能是 index search.

為了要製做 index search 用的 B-tree,我就必須學會如何透過該資料庫引擎的 storage manager 取得我需要的資料.就在了解 storage manager 的過程中,我無意間發現了一件事情而勾起了多年前的回憶,也就是 nchar 與 nvarchar 的差別.首先,我必需聲明的是 DB2 不一定是用一樣的方法,但我想以本質的精神來說是相似的.

在了解 storage manager 的過程中,我發現到如果資料欄位的 data type 設定為 nchar 時,這個固定長度會反應在硬碟的空間上,例如 nchar(500),在硬碟上你會發現每一筆資料在硬碟上就一定會有 500 字元的長度,換句話說,你寫入進去的字串也許只有 10 個字元,但最後被放在硬碟上時還是有 500 字元的長度,而前面 10 字元是資料,後面 490 字元是空白.對熟悉資料庫的朋友們來說,這本來就是技術文件上會講的事情,但為什麼 nchar 會比較快呢 ? 那就必須再來看看資料庫引擎是如何處理 nvarchar.比如說 nvarchar(500),你寫入進去的字串有 10 字元,在硬碟上所使用的空間就只有 10 字元,而不是 500 字元.直覺的反應下,你可能會覺得這樣跟快慢有什麼差別呢 ?

當我繼續往下看 storage manager 的程式碼後,我才發現到答案就在這裡.
我們來舉個簡單的例子,假設有一個表格,它的定義和範例資料如下:

ID - int公司名- nchar(50)地址- nvarchar(300)電話- nchar(12)
1 公司 A 台北市忠孝東路1段1號1樓 02-12345678
2 公司 B 台北市忠孝東路3段300巷10弄100號5樓 02-87654321

ID 是這表格的 Primary Key,所以資料就會按 Primary key 的順序寫在硬碟上,而每一欄位寫的順序也就是跟其定義的順序一下,所以你會看到在硬碟上,寫第一筆資料時,第一個欄位是長度是固定的,第二個欄位也是也是固定的,第三個欄位不是固定的,最長可到 300 字元,而第 4 個資料是固定的.那麼問題就在此時出現了,以我們之前講過的 Array/List 來類比,如果你今天要讀取一個長度是固定的資料,你會怎麼讀取呢 ? 簡單的方法就是你會告訴 storage manager 說我要在某一個位置開始讀取 50 bytes 的資料,這樣對 storage manager 就很省事.如果情況變成長度是不固定的,就像表格的地址欄位,此時 storage manager 就不能如此瀟灑地直接讀取 300 bytes 的長度了,因為第一筆的地址沒那麼長,會把電話也讀進來了.因此,storage manager 就必須要先去 "探索" 一下這一個長度到底有多長才能決定要讀取多少長度的 bytes.就是因為這樣的 "探索" 讓 storage manager 多花費了一些作業成本,因此才變成 nchar 比較快的感覺,這正好呼應了當年在執行那個 IBM 專案時所得到的答案.以上是從讀取的角度來看.若從寫入的角度來看,你就會發現還真的很麻煩.以上述表格的資料為例子,如果今天第一筆的地址要更新成跟第二筆的地址一樣,那該怎麼辦 ? Storage manager 要寫入更長的地址資料時卻發現了原本的的地址長度不夠了,若強行寫入就會把電話資料給覆蓋掉了.你可能會想那就把電話資料往後挪,這樣的話也就會造成第二筆資料都要往後挪,如果這表格有成千上萬筆的資料,你一定不會認為這是個好方法.比較簡單但也是花費較大成本的方式就是 page split.至於什麼是 page,什麼是 page split,這是一個可以做為下一篇文章的好主題.

最後,你以為用 nchar 是最好的嗎 ? 固定長度也不是十全十美,因為固定長度的資料會被全部讀出來,以上述公司名來說,大部份的公司名只有短短幾個字,可能只有少數幾家公司的名字很長,但每次讀取還是會讀取 50 bytes,所以這 50 個bytes 就會從資料庫被讀出來,然後在網路上傳送到客戶端,客戶端的應用程式也是收到 50 bytes,但你需要的資料可能只有 6 bytes 而己,你會發現後面接著一大串的空白,必須自行將空白拿掉.

因此,nchar 和 nvarchar 到底那一個好那一個快,看來就要視你站在什麼角度上來看待了.至少我能告訴你的是以資料庫引擎的角度來說,nchar 的資料的確是簡單處理多了,但這不代表客戶端應用程式或網路效能也一定會如此同意.

這是一個小小的問題但背後連接到一個程式寫法的事情,下次你遇到這問題時,可以問問你的團隊技術領導者,看看他能否給你其他更多的思考.


Share:

#17 客戶說程式碼不能有 recursive function,該怎麼辦 ?

不知大家有沒有被客戶這樣要求過,寫程式一律不準出現 recursive 的寫法.這也是十年前左右的故事了,並不是我個人遇到的狀況,是我朋友們遇過的情況.當時對這樣的要求沒太多的頭緒,不過心裡還是把這問題放著,也許有一天我會知道為什麼.

後來,有一次因為一個考試,突然讓我想到這個問題可能的答案,從技術角度來看的話,我想這應該是客戶的顧慮.情況是這樣的,在作業系統中,每個工作被執行的單位是 Process.一個作業系統裡會有許多的 Process 在執行,分別負責不同的功能,例如,有 Process 是專門負責處理 DNS 封包,有 Process 專門負責與印表機的通訊.所以,我們若自己寫一個程式在作業系統裡執行,這個程式也將是一個 Process,作業系統也就會負責他所需要做的工作內容.

在每個 Process 裡都會有一個獨立的記憶體空間用來儲存該 Process 需要的變數與資訊,其中有一項資訊叫 call stack,簡單的說是你的程式某個 function 呼叫了某個 function,然後再呼叫其他的 function,一直呼叫下去到作業系統的核心.因為這個記憶體空間的大小是有一個固定的量,所以當呼叫 function 一直無窮盡的進去下去的話,就很容易造成這個記憶體空間不夠用了,而形成了 call stack overflow 的情況.一旦這情況發生了,該 Process 就會被迫中止.

如果以作業系統的角度來想,程式裡不包含 recursive 寫法是很重要的.所以,一般的習慣都會採用 iterative 的寫法來取代 recursive 的寫法.在前面的 Binary search 文章裡,我們有介紹到 Binary search 的 recursive 寫法與 iterative 寫法.不同的情況會用到不同的寫法技巧,以 Binary search 來說,就是用兩個額外的 index 來做,如果是 Tree traversal 的話,通常是用 Stack 和 List 來把 recursive 寫法改成 iterative 寫法.

未來遇到適當的內容時,再來說明不同的 recusrive 寫法是如何改成 iterative 寫法.

Share:

#16 我是非電腦科系的,真的需要懂這些嗎 ?

這問題其實一個很好的問題.

我假設你是個非電腦科系畢業出身的軟體工程師,回想一下,你當初憑著自己的邏輯能力學習如何寫程式,也學習如何善用大家所熟知的 framework,便可以應付自己工作上所遇到的問題.我想那是因為你的工作以及所面臨到的專案都是屬於商業型導向的應用程式.我所謂商業型導向的應用程式就像是你在金融界中工作,每個需要處理的就是把資料讀出來做報表,或把資料新增進去到資料庫,或是把這資料用某一種格式包裝起來,然後傳到其他的系統再做更多的資料處理.又比如說你在不同領域裡做該領域的工作,例如在航空公司做訂票系統,在電信公司做帳務系統,在電子商務公司做庫存系統.這些軟體工作其實在台灣是佔大多數的.所以,在這些工作中,若你沒有懂這些資料結構或演算法,你照樣也可以把程式寫出來,系統照樣地可以上線,公司照樣可以營運賺錢.因為我自己也曾有過這一段路,所以我知道是可能的.

不知你是否曾問過自己,我怎麼知道我寫的程式是不是好的,我該怎麼評估,或是別人又會怎麼看待我的寫程式能力.我相信非電腦科系畢業的人在做軟體工程師時一定會有這樣的問題.如果你有這樣的問題,很恭喜你,因為你的內心對知識有了渴望,因為你想知道自己的能力或寫程式的技巧是在什麼程度.所以,最好的方法就是回到事情的本質去找答案.因此,資料結構是需要懂的,演算法也是需要懂的,但我們並不用像 Microsoft 或 Google 工程師那樣了解所有的細節,我們只需要知道最基本的道理,這樣就能幫助你了解當某個產品某個新功能出現時,那些大公司的工程師是怎麼做出來的.比如,之前的文章談過 ArrayList,到底是 Array 還是 List,.Net framework在台灣還算流行,幾年前 Microsoft 把基本的 .net library 都開放原始碼了,所以你可以去看看 Microsoft 的工程師是如何設計 ArrayList,看它到底是 Array 還是 List.

若你從前面的文章一直看到這裡,到目前為止你可以發現我講了很多有關時間複雜度和空間複雜度的事情.所以,你就知道我們寫程式時會在乎的事情就會包括這兩個,而別人看待你的程式好壞時,自然而然也會從這兩件事情來思考.考慮程式的好壞的因素很多,而時間複雜度和空間複雜度算是最基本的要求.

所以,即便是一般的商業型應用程式,你也可以回頭去看看你以前寫的程式碼,是不是有些地方可以用 Hash table 能加快資料處理,是不是有些地方可以用 Binary search 能加快資料搜尋,是不是有些地方可以用了 Stack 讓程式更簡單好寫了.透過這種的改進,我相信你寫的程式一定會更有品質.因此,資料結構和演算法是很重要的,在未來的各項主題中都還是會一直介紹下去.

Share: