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版]
- 增刪改查入門。快速展現用法,程式碼配置連線字串
- 資料模型檔案。建立表格欄位和索引,名字以及資料型別規範,推薦欄位(時間,使用者,IP)
- 物體類詳解。資料類業務類,泛型基類,介面
- 功能設定。連線字串,除錯開關,SQL日誌,慢日誌,引數化,執行超時。程式碼與配置檔案設定,連線字串區域性設定
- 反向工程。自動建立資料庫資料表
- 資料初始化。InitData寫入初始化資料
- 高階增刪改。多載攔截,自增欄位,Valid驗證,物體模型(時間,使用者,IP)
- 臟資料。如何產生,怎麼利用
- 增量累加。高併發統計
- 事務處理。單表和多表,不同連線,多種寫法
- 擴充套件屬性。多表關聯,Map對映
- 高階查詢。複雜條件,分頁,自定義擴充套件FieldItem,查總記錄數,查彙總統計
- 資料層快取。Sql快取,更新機制
- 物體快取。全表整理快取,更新機制
- 物件快取。字典快取,適用使用者等資料較多場景。
- 百億級效能。欄位精煉,索引完備,合理查詢,充分利用快取
- 物體工廠。元資料,通用處理程式
- 角色許可權。Membership
- 匯入匯出。Xml,Json,二進位制,網路或檔案
- 分表分庫。常見拆分邏輯
- 高階統計。聚合統計,分組統計
- 批次寫入。批次插入,批次Upsert,非同步儲存
- 物體佇列。寫入級快取,提升效能。
- 備份同步。備份資料,恢復資料,同步資料
- 資料服務。提供RPC介面服務,遠端執行查詢,例如SQLite網路版
- 大資料分析。ETL抽取,排程計算處理,結果持久化
朋友會在“發現-看一看”看到你“在看”的內容