NewLife.XCode是一個有10多年歷史的開源數據中間件,支持nfx/netstandard,由新生命團隊(2002~2019)開發完成並維護至今,以下簡稱XCode。
整個系列教程會大量結合示例代碼和運行日志來進行深入分析,蘊含多年開發經驗於其中,代表作有百億級大數據實時計算項目。
開源地址:https://github.com/NewLifeX/X (求star, 743+)
為何需要擴展屬性
XCode不支持多表關聯查詢,單表查詢利於優化以及分表分庫,一切Join都可以借助擴展屬性實現,配合緩存使用可以達到更好的效果!
(XCode前期支持多表關聯,直到2008年才正式廢除)
“擴展屬性”是2007年起XCode特有叫法,不同於其它任何場景的意義(如Silverlight/WPF)
前文《實體類詳解》中有提到一個學生班級的實體類模型,一個典型需求是查詢學生列表時希望暫時班級名稱或者其它信息。於是有:
select s.*, c.name where student s left join class c on s.classid=c.id
sql語法千變萬化,如果要支持多表關聯join,就很難做到統一查詢風格,更是難以優化。
於是XCode放棄支持多表關聯,寧可拆分為多次查詢。令人驚訝的是,不僅性能沒有下降,反而大大提升了,主要因為單表小查詢有多級緩存的加持!
擴展屬性用法
使用擴展屬性來實現關聯查詢,本質上就是多次查詢!
如上,這是一個經典的多表關聯場景,學生表帶有班級ID字段,同樣還有產品和分類表等等。
這是XCode根據模型文件自動生成的代碼,因為字段名ClassID剛好是Class表加上它的主鍵ID,並且都是整型。
對於實體對象來說,student.Name是學生名稱,student.ClassName是班級名稱。
看起來它們就像是一張表的屬性字段,這就是擴展屬性的由來,不僅僅是多表關聯屬性,還可以是其它屬性,為區別於數據字段屬性,統稱為擴展屬性!
擴展屬性先准備一個Class屬性,再加一個ClassName,主要是為了方便某些場合使用 student.Class。
當然,執行一次查詢得到student后,不論是訪問student.Class還是訪問student.ClassName,都會觸發一次Class.FindByID,可以理解為執行一次查詢(不一定是數據庫)。
在Web頁面上,如果每頁顯示20個學生,那么先要執行 select * from student limit 20,然后展示學生列表時,因為需要班級名稱,觸發擴展屬性查詢。
可以認為,理論上這個頁面需要查詢1+20次。
擴展屬性為什么不寫成 public Class Class => Class.FindByID(ClassID) 呢?
其實雖然看起來簡單,但是還得考慮一個可能,同一個student對象可能多次訪問student.ClassName,這么寫豈不是每次訪問都會執行Class.FindByID?
因此,XCode設計了擴展集合Extends,可以認為是一個字典,每個擴展屬性都經過它走一遭,如果查詢過一次就緩存起來,避免反復查詢。
Extends.Get第一個屬性是擴展屬性名,決定是否有緩存,第二個是沒有緩存時要執行的委托。
這就是擴展屬性緩存,默認緩存時間10秒,足夠抗住短期內成千上萬次重復調用。
擴展屬性優化
盡管有Extends擴展屬性緩存支持,但每個對象還是要執行一次Class.FindByID查詢,損耗還是不小的。
在XCode里面,根據主鍵而設計的查詢(如FindByID)往往帶有很好的緩存優化。
如上,這是XCode默認生成的代碼,當Class表數據不足1000行時,走實體緩存。
也就是說,Meta.Cache時執行一次 select * from student 返回所有行,並緩存起來。后面的Find實際上是在緩存中查找。實體緩存有效期默認10秒。
只有數據表達到1000行,才走 Find(_.ID==id) 數據庫查詢 select * from class where id=? 。然而XCode下層還有一個數據層緩存,相同select查詢默認緩存10秒
此外,也可以根據業務特點采用單對象緩存,例如跨境電商的產品種類特別多(10萬+),可以采用字典式的單對象緩存。
因此,在學生類那邊看起來訪問屬性會觸發多次Class.FindByID,殊不知它內部別有洞天,三級緩存(實體緩存、對象緩存、數據緩存)等着伺候!(后續專文介紹緩存)
回到開頭的例子,一個列表頁顯示20個學生,理論查詢次數1+20次,在多級緩存加持的擴展屬性下,99.99%的時候只會查詢1次,而班級表的關聯,完全在內存緩存中進行。
一次簡單的單表查詢,顯然要比join班級表的查詢要快得多!
魔方的特別支持
在上述擴展屬性中,注意到ClassName屬性上有一個Map特性。
它表示映射,本對象的ClassID字段,映射到Class類的ID字段。
在魔方列表頁中,本來顯示冷冰冰ClassID的地方,就會變為顯示友好的ClassName。
在魔方表單頁中,本來顯示數字框ClassID的地方,也會變成顯示下拉列表框。
如果下拉列表庫內容很多,可以精簡Map特性,只要第一個參數指明本地字段,而不需要第二第三字段表示的目標字段。此時在魔方表單頁會顯示數字框,但是后面顯示ClassName
到此,你還認為多次查詢一定比單次Join慢嗎?
系列教程
NewLife.XCode教程系列[2019版]
- 增刪改查入門。快速展現用法,代碼配置連接字符串
- 數據模型文件。建立表格字段和索引,名字以及數據類型規范,推薦字段(時間,用戶,IP)
- 實體類詳解。數據類業務類,泛型基類,接口
- 功能設置。連接字符串,調試開關,SQL日志,慢日志,參數化,執行超時。代碼與配置文件設置,連接字符串局部設置
- 反向工程。自動建立數據庫數據表
- 數據初始化。InitData寫入初始化數據
- 高級增刪改。重載攔截,自增字段,Valid驗證,實體模型(時間,用戶,IP)
- 臟數據。如何產生,怎么利用
- 增量累加。高並發統計
- 事務處理。單表和多表,不同連接,多種寫法
- 擴展屬性。多表關聯,Map映射
- 高級查詢。復雜條件,分頁,自定義擴展FieldItem,查總記錄數,查匯總統計
- 數據層緩存。Sql緩存,更新機制
- 實體緩存。全表整理緩存,更新機制
- 對象緩存。字典緩存,適用用戶等數據較多場景。
- 百億級性能。字段精煉,索引完備,合理查詢,充分利用緩存
- 實體工廠。元數據,通用處理程序
- 角色權限。Membership
- 導入導出。Xml,Json,二進制,網絡或文件
- 分表分庫。常見拆分邏輯
- 高級統計。聚合統計,分組統計
- 批量寫入。批量插入,批量Upsert,異步保存
- 實體隊列。寫入級緩存,提升性能。
- 備份同步。備份數據,恢復數據,同步數據
- 數據服務。提供RPC接口服務,遠程執行查詢,例如SQLite網絡版
- 大數據分析。ETL抽取,調度計算處理,結果持久化