从C#调用SQL select语句数千次,非常耗时。有没有更好的办法?

时间:2014-09-03 20:37:52

标签: c# sql sql-server

我从excel文件中获取ID和数量列表(数千个ID和相应的金额)。然后我需要检查数据库以查看每个ID是否存在以及是否检查以确保DB中的金额大于或等于excel文件中金额的金额。

问题是运行此select语句超过6000次并返回我需要的值需要很长时间。即使在1/2秒的时间内,所有选择也需要大约一个小时。 (我通常最多不会得到超过5个结果)

有更快的方法吗?

是否有可能以某种方式同时传递所有ID并只进行1次呼叫并获得大量收集?

我尝试过使用SqlDataReaders和SqlDataAdapters但它们似乎差不多(无论哪种方式都太长)

以下是如何工作的一般概念

for (int i = 0; i < ID.Count; i++)
{
    SqlCommand cmd = new SqlCommand("select Amount, Client, Pallet from table where ID = @ID and Amount > 0;", sqlCon);

    cmd.Parameters.Add("@ID", SqlDbType.VarChar).Value = ID[i];

    SqlDataAdapter da = new SqlDataAdapter(cmd);

    da.Fill(dataTable);
    da.Dispose();           
}

12 个答案:

答案 0 :(得分:13)

而不是长in列表(难以参数化并且有许多关于执行计划的低效率:编译时间,计划重用和计划本身),您可以通过一次性传递所有值表值参数。

有关详细信息,请参阅arrays and lists in SQL Server

通常,我确保为表类型指定主键,并使用option (recompile)来获取最合适的执行计划。

答案 1 :(得分:4)

将所有ID组合成一个大的IN子句,如下所示:

select Amount, Client, Pallet from table where ID in (1,3,5,7,9,11) and Amount > 0;

答案 2 :(得分:2)

“我尝试过使用SqlDataReaders和SqlDataAdapters”

听起来您可能对其他API持开放态度。使用Linq2SQL或Linq2Entities:

var someListIds = new List<int> { 1,5,6,7 }; //imagine you load this from where ever
db.MyTable.Where( mt =>  someListIds.Contains(mt.ID) );

这在避免潜在的SQL注入漏洞方面是安全的,并且会生成“in”子句。但请注意,someListIds的大小可能太大,以至于生成的SQL查询超出了查询长度的限制,但涉及IN子句的任何其他技术也是如此。您可以通过将列表分成大块来轻松解决这个问题,并且仍然比每个ID的查询要好得多。

答案 3 :(得分:2)

使用Table-Valued Parameters

使用它们,您可以将带有值的c#datatable传递给存储过程作为结果集/表,您可以加入并执行一个简单的操作:

SELECT * 
FROM YourTable 
WHERE NOT EXISTS (SELECT * FORM InputResultSet WHERE YourConditions)

答案 4 :(得分:1)

使用in运算符。您的问题非常常见,并且名称为N+1 performance problem

你从哪里获得身份证?如果它来自另一个查询,则考虑将它们分组为一个。

答案 5 :(得分:1)

不是对您拥有的每个ID执行单独的查询,而是执行一个查询以获取您要检查的每个ID 的数量(或者如果你有太多的ID放在一个查询中,然后将它们批量分成几千个。)

答案 6 :(得分:1)

将数据直接导入SQL Server。使用存储过程输出所需的数据。

如果必须在应用层使用它...使用xml数据类型传递给存储过程。

答案 7 :(得分:1)

您可以将excel文件中的数据作为表格导入SQL Server(使用导入数据向导)。然后,您可以在SQL服务器中执行单个查询,将此表连接到查找表,并在ID字段上加入。这个过程还有一些步骤,但它比将所有ID连接成更长的查询要简洁得多。

我在这里假设对服务器有一定的访问权限,但这是我通常所拥有的访问权限。我还假设这是一次性的任务。如果没有,则可以通过编程方式将数据导入SQL服务器

答案 8 :(得分:1)

IN 子句有限制,因此如果您采用这种方法,请确保批量大小一次用于处理X量的ID,否则您将遇到另一个问题

@Robertharvey已经注意到,如果没有很多ID并且没有发生任何事务,那么只需将所有ID一次性地拉入内存到类似对象的字典中并在那里处理它们。六千个值不是很多,一个选择可以在几秒钟内返回所有这些值。

请记住,如果另一个进程正在更新数据,则您的本地缓存版本可能过时。

答案 9 :(得分:1)

还有另一种处理方法,即制作ID的XML并将其传递给过程。这是程序代码。

IF OBJECT_ID('GetDataFromDatabase') IS NOT NULL
    BEGIN
        DROP PROCEDURE GetDataFromDatabase
    END
GO

--Definition
CREATE PROCEDURE GetDataFromDatabase
@xmlData XML
AS
BEGIN
DECLARE @DocHandle INT
DECLARE @idList Table (id INT)

EXEC SP_XML_PREPAREDOCUMENT  @DocHandle OUTPUT, @xmlData;   

INSERT INTO @idList (id) SELECT x.id FROM OPENXML(@DocHandle, '//data', 2) WITH ([id] INT) x

EXEC SP_XML_removeDOCUMENT  @DocHandle ;
--SELECT * FROM @idList

SELECT t.Amount, t.Client, t.Pallet FROM yourTable t INNER JOIN  @idList x ON t.id = x.id and t.Amount > 0;
END
GO

--Uses
EXEC GetDataFromDatabase @xmlData = '<root><data><id>1</id></data><data><id>2</id></data></root>'

您可以在程序中添加任何逻辑。您也可以通过XML传递id,amount。您可以通过XML传递大量的ID。

答案 10 :(得分:0)

您可以选择整个结果集(或加入多个'有限'结果集)并将其全部保存到DataTable然后您可以直接在数据表上进行选择和更新(如果需要)。然后重新插入新数据...不是超级高效的内存,但在批量工作时通常是非常好(并且只有)解决方案,需要它非常快。 因此,如果您有数千条记录,则可能需要几分钟才能将所有记录填充到DataTable

然后你可以像这样搜索你的表:

string findMatch = "id = value";
DataRow[] rowsFound = dataTable.Select(findMatch);

然后循环foreach (DataRow dr in rowsFound)

答案 11 :(得分:0)

SqlDataAdapter对象太重了。 首先,使用存储过程会更快。 其次,使用组操作,将此传递作为参数访问数据库侧的标识符列表,对这些参数运行查询,并返回处理结果。 它将快速有效,因为所有数据处理逻辑都位于数据库服务器一侧