正文
大家如果是做後端開發的,想必都實現過列表查詢的介面,當然有的查詢條件很簡單,一條 sql 就搞定了,但有的查詢條件極其複雜,再加上庫表中設計的各種不合理,導致查詢介面特別難寫,然後加班什麼的就不用說了(不知各位有沒有這種感受呢~)。
下面以乙個例子開始,這是某購物**的搜尋條件,如果讓你實現這樣的乙個搜尋介面,你會如何實現?(當然你說借助搜尋引擎,像 elasticsearch 之類的,你完全可以實現。但我這裡想說的是,如果要你自己實現呢?)
從上圖中可以看出,搜尋總共分為6大類,每大類中又分了各個子類。這中間,各大類條件之間是取的交集,各子類中有單選、多選、以及自定義的情況,最終輸出符合條件的結果集。
好了,既然需求很明確了,我們就開始來實現。
實現1率先登場是小a同學,他是寫 sql 方面的「專家」。小a信心滿滿的說:「不就是乙個查詢介面嗎?看著條件很多,但憑著我豐富的 sql 經驗,這點還是難不倒我的。」
於是乎就寫出了下面這段**(這裡以 mysql 為例):
select ... from table_1left join table_2left join table_3left join (select ... from table_x where ...) tmp_1...where ...order by ...limit m,n
**在測試環境跑了一把,結果好像都匹配上了,於是準備上預發。這一上預發,問題就開始暴露出來。預發為了盡可能的逼真線上環境,所以資料量自然而然要比測試大的多。所以這麼乙個複雜的 sql,它的執行效率可想而知。測試同學果斷把小a的**給打了回來。
實現2總結了小a失敗的教訓,小b開始對sql進行了優化,先是通過了explain
關鍵字進行sql效能分析,對該加索引的地方都加上了索引。同時將一條複雜sql拆分成了多條sql,計算結果在程式記憶體中進行計算。
偽**如下:
$result_1 = query('select ... from table_1 where ...');$result_2 = query('select ... from table_2 where ...');$result_3 = query('select ... from table_3 where ...');...$result = array_intersect($result_1, $result_2, $result_3, ...);
這種方案從效能上明顯比第一種要好很多,可是在功能驗收的時候,產品經理還是覺得查詢速度不夠快。小b自己也知道,每次查詢都會向資料庫查詢多次,而且有些歷史原因,部分條件是做不到單錶查詢的,所以查詢等待的時間是避免不了的。
實現3小c從上面的方案中看到了優化的空間。他發現小b在思路上是沒問題的,將複雜條件拆分,計算各個子維度的結果集,最後將所有的子結果集進行乙個彙總合併,得到最終想要的結果。
於是他突發奇想,能否事先將各個子維度的結果集給快取起來,這要查詢的時候直接去取想要的子集,而不用每次去查庫計算。
這裡小c採用 redis 來儲存快取資料,用它的主要原因是,它提供了多種資料結構,並且在 redis 中進行集合的交並集操作是一件很容易的事情。
具體方案,如圖所示:
這裡每個條件都事先將計算好的結果集id存入對應的key中,選用的資料結構是集合(set)。查詢操作包括:
這其實就是所謂的反向索引。
這裡會發現,漏了乙個**的條件。從需求中可知,**條件是個區間,並且是無窮舉的。所以上述的這種窮舉條件的 key-value 方式是做不到的。這裡我們採用 redis 的另一種資料結構進行實現,有序集合(sorted set):
將所有商品加入 key 為**的有序集合中,值為商品id,每個值對應的分數為商品**的數值。這樣在 redis 的有序集合中就可以通過zrangebyscore
命令,根據分數(**)區間,獲取相應結果集。
至此,方案三的優化已全部結束,將資料的查詢與計算通過快取的手段,進行了分離。在每次查詢時,只需要簡單的查詢 redis 幾次就能得出結果。查詢速度上符合了驗收的要求。
擴充套件這裡你或許發現了乙個嚴重的功能缺陷,列表查詢怎麼能沒有分頁。是的,我們馬上來看 redis 是如何實現分頁的。
分頁主要涉及排序,這裡簡單起見,就以建立時間為例。
如圖所示:
圖中藍色部分是以建立時間為分值的商品有序集合,藍色下方的結果集即為條件計算而得的結果,通過zinterstore
命令,賦結果集權重為0,商品時間結果為1,取交集而得的結果集賦予建立時間分值的新有序集合。對新結果集的操作即能得到分頁所需的各個資料:
關於索引資料更新的問題,有兩種方式來進行。一種是通過商品資料的修改,來即時觸發更新操作,一種是通過定時指令碼來進行批量更新。這裡要注意的是,關於索引內容的更新,如果暴力的刪除 key,再重新設定 key。因為 redis 中兩個操作不會是原子性進行的,所以中間可能存在空白間隙,建議採用僅移除集合中失效元素,新增新元素的方式進行。
效能優化
redis 是記憶體級操作,所以單次的查詢會很快。但是如果我們的實現中會進行多次的 redis 操作,redis 的多次連線時間可能是不必要時間消耗。通過使用multi
命令,開啟乙個事務,將 redis 的多次操作放在乙個事務中,最後通過exec
來進行原子性執行(注意:這裡所謂的事務,只是將多個操作在一次連線中執行,如果執行過程中遇到失敗,是不會回滾的)。
總結這裡只是乙個採用 redis 優化查詢搜尋的乙個簡單 demo,和現有的開源搜尋引擎相比,它更輕量,學習成本頁相應低些。其次,它的一些思想與開源搜尋引擎是類似的,如果再加上詞語解析,也可以實現類似全文檢索的功能。
感謝閱讀
一步步學ROS
最近因為看svo的 裡面用到catkin決定要好好看ros,年前學會基本操作。啟動節點 rosrun package name executable name 檢視節點 rosnode list 注 rosout 節點是乙個特殊的節點,通過 roscore 自動啟動 檢視特定節點的資訊 rosnod...
windows Thrift c 一步步搭建
1.thrift 原始碼路徑 2.libevent原始碼路徑 3.boost路徑 安裝 conan install boost 1.68.0 conan stable 4.openssl路徑 安裝 conan install openssl 1.1.1a conan stable conan安裝bo...
一步步啟動linux
可以一步一步啟動linux.在ubantu剛一啟動時,按c健即進入grub 提示符狀態,在此狀態下輸入 我用的是ubuntu 13 grub linux vmlinuz grub ls boot grub initrd boot initrd.img 3.11.0 15 generic grub b...