.net 如何提高SQL Server存储过程的性能?

.net 如何提高SQL Server存储过程的性能?,.net,sql-server,performance,stored-procedures,duplicates,.net,Sql Server,Performance,Stored Procedures,Duplicates,我通过企业库DAL应用程序块调用一个存储过程,并在我的过程中传递一个数据表,该数据表作为自定义表数据类型(@namesasNamesTable)被“接收”。从第二次调用开始,这个过程非常慢,我正在寻找一种不同的方法来实现它,这样性能会大大提高 Names/HistoricalNames表非常庞大(1亿条记录),传递给这些表的数据(通过dataset/table参数)大约有400万条记录) 它的基本功能(需要做的)如下: create table Names ( id int IDENTI

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

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

它的基本功能(需要做的)如下:

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
)
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
  • 导入
    @names
    (这是DataTable/Table参数
    • 检查
      名称
      历史名称
      表是否包含新数据集/表参数中包含的任何名称,如果包含,则跳过整个导入并返回2
    • 否则,在
      名称中插入
      @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
    )
    
    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
    
    表值参数(
    @names
    )如下所示:

    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
    )
    
    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
    
    这是一个过程:

    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
    )
    
    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
    

    这可以很容易地调整性能吗?任何帮助都将不胜感激!

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

    可能是这样的:

    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
    

    • 一个语句“IF NOT EXISTS”和两个CEHCK。您每次都计算完整计数,但只关心是否存在一个项,这可以更快地完成(找到一行后放弃查询)。EXISTS子句的存在就是因为这个原因

      • 表值参数几乎肯定是您的问题

        对于一个基本的ETL过程来说,使用表参数似乎很重要,但在任何情况下,表值参数都不会被索引

        因此,您将得到一个4m行的表扫描,这在关系数据库中是您永远不希望看到的


        通过将它插入一个真正的表中作为一个带有索引的暂存区域,然后对该表而不是参数执行操作,您应该可以获得巨大的提升。此外,还要确保在其他表上也有索引。

        抛开传递这么多数据听起来是个坏主意的问题,作者建议的方法是Arion是我的建议。假设您在名称列上有索引,您不需要任何有关匹配名称或匹配位置的详细信息,您只想找到第一个匹配项并返回您成功的消息

        我还将使用联接检查exists的性能:

        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
        

        你在这些表上有什么样的索引?请注意。我们在
        name
        列上有索引,但是这些没有被纳入“创建脚本”中自动…并且您在
        id
        列上有群集主键,对吗?是的,但是在表类型参数中不是这样的…我如何有效地组合这些键?只需使用一个没有参数的简单联接?如果找到重复项,此查询非常有效。但是,如果没有找到重复项,它仍然会挂起超过一个小时在尝试插入50.000条记录(其中Names表包含100.000条记录)时,需要120秒…这不是正确的解决方案吗?如何显示“活动”存储过程的执行计划?它是从.NET调用的,数据表是从.NET传递的…从SSMS运行存储过程,或者如果您没有访问权限,请让dba执行此操作-计划在那里可见。将其选择为变量(temp)是否有帮助表?或者它应该是一个真实的表吗?@ RePosibe我只使用一个真实的表。我也可能考虑一下你的整个体系结构,你将4m行发送到一个PROC,而不是一个传统的ETL过程。这只是一个非常罕见的场景。我们正在从CSV文件中提取数据,并把它加载到一个DATATATE中,它被发送到过程。数据库和应用程序服务器是分布式的,不能将CSV发送到数据库服务器。是否有另一种解决方案可以将整个CSV文件视为“批处理”?@ReFocus我不知道您的架构或动机。如果不可能,那没关系。我们在数据仓库中通常做的是将压缩文件获取到SSIS服务器,然后将其加载到数据库中的暂存表中。该过程是否使用内联查找或稍后的批处理SQL操作将取决于设计。这只是一点通过proc table参数接口发送4m行是很不寻常的,正如您所看到的,它没有索引。如果您需要重试批处理,您现在必须通过网络再次发送4m行。