如何优化这个嵌套查询?
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!
我希望就我为工作编写的查询获得一些意见。简而言之,它使用 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!