如何优化这个嵌套查询?

How can I optimize this nested query?

我希望就我为工作编写的查询获得一些意见。简而言之,它使用 select 和一些连接来显示来自工厂生产线的组装零件的数据。查询效果很好,但执行大约需要 40 秒,当我查看多个部分(一次一个)时,这是非常无用的。

我可以在这里做些什么来优化它吗?我看到我无法控制的最大吸引力是 "item_xref" table 没有索引,这会减慢速度。

项目table包含零件的状态,模型table包含项目模型的信息,item_xreftable用于记录任何两个部分相互连接。结果是:我扫描一个零件上的序列码,它显示该零件的所有组件,然后是组件模型的描述,以及添加日期和添加它的站点。这用英语向我显示了附加的部件,而不是大量的序列号列表。

我也有一些参数来隐藏系统记录的某些站点和组件,如螺丝和夹子,但在报告中不需要我的数据。

select 
    i.ITEM_ID as "Components of Target", 
    m.DESCRIPTION as "Component Type",
    ix.LAST_UPDATE as "Time added",
    ix.LAST_USER as "Added by"
from item_xref as ix with (nolock) 
join item as i with (nolock) on ix.COMPONENT_ITEM_ID = i.ITEM_ID 
join model as m with (nolock) on i.MODEL_ID = m.MODEL_ID 
where ix.COMPONENT_ITEM_ID in (
    select component_item_id 
    from item_xref 
    where 
        item_ID = @variable 
        and COMPONENT_ITEM_ID not like '1T%' 
        and COMPONENT_ITEM_ID not like 'T4%' 
        and LAST_USER not like '%IMM%'
        and LAST_USER not like '%HST%'
    )

感谢您的任何建议!

你可以将其更改为这样的联接

select 
    i.ITEM_ID as "Components of Target", 
    m.DESCRIPTION as "Component Type",
    ix.LAST_UPDATE as "Time added",
    ix.LAST_USER as "Added by"
from item_xref as ix with (nolock) 
join item as i with (nolock) on ix.COMPONENT_ITEM_ID = i.ITEM_ID 
join model as m with (nolock) on i.MODEL_ID = m.MODEL_ID 
join item_xref as limit on ix.COMPONENT_ITEM_ID = limit.component_item_id 
        and limit.item_id = @variable 
        and left(limit.COMPONENT_ITEM_ID,2) <> '1T' 
        and left(limit.COMPONENT_ITEM_ID,2) <> 'T4' 
        and limit.LAST_USER not like '%IMM%'
        and limit.LAST_USER not like '%HST%'

这取决于您的优化器可能会更快。

然而,真正缓慢的部分将是

limit.LAST_USER not like '%IMM%' and limit.LAST_USER not like '%HST%'

由于开头的通配符,它​​必须查看所有元素并且可以使用索引

如果有办法不使用这个过滤器,我希望它会更快。

select
    x.component_item_id as "Components of Target", 
    m.DESCRIPTION as "Component Type",
    x.LAST_UPDATE as "Time added",
    x.LAST_USER as "Added by"
from 
(
    select top (100000) component_item_id, LAST_UPDATE, LAST_USER
    from item_xref with (nolock)
    where 
        item_ID = @variable 
        and COMPONENT_ITEM_ID not like '1T%' 
        and COMPONENT_ITEM_ID not like 'T4%' 
        and LAST_USER not like '%IMM%'
        and LAST_USER not like '%HST%'
) as x
join item as i with (nolock) on x.component_item_id = i.ITEM_ID 
join model as m with (nolock) on i.MODEL_ID = m.MODEL_ID; 

您的查询和数据库设计中的某些问题肯定要修复。

首先,所有 table 都应该被适当地索引以达到最佳性能。您写道:我无法控制的最大吸引力是 "item_xref" table 未编入索引。 为什么你不能控制这个?谁在控制?为了最好地执行查询,所有 table 都必须具有最少数量的索引,至少对于连接条件中涉及的列(例如 "join item as i with (nolock) on ix.COMPONENT_ITEM_ID = i.ITEM_ID"、COMPONENT_ITEM_ID 应该被索引,如果它不是 table).

的聚集索引

我强烈建议您找到控制 table(s) 设计的人来实现所需的索引.

我现在将通过连续迭代来寻找最佳结果。某些迭代可能需要更改 table 或创建索引的权限,因此,如果您没有这些权限,请找到有权限的人。

第一次迭代:消除嵌套查询

来到嵌套查询本身,因为它的范围与主查询 (item_xref) 相同 table,你可以避免它,摆脱嵌套查询和 IN 子句,即不是 Sql 服务器中性能最高的操作之一。您既可以在 JOIN 中转换它,也可以直接将条件应用于主查询(我更喜欢)。 那么您的查询变为:

select 
    i.ITEM_ID as "Components of Target", 
    m.DESCRIPTION as "Component Type",
    ix.LAST_UPDATE as "Time added",
    ix.LAST_USER as "Added by"
from item_xref as ix with (nolock) 
join item as i with (nolock) on ix.COMPONENT_ITEM_ID = i.ITEM_ID 
join model as m with (nolock) on i.MODEL_ID = m.MODEL_ID 
where ix.item_ID = @variable 
  and ix.COMPONENT_ITEM_ID not like '1T%' 
  and ix.COMPONENT_ITEM_ID not like 'T4%' 
  and ix.LAST_USER not like '%IMM%'
  and ix.LAST_USER not like '%HST%'

第二次迭代:点赞不高效

您的查询中有两种 LIKE 语句:"Starts with" like(例如 ix.COMPONENT_ITEM_ID not like '1T%')和 "Contains" like(例如 ix.LAST_USER不像 '%IMM%')。两者都不是高效的,但后者是您在 Sql 服务器中可以做的最低效的事情之一 :) 当您发现自己需要使用 LIKE 语句时,问问自己您想要完成什么。 在我看来,您基本上是 将您的记录聚类 在特定的组中。 有

  and ix.COMPONENT_ITEM_ID not like '1T%' 

  and ix.COMPONENT_ITEM_ID not like 'T4%' 

你基本上是在说 "I don't want records that are in group " 以 1T 开头" 或 "Starts with T4"。所以基本上你有组件类别,由它们的前 2 个字母标识 COMPONENT_ITEM_ID。 您知道这些组件在某种程度上是不同的,并且您应该将这些知识转移到您的数据库设计和客户端应用程序中。 但是让我们假设您不能创建 item_category table 和创建外键。 如果您拥有更改 table 和创建索引的权限,您仍然可以做一些事情:您可以利用计算列的强大功能。 计算列不是 table 中的物理列,而是在每次读取操作中包含计算列时使用其他列的规则进行计算(如 "SELECT MyComputedColumn")。有趣的是,如果您在计算列上创建索引,该索引实际上会持久化,并在 table 上的每个 INSERT 或 UPDATE 时更新。 我的建议是在 item_xref table 中创建一个计算列并为其创建索引。 让我们看一些代码:

ALTER TABLE dbo.item_xref ADD
ITEM_CATEGORY  AS LEFT(COMPONENT_ITEM_ID, 2)
GO
CREATE NONCLUSTERED INDEX [IX_ItemCategory] ON [dbo].[item_xref]
(
    [ITEM_CATEGORY] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, 
OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF)

"Contains" 类似的语句更复杂,但适用相同的概念:

ALTER TABLE dbo.item_xref ADD
LAST_USER_GROUP  AS CASE 
       WHEN LAST_USER LIKE '%IMM%' THEN 'IMM'
       WHEN LAST_USER LIKE '%HST%' THEN 'HST'
       -- add other cases if needed
       ELSE '---'
    END
GO
CREATE NONCLUSTERED INDEX [IX_LastUserGroup] ON [dbo].[item_xref]
(
    [LAST_USER_GROUP] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, 
OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF)

这样做你的查询可以变成:

select 
    i.ITEM_ID as "Components of Target", 
    m.DESCRIPTION as "Component Type",
    ix.LAST_UPDATE as "Time added",
    ix.LAST_USER as "Added by"
from item_xref as ix with (nolock) 
join item as i with (nolock) on ix.COMPONENT_ITEM_ID = i.ITEM_ID 
join model as m with (nolock) on i.MODEL_ID = m.MODEL_ID 
where ix.item_ID = @variable 
  and ix.COMPONENT_ITEM_ID <> '1T' 
  and ix.COMPONENT_ITEM_ID <> 'T4' 
  and ix.LAST_USER <> 'IMM'
  and ix.LAST_USER <> 'HST'

这应该比您的原始查询快得多(我建议您使用探查器验证并检查执行计划)。 最后注意:注意 WITH(NOLOCK) 提示,它等同于 READ_UNCOMMITTED,导致脏或重复的结果。一旦查询运行得足够快,您可能就不再需要它了。

可能的其他迭代

如果您不需要查询实时数据,但足以使用特定时期的旧数据,您可以安排每 {period} 拍摄一次数据库快照并查询快照而不是实时数据库。这样你就不会因为写操作而受到任何锁定。这在计划报告中很有用,通常,当您必须每天或每周构建报告时:您首先创建/更新数据库快照,然后查询它。但是请注意:快照将获取与主数据库数据文件相同的 space,因此请检查您的磁盘 space!