繁体   English   中英

在我的情况下如何提高SQL查询性能

[英]how to improve SQL query performance in my case

我有一个表,架构非常简单,一个ID列作为唯一的主键(uniqueidentifier类型)和其他一些nvarchar列。 我当前的目标是,对于5000个输入,我需要计算表中已经包含哪些输入,哪些不包含。 输入是字符串,我有一个C#函数,它将字符串转换为uniqueidentifier(GUID)。 我的逻辑是,如果存在现有ID,则将字符串视为已包含在表中。

我的问题是,如果我需要找出数据库中已经包含的5000个输入字符串中的哪些,哪些不是,最有效的方法是什么?

顺便说一句:我当前的实现是使用C#代码将字符串转换为GUID,然后调用/实现一个存储过程,该过程查询数据库中是否存在ID并返回到C#代码。

我的工作环境:VSTS 2008 + SQL Server 2008 + C#3.5。

我的第一个直觉是将5000个输入泵入一个单列临时表X,可能对其进行索引,然后使用:

SELECT X.thecol
FROM X
JOIN ExistingTable USING (thecol)

以获取存在的内容,并且(如果需要这两个集合)

SELECT X.thecol
FROM X
LEFT JOIN ExistingTable USING (thecol)
WHERE ExistingTable.thecol IS NULL

得到那些缺席的。 至少值得进行基准测试。

编辑:根据要求,以下是有关SQL Server中的临时表的一些不错的文档和教程。 Bill Graziano有一个简单的介绍,介绍了临时表,表变量和全局临时表。 Randy DyessSQL Master讨论了支持和反对他们的性能问题(但是请记住,如果遇到性能问题,您确实希望对替代方法进行基准测试, 不仅仅是考虑理论上的问题!)。

MSDN上有关于tempdb (保存临时表)和优化其性能的文章。

步骤1.确保您要解决的问题。 在许多情况下,一次插入五千个插入并不多。

您确定最简单的方法还不够吗? 到目前为止,您衡量了哪些性能问题?

您需要对表中存在或不存在的那些条目做什么?

根据您的需求,也许SQL Server 2008中的新MERGE语句可以满足您的要求-更新现有内容,插入新内容,将所有内容整齐地包装到单个SQL语句中。 看看这个!

您的声明如下所示:

MERGE INTO 
    (your target table) AS t
USING 
    (your source table, e.g. a temporary table) AS s
ON t.ID = s.ID
WHEN NOT MATCHED THEN  -- new rows does not exist in base table
  ....(do whatever you need to do)
WHEN MATCHED THEN      -- row exists in base table
  ... (do whatever else you need to do)
;

为了使之真正快速,我将使用BULK INSERT将“新”记录从TXT或CSV文件加载到SQL Server中的临时表中:

BULK INSERT YourTemporaryTable
FROM 'c:\temp\yourimportfile.csv'
WITH 
(
    FIELDTERMINATOR =',',
    ROWTERMINATOR =' |\n'
)

大容量插入与MERGE相结合应该为您提供在这个星球上可以获得的最佳性能:-)

PS:这是TechNet上关于MERGE性能的说明,以及为什么它比个别陈述要快的原因:

在SQL Server 2008中,可以使用MERGE语句在单个语句中执行多种数据操作语言(DML)操作。 例如,您可能需要通过根据另一个表中的差异在一个表中插入,更新或删除行来同步两个表。 通常,这是通过执行包含单个INSERT,UPDATE和DELETE语句的存储过程或批处理来完成的。 但是,这意味着源表和目标表中的数据都会被评估和处理多次; 每个陈述至少要一次。 通过使用MERGE语句,可以将单个DML语句替换为单个语句。 因为操作是在单个语句中执行的,所以可以提高查询性能,从而最大程度地减少了源表和目标表中数据的处理次数。 但是,性能的提高取决于正确的索引,连接和其他考虑因素。 本主题提供最佳实践建议,以帮助您使用MERGE语句时获得最佳性能。

尝试确保最终只运行一个查询-即,如果您的解决方案包含对数据库运行5000个查询,那么这可能是该操作最大的资源消耗者。

如果您可以将5000个ID插入临时表中,则可以编写一个查询以查找数据库中不存在的ID。

如果要简化,因为5000条记录不是很多,那么从C#中只需使用循环为要添加到表中的每个字符串生成一个插入语句。 将插入物包装在TRY CATCH块中。 将它们全部发送到服务器,如下所示:

BEGIN TRY
INSERT INTO table (theCol, field2, field3)
SELECT theGuid, value2, value3
END TRY BEGIN CATCH END CATCH

BEGIN TRY
INSERT INTO table (theCol, field2, field3)
SELECT theGuid, value2, value3
END TRY BEGIN CATCH END CATCH

BEGIN TRY
INSERT INTO table (theCol, field2, field3)
SELECT theGuid, value2, value3
END TRY BEGIN CATCH END CATCH

如果在字符串GUID上定义了唯一索引或主键,则重复插入将失败。 提前检查以查看记录是否不存在,只是重复了SQL将要做的工作。

如果性能确实很重要,请考虑将5000 GUIDS下载到本地工作站并进行所有本地分析。 读取5000 GUIDS应该花费不到1秒的时间。 这比批量导入到临时表(这是从临时表中获得性能的唯一方法)并使用对临时表的联接进行更新要简单得多。

绝对不要一一对应。

我的首选解决方案是使用一个可以采用以下参数的参数创建存储过程,并且XML采用以下格式:

<ROOT>
  <MyObject ID="60EAD98F-8A6C-4C22-AF75-000000000000">
  <MyObject ID="60EAD98F-8A6C-4C22-AF75-000000000001">
  ....
</ROOT>

然后,在使用类型为NCHAR(MAX)的参数的过程中,将其转换为XML,然后将其用作具有单列的表(将其称为@FilterTable)。 存储过程如下:

CREATE PROCEDURE dbo.sp_MultipleParams(@FilterXML NVARCHAR(MAX))
AS BEGIN
    SET NOCOUNT ON

    DECLARE @x XML
    SELECT @x = CONVERT(XML, @FilterXML)

    -- temporary table (must have it, because cannot join on XML statement)
    DECLARE @FilterTable TABLE (
         "ID" UNIQUEIDENTIFIER
    )

    -- insert into temporary table
    -- @important: XML iS CaSe-SenSiTiv
    INSERT      @FilterTable
    SELECT      x.value('@ID', 'UNIQUEIDENTIFIER')
    FROM        @x.nodes('/ROOT/MyObject') AS R(x)

    SELECT      o.ID,
                SIGN(SUM(CASE WHEN t.ID IS NULL THEN 0 ELSE 1 END)) AS FoundInDB
    FROM        @FilterTable o
    LEFT JOIN   dbo.MyTable t
            ON  o.ID = t.ID
    GROUP BY    o.ID

END
GO

您将其运行为:

EXEC sp_MultipleParams '<ROOT><MyObject ID="60EAD98F-8A6C-4C22-AF75-000000000000"/><MyObject ID="60EAD98F-8A6C-4C22-AF75-000000000002"/></ROOT>'

您的结果如下所示:

ID                                   FoundInDB
------------------------------------ -----------
60EAD98F-8A6C-4C22-AF75-000000000000 1
60EAD98F-8A6C-4C22-AF75-000000000002 0

由于使用的是Sql Server 2008,因此可以使用表值参数。 这是一种提供表作为存储过程参数的方法。

使用ADO.NET,您可以轻松地预先填充DataTable并将其作为SqlParameter传递。 您需要执行的步骤:

创建一个自定义Sql类型

CREATE TYPE MyType AS TABLE
(
UniqueId INT NOT NULL,
Column NVARCHAR(255) NOT NULL
) 

创建一个接受类型的存储过程

CREATE PROCEDURE spInsertMyType
@Data MyType READONLY
AS 
xxxx

使用C#调用

SqlCommand insertCommand = new SqlCommand(
   "spInsertMyType", connection);
 insertCommand.CommandType = CommandType.StoredProcedure;
 SqlParameter tvpParam = 
    insertCommand.Parameters.AddWithValue(
    "@Data", dataReader);
 tvpParam.SqlDbType = SqlDbType.Structured;

链接: SQL 2008中的表值参数

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM