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

时间:2012-02-09 15:40:55

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

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

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

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

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

    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
    

    这可以轻松调整性能吗?任何帮助将不胜感激!

5 个答案:

答案 0 :(得分:3)

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

Table Valued Parameter has slow performance because of table scan

基本的ETL过程使用表参数似乎相当多,但无论如何,表值参数都没有编入索引。

所以你得到一个4米的行表扫描,这绝不是你想在关系数据库中看到的。

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

答案 1 :(得分: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

答案 2 :(得分:1)

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

答案 3 :(得分:1)

Hm中

  • 使用2个cehcks的一个声明“IF NOT EXISTS”。您每次都计算完整的计数,但只关注是否存在一个项目,这可以更快地完成(一旦找到一行就放弃查询)。出于这个原因,存在EXISTS子句。

答案 4 :(得分:1)

抛开那些传递大量数据的问题听起来不错,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