sql 写query_为什么需要动态SQL
為什么需要動態SQL
在使用?EF或者寫?SQL語句時,查詢條件往往是這樣一種非常常見的邏輯:如果客戶填了查詢信息,則查詢該條件;如果客戶沒填,則返回所有數據。
我常常看到很多人解決這類問題時使用了錯誤的靜態?SQL的解決辦法,使得數據庫無法利用索引,導致性能急劇下降。
介紹數據
這次我將使用我的某客戶的真實數據來演示(已確認不涉及信息安全?),有一個訂單表?FoodOrder,結構如下:?
我在?Id、?FoodMenuId、?OrderUserId上創建了非聚集索引,在?OrderTime上創建了聚集索引。該表有?51652條數據。
靜態SQL
在這種邏輯中如果想用一條?SQL語句搞定所有查詢,那么代碼可能長這個樣子:
set statistics io on
declare @userId int = 506
declare @menuId int = 3176
select * from FoodOrder where
(@userId is null or OrderUserId = @userId) AND
(@menuId is null or FoodMenuId = @menuId)
這種寫法雖然方便,但基于其?SQL過于“復雜”,甚至還使用了?IS NULL和?OR,導致語句完全無法使用索引,運行?SET STATISTICS IO ON后,顯示信息如下:
(3 行受影響)
Table 'FoodOrder'. Scan count 1, logical reads 337, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
顯示其進行了一次表掃描,并進行了?337次邏輯讀,輸出數據只有?3行。
然后看看實際的執行計劃:?
如圖,顯示了一個極其簡單的執行計劃,確實進行了一次表掃描,讀取了?51652行數據,并且完全沒有走索引。
動態SQL
而動態?SQL,就是將查詢條件中的判斷語句,提前在代碼中判斷完成,而放到數據庫(如?SQLServer)中執行時就是簡單的、可利用索引的?SQL語句了,在這個例子中,判斷?@userId和?@menuId是否為?null的代碼,可能會長這個樣子(如果是?Dapper):
var sql = new StringBuilder();
sql.Append("SELECT * FROM FoodOrder WHERE 1=1 ");
if (userId != null)
{
sql.AppendLine("AND OrderUserId = @userId");
}
if (menuId != null)
{
sql.AppendLine("AND FoodMenuId = @menuId");
}
// ...
如果是?EF,代碼可能是這個樣子:
IQueryable<FoodOrder> query = db.FoodOrders;
if (userId != null)
{
query = query.Where(x => x.OrderUserId == userId);
}
if (menuId != null)
{
query = query.Where(x => x.FoodMenuId = menuId);
}
// ...
這樣一來,最終在數據中執行的?SQL語句就比較簡單了,如果客戶確實傳了?userId和?menuId兩個參數,?SQL就應該長這個樣子:
select * from FoodOrder where
OrderUserId = @userId AND
FoodMenuId = @menuId
運行的?setstatistics io on結果如下:
(3 行受影響)
Table 'FoodOrder'. Scan count 2, logical reads 11, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
顯然僅進行了?11次邏輯讀(相比靜態?SQL的?337次),然后執行計劃如下:?
顯示進行了兩次?IndexSeek,顯然是走了索引,顯示查詢開銷只占?5%,而之前的開銷占?95%,性能區別高達?20倍以上。
總結
據說上次博客園出現性能問題,就是因為?EFCore3.0有這個?bug,會生成多余的?IS NOT NULL(鏈接:EF Core 3.0 Preview 9 的2個小坑),這個?bug已經確認最新的?EFCore3.1中解決。
就像文中所說的動態?SQL,我認為理解數據庫、對寫出高性能的應用程序至關重要——這顯而易見,但其實又很容易忽略。忽略的原因不僅是因為新手,很多老手有時因為“互聯網”思維和“設計模式”等原因,也會有意忽略數據庫的理解。
現在很多“互聯網”應用思維認為,數據庫就是一個倉庫,它應該只負責其最“擅長”的增刪改查功能即可,其它的應該都交由緩存來解決。有句話說得好,就是命名和緩存失效,是編程界最困難的兩個問題。緩存有緩存的問題,不好好理解數據庫,就必須花大量時間好好理解緩存。設計一個正確的緩存往往又比花大量時間設計數據庫要復雜得多。
另外現在流行的“領域驅動設計”(?DDD)也主張應用應該先從業務邏輯開始抽象,數據庫和性能往往成為他們首先忽略的對象,最后可能也得加個“緩存”來解決,導致原來簡單的系統急劇膨脹,復雜不堪。這種過度設計、人云亦云的做法值得深思。
喜歡的朋友 請關注我的微信公眾號:【DotNet騷操作】
總結
以上是生活随笔為你收集整理的sql 写query_为什么需要动态SQL的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 十字链表存储
- 下一篇: android开发之 SQLite(数据