如何优化 NOT IN 语句

Ros*_*ana 7 sql-server optimization index-tuning

为什么NOT IN执行INDEX SCAN而不是INDEX SEEK

这是我目前在桌子上的索引,

CREATE NONCLUSTERED INDEX [idx_dmcasarms_CourseList_cDesc] 
ON [dbo].[courselist]
(
    [c_desc] ASC,
    [c_code] ASC
)
Run Code Online (Sandbox Code Playgroud)

SQL语句,

SELECT  c_code CourseCode
FROM    courselist
WHERE   c_desc = 'BACHELOR OF SCIENCE IN INFORMATION TECHNOLOGY'
GO

SELECT  c_code CourseCode
FROM    courselist
WHERE   c_desc NOT IN ('PRE SCHOOL', 'BASIC EDUCATION', 'SCIENCE HIGH SCHOOL')
GO
Run Code Online (Sandbox Code Playgroud)

和执行计划,

在此处输入图片说明

无论如何我可以优化这个吗?

小智 7

它执行扫描,因为它必须找到不在列出的值中的所有值。想象一下,在电话簿中查找所有不是“Smith”的姓氏值。然后你必须扫描整本书。如果您只需要找到姓氏值为“Smith”的人,那么您可以只查找该部分。

至于如何优化,不幸的是,如果扫描成本太高而无法接受,则需要重新设计逻辑以避免 NOT IN。因此,如果您能以某种方式将 NOT IN 更改为 IN 或 =,那么对数据库引擎来说会更好。当然,这有时是不可能的,然后您必须权衡查询成本与重新设计逻辑的成本。


Mar*_*ith 7

您也可以将查询编写为

SELECT  c_code CourseCode
FROM    courselist
WHERE   c_desc < 'BASIC EDUCATION'
UNION ALL
SELECT  c_code CourseCode
FROM    courselist
WHERE   c_desc > 'BASIC EDUCATION' AND c_desc < 'PRE SCHOOL'
UNION ALL
SELECT  c_code CourseCode
FROM    courselist
WHERE   c_desc > 'PRE SCHOOL' AND c_desc < 'SCIENCE HIGH SCHOOL'
UNION ALL
SELECT  c_code CourseCode
FROM    courselist
WHERE   c_desc > 'SCIENCE HIGH SCHOOL'
Run Code Online (Sandbox Code Playgroud)

获得四个范围搜索。

除非任何NOT IN值非常常见,否则我希望这会更糟而不是更好。

如果这些值是通用的,那么上次我看这个时,SQL Server 最好的做法是跳过第一个索引值并扫描其余的值。

一个完全操纵的例子,看看这可能更好的情况......

CREATE TABLE [dbo].[courselist](
[c_desc] VARCHAR(20),
[c_code] CHAR(880))

CREATE NONCLUSTERED INDEX [idx_dmcasarms_CourseList_cDesc] 
ON [dbo].[courselist]([c_desc] ASC,[c_code] ASC);

WITH T AS (SELECT TOP (10000) v1.number FROM  master..spt_values v1, master..spt_values v2)
INSERT INTO [dbo].[courselist]
SELECT X, number 
FROM T
CROSS JOIN (VALUES ('PRE SCHOOL'),
                   ('SCIENCE HIGH SCHOOL')) V(X)
UNION ALL                   
SELECT REPLICATE('A',20), 1    
Run Code Online (Sandbox Code Playgroud)

在我的带有热缓存的机器上,它进行了NOT IN一次完整扫描和 3,132 次逻辑读取,时间为 20 毫秒。而多次搜索显示 28 次读取和 0 毫秒的经过时间。