【问题标题】:How to improve SQL Server stored procedure performance?如何提高 SQL Server 存储过程的性能?
【发布时间】:2012-03-02 02:29:24
【问题描述】:

我正在通过企业库 DAL 应用程序块调用存储过程,并在我的过程中传递一个 DataTable,该 DataTable 又作为自定义 Table 数据类型(@names 作为NamesTable)被“接收”。从第二次调用开始,该过程非常慢,我正在寻找一种不同的方法来实现它,以便大大提高性能。

Names/HistoricalNames 表很大(1 亿条记录),传递给这些表的数据(通过 dataset/table 参数)大约有 400 万条记录。

基本上它所做的(需要做的)如下:

  1. 导入@names(即DataTable/Table参数
    • 检查NamesHistoricalNames 表是否包含新数据集/表参数中包含的任何名称,如果是则跳过整个导入并返回 2
    • 否则将@names中的所有记录插入Names并返回1;

表格如下所示:

create table Names
(
    id int IDENTITY(1,1) NOT NULL,
    name nvarchar(20),
    otherId uniqueidentifier
)

create table HistoricalNames
(
    id int IDENTITY(1,1) NOT NULL,
    name nvarchar(20),
    otherId uniqueidentifier
)

表值参数 (@names) 如下所示:

create table NameTable
(
    name nvarchar(20)
    otherId uniqueidentifier
)

这是程序:

GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [dbo].[sp_ImportNames]
    @names NameTable READONLY
AS
BEGIN       
    IF ((SELECT COUNT(cd.name) FROM Names as cd WHERE cd.name IN (SELECT c.name FROM @names as c)) > 0)
        BEGIN
            SELECT 2;
        END
    ELSE IF ((SELECT COUNT(cd.name) FROM HistoricalNames as cd WHERE cd.name IN (SELECT c.name FROM @names as c)) > 0)
        BEGIN
            SELECT 2;
        END
    ELSE
        BEGIN
            INSERT INTO Names (name, otherId) SELECT * FROM @names;
            SELECT 1;
        END
END


GO

能否轻松调整性能?任何帮助将不胜感激!

【问题讨论】:

  • 你在这些表上有什么样的索引?
  • 正确。我们在name 列上有索引,但是这些索引并没有自动纳入“创建脚本”...
  • 你已经在id 列上聚集了主键,对吧?
  • 是的,但是在表类型参数中不是这种情况...

标签: .net sql-server performance stored-procedures duplicates


【解决方案1】:

表值参数几乎肯定是你的问题。

Table Valued Parameter has slow performance because of table scan

对于基本的 ETL 过程来说,使用表参数似乎很多,但无论如何,表值参数没有被索引。

因此,您将获得 4m 行的表扫描,这在关系数据库中绝不是您希望看到的。

您应该通过将它作为带有索引的暂存区域插入到 REAL 表中,然后对该表而不是参数进行操作来获得巨大的提升。另外,请确保您在其他表上也有索引。

【讨论】:

  • 将其选择到 VARIABLE (temp) 表中是否有帮助?还是应该是一张真实的桌子?
  • @ReFocus stackoverflow.com/questions/886050/… 我会用一张真正的桌子。我还考虑可能会审查您将 4m 行发送到 proc 与传统 ETL 过程的整体架构。这只是一个非常罕见的情况。
  • 我们正在从 CSV 文件中提取数据并将其加载到发送到 proc 的 DataTable 中。数据库和应用程序服务器是分布式的,不能将 CSV 发送到数据库服务器。是否有另一种解决方案可以将整个 CSV 文件视为“批处理”?
  • @ReFocus 我不知道你的架构或动机。如果这不可能,那很好。我们通常在数据仓库中所做的是将压缩文件获取到 SSIS 服务器,然后将它们加载到数据库中的暂存表中。该过程是使用内联查找还是稍后使用批处理 SQL 操作取决于设计。通过 proc 表参数接口发送 4m 行是非常不寻常的 - 正如您所见,它没有被索引。如果您有重试批次的要求,您现在必须通过网络再次发送 4m 行。
【解决方案2】:

可能是这样的:

GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [dbo].[sp_ImportNames]
    @names NameTable READONLY
AS
BEGIN       
    IF EXISTS(SELECT NULL FROM Names as cd WHERE EXISTS(SELECT NULL FROM @names as c WHERE c.name=cd.name))
        BEGIN
            SELECT 2;
        END
    ELSE IF EXISTS(SELECT NULL FROM HistoricalNames as cd WHERE EXISTS(SELECT NULL FROM @names as c WHERE c.name=cd.name))
        BEGIN
            SELECT 2;
        END
    ELSE
        BEGIN
            INSERT INTO Names (name, otherId) SELECT * FROM @names;
            SELECT 1;
        END
END

【讨论】:

  • 如果发现重复,此查询非常有用。但是,当没有发现重复项时,在尝试插入 Names 表包含 100.000 条记录的 50.000 条记录时,它仍然会挂起超过 120 秒...这不是解决方案吗?
【解决方案3】:

打开实际执行计划显示 - 这将显示性能更差的地方。

【讨论】:

  • 如何显示“实时”存储过程的执行计划?它是从 .NET 中调用的,其中数据表是从中传递的......
  • 从 SSMS 运行存储过程,或者如果您无权访问,请让 dba 执行此操作 - 计划在那里可见。
【解决方案4】:

嗯。

  • 一个带有 2 个 cehcks 的语句“IF NOT EXISTS”。您每次都计算完整计数,但只对是否存在一项的指示感兴趣,这可以更快地完成(一旦找到一行就放弃查询)。 EXISTS 子句就是因为这个原因而存在的。

【讨论】:

  • 如何有效地结合这些?只需使用不带参数的简单 JOIN?
【解决方案5】:

抛开传递大量数据听起来是个坏主意的问题不谈,Arion 建议的方法就是我所建议的。假设您在名称列上有索引,您只想找到第一个匹配项并返回您成功的信息,您不需要任何有关哪些名称匹配或他们在哪里匹配的详细信息。

我还会使用连接检查存在的性能:

if exists(select 1
from Names exist
inner join @names newNames on newNames.name = exist.name)
begin
  select 2;
end

还请注意,对于“不匹配”情况,通常建议在插入时显式使用列名:

insert into Names (name, otherId)
select name, otherId
from @names

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-28
    • 2017-06-20
    相关资源
    最近更新 更多