首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> SQL Server 資料庫 >> 關於SqlServer

SQL 查詢優化核心原則與 20+ 實戰技巧完整指南

     編輯:關於SqlServer

優化核心原則

目標:將單次查詢讀取的資料頁數降至最低。

一、查詢重寫規則

規則說明範例
明確列出欄位禁用 SELECT *,減少 I/O 與網路傳輸SELECT Id, Name FROM Users;
高選擇性條件前置WHERE 子句中,過濾率高的條件寫在前面WHERE Status=1 AND CreateDate>'2023-01-01'
善用索引覆蓋SELECT 欄位全部包含在非叢集索引中,避免鍵查找CREATE INDEX IX_Users_Cover ON Users(Status) INCLUDE(Name, Email);
存在性檢查用 EXISTS勿用 COUNT(*) > 0,EXISTS 遇第一筆即停止IF EXISTS (SELECT 1 FROM Orders WHERE CustID=@CustID)
內層限定原則子查詢/衍生表中盡早套用篩選條件見下方「子查詢優化」
避免 ORDER BY 運算式ORDER BY YEAR(DateCol) 導致排序無法用索引改用計算欄位+索引

二、讓索引生效的 SARGable 寫法

反模式 (非 SARGable)優化寫法 (SARGable)原因
WHERE Salary*2 > 10000WHERE Salary > 5000欄位參與運算導致索引失效
WHERE DeptID <> 10WHERE DeptID < 10 OR DeptID > 10<> 無法尋找,改用範圍
WHERE SUBSTR(Name,1,3)='ABC'WHERE Name LIKE 'ABC%'函數導致掃描,LIKE 開頭固定可用索引
WHERE Name LIKE '%ABC'全文檢索 / 反向索引 / 計算欄位前置萬用字元無法用 B-tree 索引
WHERE Name LIKE @var + '%'WHERE Name BETWEEN @var AND @var + CHAR(255)變數導致參數化失敗,BETWEEN 可用索引
WHERE Col IS NULL設計為 NOT NULL + DEFAULTNULL 不進索引(過濾索引除外)
WHERE FirstName+' '+LastName='John Doe'WHERE FirstName='John' AND LastName='Doe'串聯運算破壞索引
WHERE ID IN (1,2,3)拆分為多條 OR 或 UNION ALLIN 列表常轉為「OR 策略」產生臨時表

複合索引最左前綴原則

索引 (Col1, Col2, Col3) 僅以下 WHERE 能有效使用:

  • WHERE Col1=@v1 AND Col2=@v2 AND Col3=@v3
  • WHERE Col1=@v1 AND Col2=@v2
  • WHERE Col1=@v1

跳過 Col1 直接查 Col2、Col3 將導致索引掃描。

三、子查詢優化策略

IN / EXISTS → JOIN

-- 原始
SELECT EmpID FROM Emp WHERE DeptID IN (SELECT DeptID FROM Dept WHERE DeptNo='001');
-- 優化 (DeptID 唯一)
SELECT e.EmpID FROM Emp e JOIN Dept d ON e.DeptID=d.DeptID WHERE d.DeptNo='001';
-- 優化 (DeptID 不唯一)
SELECT e.EmpID FROM Emp e JOIN (SELECT DISTINCT DeptID FROM Dept WHERE DeptNo='001') d ON e.DeptID=d.DeptID;

NOT IN / NOT EXISTS → LEFT JOIN ... IS NULL

SELECT e.EmpID FROM Emp e
LEFT JOIN Dept d ON e.DeptID=d.DeptID AND d.DeptNo='001'
WHERE d.DeptID IS NULL;

自相關子查詢 → 衍生表聚合

-- 原始:各部門薪資高於平均者
SELECT * FROM Salary a
WHERE a.Amount > (SELECT AVG(b.Amount) FROM Salary b WHERE b.DeptID=a.DeptID);

-- 優化:預先聚合
SELECT a.* FROM Salary a
JOIN (SELECT DeptID, AVG(Amount) AS AvgAmt FROM Salary GROUP BY DeptID) b
  ON a.DeptID=b.DeptID
WHERE a.Amount > b.AvgAmt;

四、參數化與計畫重用

-- 未參數化:每次不同值產生新計畫,污染 Plan Cache
SELECT * FROM Orders WHERE OrderID = 1001;
SELECT * FROM Orders WHERE OrderID = 1002;

-- 參數化:單一計畫重用
DECLARE @OrderID INT = 1001;
SELECT * FROM Orders WHERE OrderID = @OrderID;
-- 或使用 sp_executesql
EXEC sp_executesql N'SELECT * FROM Orders WHERE OrderID = @ID', N'@ID INT', @ID=1001;

實測:1000 次迴圈查詢,參數化後編譯時間從 18 秒降至 0.56 秒。

五、減少往返次數

-- 兩次查詢:先檢查再決定 INSERT/UPDATE
IF NOT EXISTS (SELECT 1 FROM Item WHERE ID=@ID) INSERT ... ELSE UPDATE ...

-- 單次 UPSERT (SQL Server 2008+ MERGE 或 OUTPUT 子句)
UPDATE Item SET Qty=Qty-@Qty WHERE ID=@ID;
IF @@ROWCOUNT=0 INSERT INTO Item (ID, Qty) VALUES (@ID, -@Qty);

六、UNION ALL 替代 UNION

業務邏輯確定無重複時,UNION ALL 省去排序去重成本。

七、索引維護與統計資訊

  • 定期 UPDATE STATISTICS 或啟用 AUTO_UPDATE_STATISTICS。
  • 碎片 > 30% → ALTER INDEX ... REBUILD;10-30% → REORGANIZE。
  • 使用 sys.dm_db_index_usage_stats 找出未使用索引並移除。

八、常見等待類型對應優化方向

等待類型可能原因對策
PAGEIOLATCH_SH/EX磁碟 I/O 瓶頸增加記憶體、優化查詢減少讀取、SSD/分離資料檔
LCK_M_*封鎖/死結縮短交易、調整隔離等級、索引優化減少掃描
RESOURCE_SEMAPHORE查詢記憶體授予不足調高 max server memory、優化查詢計畫、Resource Governor
CXPACKET平行查詢負載不均調整 MAXDOP、Cost Threshold for Parallelism
WRITELOG記錄檔寫入延遲記錄檔放獨立快速磁碟、檢查自動增長設定
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved