歡迎光臨
每天分享高質量文章

[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抽取,排程計算處理,結果持久化

    已同步到看一看
    贊(0)

    分享創造快樂