字體:小 中 大 | |
|
|
2022/08/19 18:08:21瀏覽1932|回應0|推薦5 | |
上一份工作傻蛋有處理過一次效能問題。 程式是自己設計的,一開始資料還少都很快,某天開始變慢,壓測時變得很慢很慢。 查了下,如果用常見的slow SQL查數據庫速度慢的執行那套思路,倒是沒發現問題。 畢竟目前已經是使用固態硬碟當硬碟的時代了,沒到海量的數據,很難慢到哪裡去。 於是就一步一步查變慢的那個api到底是哪裡慢了。 結果,是一個下條件抓最近幾筆資料出來的小查詢,意外的耗時。 其實也不是說多慢,沒壓測時,不到兩百毫秒,也就是一次0.2秒那樣而已。 然後因為是高頻的呼叫,就會變得有點慢,很慢,再非常慢那樣。 其實這個數據結構也沒有說多複雜,就是一個要排程同步另一個系統資料的數據表,每分鐘會一直漲數據。 除了流水號的主鍵之外,有一個文字型態的欄位放值目前其實都是數字的算是一種分類的id吧,另外還有一個時間欄位是用秒數,也就是那個unix timestamp保存的整數的欄位。 那個高頻而有點慢的查詢,其實也就只是指定一個分類抓那個秒數時間最大的前五筆出來的這樣的查詢而已。 因為部分原因,那個時間的順序並不總是和每筆資料建立時的流水號的順序一致,而剛好需求就是必須用那個欄位才對。 說穿了就是下條件抓分頁資料時不使用主鍵排序的查詢問題那樣。 嗯,這個,傻蛋應該有想過這是個高頻查詢,應該預先準備好對策了才是啊? 對,那個分類欄位再接著那個時間欄位,有建一個兩個欄位的複合欄位索引了。 那怎麼會慢呢? 數據表才幾百MB而已,而照條件配合索引,每次要抓的資料不到1kb,不該這麼慢。 實驗一下,查詢條件的排序換成主鍵欄位而不是那個時間欄位,速度就變很快,個位數毫秒,所以果然是排序的問題。 但用主鍵排序就業務邏輯來說是不對的,所以一定是哪裡出了問題。 關聯式資料庫的查詢,有個很重要的指令explain,explain那個查詢的select指令,看到底怎麼回事。 疑,準備好的那個索引沒用到啊。 第一個錯誤是,分類那個欄位也就是索引第一個欄位,格式是字串,但指令語法裡面值是用數字塞進來的。 那個MySQL會很聰明的幫我們把數字的分類id轉換成文字,不會報錯,會回傳正確的結果。 只是那個欄位的索引就廢了。 修正一下改傳值是數字的字串再看看,疑,還是沒有使用到索引,怎回事? 傻蛋甚至試個強制使用索引的hint,用use index不行再用force index,但就是不行,反而更慢。 見鬼了。 然後傻蛋就東想想西想想,索引的兩個欄位資料型態不一樣,然後傻蛋就添加一個新欄位,值是從時間秒數那個排序要的欄位複製寫過去的,兩個都是文字欄位了。 然後再建一個新的索引,是分類和那個新欄位。 結果一測試啊,耗時減少百倍以上,有使用到剛建的那個索引了。 這才對嘛。 所以很合理的懷疑是否兩個欄位資料型態不同的複合索引會導致第二個欄位的排序無法生效被使用到。 不過傻蛋改試增加分類id的整數新欄位和本來的時間秒數欄位組合建立新索引,竟然沒效果,怪哉。 但畢竟不是吃飽太閒,反正解決問題了,就先不糾結那些細節了,最後就是加一個文字格式的時間秒數欄位,當那個時間秒數的值更新時同步寫入更新,然後用那個索引就行了。 無效的索引就可以取消拿掉,總之問題解決了,壓測的數據也大幅改善了,從每秒大概只能回應幾十次就緊繃,變成回應幾百上千次都游刃有餘,可以看到主管的臉色變成笑臉那樣。 這個案例其實應該算MySQL特有的問題吧,或許換個資料庫就沒事。 但下條件再抓分頁且指定排序欄位,倒是不算太罕見的需求。 額,傻蛋工作約廿年,碰過知道正確建立複合欄位索引的工程師,好像是零。 分頁查詢功能,不知道查詢的排序條件需要索引,是很常見的情況。 但其實照邏輯想一下,如何確保分頁第一頁的前十筆資料一定是是所有符合條件裡的資料的正確的排序的結果? 確保排序時,沒索引就意味著可能的全表查詢啊。 如果是無腦的用常見的框架設計解決方案的確可以降低問題發生的機率。 因為排序都是用主鍵排序的,通常主鍵排序都足夠高效。 缺點自然也很明顯,如果業務邏輯需求不夠簡單,要東加西加一些小需求,然後要不使用主鍵也就是建立時間順序來排序的話,很容易改個小需求,系統就詭異的變慢,慢得無法讓人忍受。 通常這會看這個查詢需求是前台還是後台,低頻的後台報表查詢一個動作幾秒鐘是可以忍耐的,硬碟換成固態硬碟ssd甚至能降到不用一秒鐘,或是透過讀寫分離讓慢操作集中在少用的副本都行,完全沒問題。 但如果是前台高頻查詢,如即時排行榜,也不適合用緩存的話,那種需求拖累速度時,傻蛋看過的案例就是退版砍需求那樣。 有這個必要嗎?如果不懂得如何調校這種指令,建正確的適用索引,數據量與存取量一大,不砍功能退版,使用者的反饋大概就是系統壞掉了慢到不能用那樣,基本上就掛了啊。 而因為這類tune不了的SQL(當然不是不能,只是做的人不會也沒有掙扎找出解法而已),關聯式數據庫貌似也因此受到不少工程師的仇視喔。 所以很多人討厭SQL,偏愛那些叫NoSQL的非關聯式數據庫或是擁抱那些會封裝SQL指令的物件關聯對應的ORM工具,喔反正我沒下SQL都是工具自動產生的,我只是正確的用物件操作的方式寫Code,效能問題是ORM的限制與無能和我無關啦。 然後可以再追加理由,那麼多人那麼多大企業都使用那個ORM工具喔,這就是業界標準最佳實務了,你不能再給我批評要求改善的,有問題是你的需求不對啦。 甚至到最後,有些工程師會以為關聯式數據庫就是很爛的東西,學新的NoSQL才是又酷又強的新選擇,然後一開始選型的時候就強烈指定不要用關聯式數據庫,而要正確的使用合適的非關聯式資料庫喔! 其實呢,問題很簡單,就是很多工程師對SQL只有皮毛的掌握度,不懂所以無法寫出高效的系統而已。 同樣的工程師用不同的工具,可以想像應該會有差不多的結果才是。 把非關聯式資料庫當救贖當神的那些人應該很快就會再次碰壁,發現有一大堆問題,又面臨的博大精深的效能調校問題了。 而且有時候簡單且適合關聯式數據模型的需求,硬套了不合適的非關聯數據庫時,學的時候興奮,用的時候不順,維護的時候呢,媽的這是什麼複雜的混沌? 前幾年就看過不少後悔使用NoSQL的感嘆文。 實際上呢,因為關聯式資料庫算是老技術,在工程師鄙視鏈是比較下游的位置,所以呢,傻蛋以前解決完問題倒是願意教同事,但同事學習意願常常不怎麼樣就是了。 而且,SQL厲害,在當前的求職市場裡,基本上毫無加分,無助於提高談薪水的籌碼,所以不受重視也不用意外。 只不過,目前大部分的系統還是使用關聯式數據庫的就是了,關於效能與成本,還是很有影響的。 之前看過一個不歸我管的電商系統數據庫,數據量和使用頻率其實算還好,但系統慢占用資源驚人的高,還有很多為了解決問題的結構設計。 如舊訂單定期搬移到歷史數據表,避免訂單主檔資料太多跑不動,這都會增加額外的複雜度。 複雜而低效,最後的結局是整個系統淘汰掉,放棄自己維護,改用別的電商平台提供的解決方案那樣了。 基本功不行有時候是需要付出大代價的。 而說起來,關聯式數據庫最關鍵的技術,其實就是索引的設計而已。 這個其實也沒有太多高深的技術或演算法。 每一個最常用的二元樹索引都只是一個排序好的小數據表,通常索引數據表的唯一資料就是指向實際保存數據位置的指針,就這樣而已。 說來抽象,其實用excel來說明就容易懂了。 excel的第一個sheet裡面放了很多欄位格式固定的資料,每一列都是一筆資料。 假設有流水號id,姓,名,地址與很多雜七雜八的用戶資料欄位,就是用戶數據表吧。 我想找所有姓陳的人出來,怎麼找? 從上往下,一列一列看,姓那個欄位是陳的複製出來貼到文字檔當結果,找到最後一列就完成了。 這就是全表查詢。 如果對姓欄位做索引呢? 則第二個sheet,增加一個兩個欄位的數據表,這個數據表第一個欄位就是姓,照筆畫和字碼排序,相同的姓就會排在一起連續保存,然後右邊的第二個欄位是指到第一個sheet的對應數據的位置的連結或是那個流水號也行。 第一個工作表有幾列,第二個工作表就有幾列,一對一個,如果第一個工作表共有505個姓陳的資料,第二個sheet姓陳的那就會有連續505列資料。 怎麼找姓陳的? 看第二個sheet大概中間的位置吧,是姓啥?筆畫和陳相比,是多還少? 這樣可以判斷陳是在上面還是下面,在下面的話,下面大概再抓一半的位置,看看筆畫,陳是在上面還是下面,還是直接就姓陳了? 這樣排序好的資料,只要找個幾次很快就能找到姓陳的,資料多一倍就多找一次,一百萬筆了不起找個二十次,一千億筆呢,了不起三十次,一百兆筆大約也就四十次。 怎算的?2的十次方是1024,大概算1000,就能抓出上面那個估計了。 這是關聯式數據庫最土最常用的二元樹索引方式的處理方式,就是排序好的索引表格那樣了。 再來,使用索引找資料就是要先去排序的表找到後再回主表抓資料,要跳一次。 所以如果你的數據表資料本來就不多,全表查詢會比索引快。 用excel比喻,你的畫面高度可以一次顯示32列資料,那當整個表都不到32列時,你還查什麼索引呢? 而機器抓取數據時,數據庫設定會有一個資料區塊大小的設定,可以設16kb之類的值,根據你數據表一列的欄位裡實際資料占用的大小,決定一個資料區塊大約實際存幾筆資料。 1kb是1024個byte,一個整數欄位是4byte吧,一個文字如果是unicode應該是佔3~4個byte,欄位越多越肥一個資料區塊的筆數就越少,反之欄位少又精簡佔的空間就少,一個資料區塊可以放很多筆資料。 機器處理資料時和人看excel類似,就是一次抓一個資料區塊,往前抓往後抓一次就抓一塊,所以呢,如果一次查詢的資料僅僅是需要用來查看與處理的少少幾筆而已,那一個資料區塊通常就滿足了。 一個I/O讀取就解決了。 而絕大多數的情況,特別是高頻率執行的,通常都只會抓極少量的資料而已,查詢介面上一次顯示五十筆資料夠多了吧,但那也就是一個或幾個資料區塊連續抓取的功夫而已。 即使你的數據庫有海量,甚至到TB等級的數據量,你也不會經常需要一次抓幾GB以上的數據頻繁操作的。 所以查詢結果如果有配合適當的索引,在超大量數據的情境,查詢速度影響,幾乎是無感的。 而沒有排序好的數據表可以方便找到需要的那幾十筆資料的時候呢? 假設數據有2GB,然後一個資料區塊是16kb,那就是有一萬多個資料區塊了,要找多久就祝你好運了。 運氣好,第一個就找到,秒回,爽。 運氣不好,找到最後一個才找到,那就是考驗硬體效能的時候了,看看硬體能有多強悍吧。 喔,那是找一筆資料的時候喔。 如果是要找排序的前幾筆資料呢?不找到最後一個資料區塊,如何很肯定那裏沒有你要的資料? 所以當需要排序又沒有索引可以用的時候,就會是悲劇了。 說到排序,就順便說一下排序索引的概念吧。 用同樣上面那個例子,假設用戶表有一個欄位叫最後按讚時間,找出姓陳的最新按讚的五十個人,那要怎麼實現? 如果沒有另外建複合索引,數據庫引擎有兩個全表查詢以外的選擇。 把所有姓陳的全部抓出來,透過那個姓的索引,然後再把抓出來的資料再用按讚時間排序,回傳前五十筆就完成了。 如果按讚時間這欄位沒單獨建索引就只能再全表查詢和上面那個做法選一個。 如果有呢?則跳到按讚時間的那個索引sheet,從最新的往前找,每一筆或每一個索引資料區塊的資料就指針跳回去把姓抓出來,然後開始數目前有抓到幾個姓陳的了,滿五十個就完成了可以回傳了。 就是這樣找而已,實際快慢是有一套稍微複雜的機制決定,各牌的關聯數據庫會定時分析實際數據狀況,然後號稱聰明的選擇最佳策略用最少的讀取完成任務的。 想想喔,如果數據表有幾十萬筆資料,姓陳就有好幾萬筆,第一種把所有姓陳的先撈出來放記憶體準備排序後找結果,那會很吃記憶體與運算資源吧? 所以你建了索引,查詢條件也有使用到那個索引的欄位,但數據庫的引擎不使用你的索引,通常是有道理的。 但姓陳的如果只有幾百筆呢,可能代價就不大了,資料非常多,姓陳的不多時,這個索引就會有很高的成本效益。 反之,如果用按讚時間索引來抓,如果姓陳的佔整個數據的比例很低很低,抓滿五十筆的代價可能就很高,反之則很快就能完成。 所以怎樣比較快不好說,實際資料的分布形狀不同,同樣的索引設計效果也不同。 但如果是用兩個欄位的索引呢? 第一個欄位是姓,第二個欄位就是按讚時間,所有資料會依姓再依按讚時間排序排好。 那樣只要去這個索引表找到姓陳的區塊們,再從最後面往前抓抓滿五十筆就完成了,是不是很高效? 當然關聯式數據庫還有許多技術細節,如索引可以塞資料欄位,以及數據表join等等的,實際會比傻蛋舉的例子要複雜許多,但概念基本上也就是那樣了。 嗯,所以呢,答案是索引要建好建滿嗎?可能用到的組合都建需要的索引就是厲害? 當然不是。 多開一個excel的sheet不用佔磁碟空間和打開時的記憶體使用量嗎? 當然要啊。 而且用多少佔多少。 再來維護好一個排序好的數據表有那麼輕鬆嗎? 數據庫底層通常會有一些寬鬆機制,不會需要多少數據就使用多少空間,這樣,需要插入一筆新的索引到索引表資料區塊中間的位置時,才可以簡單的插入而不用重新整理。 不然呢?放不下了啊,可又得保持索引數據表正確的根據定義排序正確啊! 那就只好在那個資料區塊和下一個資料區塊間先插入一個空的資料區塊,然後插入的資料放到正確位置後,再平均寬鬆的分配到目標區塊和新增的空區塊兩個資料區塊上,以備之後的插入那樣。 喔,也可以不那麼做,反正就全部重新排序一點空間也不浪費就是了吧?那就是大面積的重新寫入整個索引數據表,祝你好運啊。 上述這些細節,大牌子的即使是免費的關聯式數據庫,基本上都是經過千錘百鍊的各種優化,我們使用時可以忽略細節的。 但那些優化無法解決根本問題,當你逼著它對大量資料做全表查詢時,它還是只能慢給你看甚至死給你看而已。 又回到根本的問題,我們真的會需要頻繁的虐待關聯式數據庫,要求對方每秒寫入又讀取超海量的數據嗎? 通常問題都是出在,其實你根本就只需要幾筆甚至一筆資料而已,人腦不會同時高頻又操作大量的數據的,但因為設計不當而讓數據庫得撈取大量數據甚至全表查詢來滿足你的小小需求,簡單說就是嚴重的浪費而已。 精算一下成本效益決定數據庫結構的設計與索引規劃,關聯式數據庫這樣的老技術也是可以用得很爽的。 話說回來,這些技術細節的掌握,對於解決問題降本增效是很有用的,但是呢,對面試找工作幫助真的不大喔。 新技術才值錢,那些基本功可以讓你進入職場後成為裡面有點地位的高手,但市場上要的是新技術就是了。 所以說,這是個浪費又愚蠢的時代,不是嗎? |
|
( 不分類|不分類 ) |