[NewLife.XCode]高級查詢(化繁為簡、分頁提升性能)


NewLife.XCode是一個有10多年歷史的開源數據中間件,支持nfx/netcore,由新生命團隊(2002~2019)開發完成並維護至今,以下簡稱XCode。

整個系列教程會大量結合示例代碼和運行日志來進行深入分析,蘊含多年開發經驗於其中,代表作有百億級大數據實時計算項目。

開源地址:https://github.com/NewLifeX/X (求star, 754+)

 

擴展查詢

前文《[NewLife.XCode]實體類詳解》中有講到擴展查詢,XCode生成實體類代碼時,在模型類有一個region叫“擴展查詢”,一般是FindByAbc/FindAllByAbc的形式。

擴展查詢以數據表索引為依據來生成:

  • 唯一索引(含主鍵)生成FindByAbc方法(如FindByName),返回單個對象;
  • 非唯一索引生成FindAllByAbc方法(如FindAllByClassID),返回對象列表(非null);

如上圖,可知Entity實體基類內部,查詢方法分為單對象查詢的Find和對象列表的查詢FindAll。

實際上,Find最終調用FindAll方法查一行。

 

Find/FindAll有多個重載,最主要的地方都是構造where查詢條件。

下划線_是每個實體類都有的內嵌類,它包含了每一個字段的Field引用,借助運算符重載,可以很方便的構造查詢條件,例如上面的_.Name == name最終會生成 where Name='Stone'

因為是內嵌類,在實體類內部使用的時候非常方便。但要是想要實體類外部使用,就麻煩很多了,需要帶上實體類類名。

 

原則:XCode是充血模型,不管多么簡單的查詢,建議都封裝Find/FindAll/Search等方法供外部使用。

 

高級表達式查詢

 僅靠一兩個字段的簡單查詢,肯定無法滿足各種業務要求,我們需要更強大的查詢支持,特別是根據不同條件拼接不同語句。

上面是兩個非常典型的業務查詢。

這里請出了條件表達式WhereExpression,實際上它只有兩個功能,&表示And,|表示Or,根據表達式級別支持括號運算。

exp&=xxx 是最常用的寫法,右邊一般是各種Field表達式。

上面第一個例子,生成的查詢語句可能是 select * from Student where classid=?classid and name like '%?key%'

為什么說“可能”?因為classid為0,或者key為空時,並不會參與拼接查詢語句。

 

第二個例子稍微復雜一些,首先對key進行精確查詢,找到了就返回,若是沒找到,則開啟模糊查詢。 

這里遇到了等於、包含、區間等判斷操作,后面會詳解所有支持的操作。

 

如非必要,建議保留select * 的查詢方式,而不是指定列。 

碼農法則:數據庫壓力小於100qps時不要考慮指明select列來優化,大多數系統活不到需要優化的明天!

 

高級分頁 

兩個例子都出現了一個PageParameter參數page,這是分頁參數,包含分頁查詢以及排序所需要的數據。

PageIndex和PageSize指定頁序號和每頁大小,這是內部建立分頁查詢的核心依據;

Sort 指定排序字段,Desc 指定是否降序(默認升序);

RetrieveTotalCount 指定是否或者總記錄數,若為true,則在查詢記錄集之前,先查詢滿足條件的總行數TotalCount,用於分頁PageCount。此時等於執行兩次數據庫查詢;

RetrieveState 指定是否獲取統計 State,若為true,則在查詢記錄集之后,執行聚合查詢,對數字型字段使用Sum聚合。此時最多可能執行3次數據庫查詢;

在執行FindAll查詢時,若有傳入 PageParameter 且 RetrieveTotalCount 為true,則先查詢滿足條件的記錄數,大於0時才查某一頁數據。

如果 Meta.Count 評估認為本表總行數超過100萬,且FindAll查詢沒帶有條件,則page.TotalCount直接取Meta.Count(少量偏差),以避免極大的FindCount耗時。

100萬行以上數據表,如若不帶條件或者條件沒有命中索引,select count 將會極其的慢,在1000萬以上甚至查不出來,這是XCode能對100億表進行分頁查詢的關鍵所在

 

Meta.Count 的初始值來自於數據庫元數據索引表,里面有該表主鍵的總行數,取得該值后如果小於100萬再異步select count一次。

10多年前博客園ORM大戰的時候,我們常說,等你支持千萬級分頁的時候再來比,就是鑽了select count很慢的這個空子,很多人count出來總數再分頁 ^_^

上圖4億數據,查詢第10000頁,在SQLite單表上,阿里雲1C1G服務器。

 

FindCount 分頁

在早期版本,不支持RetrieveTotalCount ,只能通過 FindCount 取得滿足該條件的總記錄數,然后進行分頁,至今仍然支持傳統方法。

因此可以看到,FindAll 和 FindCount 都是成對出現,參數一摸一樣。

並且 FindCount 方法也會帶有分頁參數,雖然用不到,但.NET2.0時代的 ObjectDataSource 要求兩者的參數名稱和順序必須一致。

所有 FindCount 方法,將會得到 select count 查詢語句,因此千萬級大表需要慎用。

 

PageSplit 分頁

內置支持的各種數據庫,都有實現普通查詢語句轉為分頁語句的 PageSplit(sql, start, maxNums) 方法。

MySql/SQLite/PostgreSQL 能夠很好支持,只需要在 sql 后加上 limit start, maxNums 即可;

Oracle/SqlServer/Access/SqlCe 則要麻煩一次,其中SqlServer最復雜,不同版本的分頁方法還不同,早期版本還要求有主鍵字段;

因此,sql 必須是簡單的單表查詢語句,PageSplit 才能把任意查詢拆開並轉換為分頁查詢。

 

大表分頁優化

大表分頁查詢,開頭會很快,越是往后越慢!

XCode采用倒置優化法,對於超過100萬行(借助Meta.Count評估)的表,如果查詢頁超過中線,則從另一個方向查詢,然后再把結果倒置回來。

 

 

XCode要求數據查詢必須考慮分頁,沒有分頁的系統一般死在100萬行以內。

 

Field擴展

內嵌類_引用的字段是Field,它繼承自FieldItem。

Field/FieldItem全部功能:

  • Equal 等於,操作符==
  • NotEqual 不等於,操作符!=
  • 大於操作符>,大於等於>=
  • 小於操作符<,小於等於<=
  • StartsWith 字符串開始,like '{0}%'。(支持索引)
  • EndsWith 字符串結束,like '%{0}'
  • Contains 字符串包含,like '%{0}%'
  • In 集合包含,支持列表集合、字符串子查詢和SelectBuilder子查詢,集合只有一個元素時轉為相等操作
  • NotIn 集合不包含,支持列表集合、字符串子查詢和SelectBuilder子查詢,集合只有一個元素時轉為不相等操作
  • IsNull 是否空
  • NotIsNull 不是空
  • IsNullOrEmpty 字符串空或零長度
  • NotIsNullOrEmpty 字符串非空非零長度
  • IsTrue 是否True或者False/Null,參數決定兩組之一
  • IsFalse 是否False或者True/Null,參數決定兩組之一
  • Between 時間區間,大於等於開始,小於結束,如果開始結束都只有日期而沒有時分秒,則結束加一天,如(2019-04-17,  2019-04-17)查 time>='2019-04-17' and time<2019-04-18'

排序字句/分組聚合

  • Asc,升序
  • Desc,降序。order by name desc
  • GroupBy,分組。group by name
  • As,聚合別名
  • Count,計數
  • Sum,求和
  • Min,最小
  • Max,最大

 

查詢的本質

查詢的本質是五參數版FindAll(where, order, selects, start, maxnums),其它查詢方式都由它轉化而來!

Entity實體基類封裝了各種常用的查詢方法:

對於單表查詢的XCode來說,五參數版FindAll很容易得到 select [selects] from [table] where [where] order by [order] limit [start], [maxnums] 語句,根據這個理念,FindAll可以支持任意復雜查詢!

最終查詢語句,由SelectBuilder類承載。 

 

多表子查詢

XCode不支持多表Join關聯,這在前面《擴展屬性》中提到過。

擴展屬性固然可以解決關聯多表字段的問題,並且借助緩存性能還不錯,但是需要同時在兩張表上設置條件的時候,就行不通了。

於是,需要用到高級查詢,可以用子查詢 來替代,正是前面說到的FieldItem.In擴展。

 

要查詢名為“992班”的所有學生,一般這樣寫:

select * from student s inner join class c on s.classid=c.id where c.name='992班'

XCode從2008年起,就放棄支持多表關聯,自然也就不支持這樣的寫法。

在一般系統里面,班級表數據不多,可以借助實體緩存或者對象緩存:

// Class.FindByName 內部用緩存
var cls = Class.FindByName("992班");
var list = Student.FindAll(Student._.ClassID==cls.ID);
// select * from student where classid=1234

但如果主表從表都是百萬級大表,或者從表查詢條件比較復雜,緩存就有點難以為繼了。

於是有了子查詢:

調用方法:var list = Student.Search(SexKinds.女, "992班", p);

得到結果:

select * from student where sex=2 and classid in(select id from class where name='992班')

至此,絕大部分多表關聯復雜查詢語句,可以轉化為子查詢 !

 

系列教程

NewLife.XCode教程系列[2019版]

  1. 增刪改查入門。快速展現用法,代碼配置連接字符串
  2. 數據模型文件。建立表格字段和索引,名字以及數據類型規范,推薦字段(時間,用戶,IP)
  3. 實體類詳解。數據類業務類,泛型基類,接口
  4. 功能設置。連接字符串,調試開關,SQL日志,慢日志,參數化,執行超時。代碼與配置文件設置,連接字符串局部設置
  5. 反向工程。自動建立數據庫數據表
  6. 數據初始化。InitData寫入初始化數據
  7. 高級增刪改。重載攔截,自增字段,Valid驗證,實體模型(時間,用戶,IP)
  8. 臟數據。如何產生,怎么利用
  9. 增量累加。高並發統計
  10. 事務處理。單表和多表,不同連接,多種寫法
  11. 擴展屬性。多表關聯,Map映射
  12. 高級查詢。復雜條件,分頁,自定義擴展FieldItem,查總記錄數,查匯總統計
  13. 數據層緩存。Sql緩存,更新機制
  14. 實體緩存。全表整理緩存,更新機制
  15. 對象緩存。字典緩存,適用用戶等數據較多場景。
  16. 百億級性能。字段精煉,索引完備,合理查詢,充分利用緩存
  17. 實體工廠。元數據,通用處理程序
  18. 角色權限。Membership
  19. 導入導出。Xml,Json,二進制,網絡或文件
  20. 分表分庫。常見拆分邏輯
  21. 高級統計。聚合統計,分組統計
  22. 批量寫入。批量插入,批量Upsert,異步保存
  23. 實體隊列。寫入級緩存,提升性能。
  24. 備份同步。備份數據,恢復數據,同步數據
  25. 數據服務。提供RPC接口服務,遠程執行查詢,例如SQLite網絡版
  26. 大數據分析。ETL抽取,調度計算處理,結果持久化

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM