當你用 LIKE ‘%xxx%’,SQL Server 無法知道從哪裡開始搜尋,因此只能用 Table Scan
查詢
能否使用索引
原因
LIKE 'abc%'
✅ 可以
開頭是固定字串,索引能知道從哪裡開始找
LIKE 'abc%xyz'
✅ 部分可用
開頭仍固定,結尾條件在資料列比對時過濾
LIKE '%abc'
❌ 不行
無法知道從哪裡開始找,只能掃描全表
LIKE '%abc%'
❌ 不行
同上,開頭未知
=(等於)
✅ 完全可用
精確比對,最有效率
假設有一個索引在 username 欄位上,索引的順序可能是
1 2 3 4 5 6
aaron adam alex amy ben bob
SQL Server 可以從索引樹中快速定位「a」開頭的第一筆 (aaron),然後一路往後掃到不符合的那筆為止 (ben 為止),這樣的過程叫 Index Range Seek,效能 ok!
🔹 搭配其他條件
雖然 LIKE ‘%xxx%’ 不能用索引,但若搭配其他條件,可以讓 SQL Server 少掃一些資料
1 2 3 4 5
SELECT* FROM logs WHERE log_date >='2025-01-01' AND log_date <'2025-01-02' AND message LIKE'%error%';
即使 message 用不到索引,但 log_date 的條件可以讓 SQL Server 先從日期範圍內的資料掃描,縮小搜尋集!
🔹 先依索引條件撈取到 Temp Table 再做 LIKE
1 2 3 4 5 6 7 8 9 10
-- Step 1:先利用索引欄位 (log_date) 篩選資料進暫存表 SELECT* INTO #tmpLogs FROM logs WITH (NOLOCK) WHERE log_date >='2025-01-01'; -- ✅ log_date 有索引,能 Index Seek
-- Step 2:在暫存表內做 LIKE 搜尋(小範圍比對) SELECT* FROM #tmpLogs WHERE message LIKE'%error%'; -- ❌ 暫存表沒索引,但資料量已縮小
第一步用索引條件縮小資料量(Index Seek)
第二步再在少量資料上做 LIKE
💼 字串拼接
🔹 大量字串串接(loop 累加)
1 2 3 4 5 6 7 8
DECLARE@result NVARCHAR(MAX) =''; DECLARE@iINT=1;
WHILE @i<=50000 BEGIN SET@result= CONCAT(@result, 'X'); SET@i+=1; END
每次迴圈都產生新字串 → 記憶體重分配 → CPU 高
在沒有索引、無 schema 可改的情況下,這樣會極慢
✅ 改善:一次聚合字串,不逐筆累加
1 2 3 4 5 6
DECLARE@result NVARCHAR(MAX);
SELECT@result= STRING_AGG('X', '') FROM (SELECT TOP (50000) 'X'AS val FROM sys.objects) AS x;
SELECT LEN(@result);
使用 STRING_AGG()(SQL Server 2017+)
一次聚合而非逐次串接 → 省下大量 CPU 與 TempDB 資源
不需要改任何資料結構
🔹 WHERE 裡用 CONCAT 導致索引失效
1 2 3
SELECT* FROM Users WHERE CONCAT(FirstName, LastName) ='JohnDoe';
✅ 改善寫法
1 2 3
SELECT* FROM Users WHERE FirstName ='John'AND LastName ='Doe';
邏輯上等價(除非你的資料允許不同組合的空格/大小寫)
這樣 SQL Server 可獨立比對兩個欄位,而不用做函數運算
🔹 隱性轉換
1 2 3
SELECT* FROM Customers WHERE CONCAT(CustomerID, '') ='123';
CustomerID 是 INT,CONCAT() 會強制轉為 NVARCHAR → CPU 開銷 + 無法最佳化
✅ 改善寫法(型別正確)
1 2 3
SELECT* FROM Customers WHERE CustomerID = TRY_CAST('123'ASINT);
明確轉型,避免隱性轉換
Query Optimizer 能更精準預估成本(雖仍是掃描,但更穩定)
🔹 CONCAT + DISTINCT / GROUP BY 效能差
1 2
SELECTDISTINCT CONCAT(FirstName, LastName) FROM Users;
✅ 改善寫法(利用 ROW_NUMBER 篩唯一)
1 2 3 4 5 6 7
SELECT FirstName, LastName FROM ( SELECT FirstName, LastName, ROW_NUMBER() OVER (PARTITIONBY FirstName, LastName ORDERBY (SELECTNULL)) AS rn FROM Users ) AS x WHERE rn =1;
🔹 NULL 處理差異(CONCAT vs +)
用法
行為
範例結果
+(字串串接運算子)
只要任一邊是 NULL,整個結果就變成 NULL
'John' + NULL = NULL
CONCAT()(函數)
自動將 NULL 視為空字串 ‘’
CONCAT('John', NULL) = 'John'
1 2
SELECT CONCAT(FirstName, ' ', LastName) AS FullName FROM Users;