3

在我的应用程序中,我将 TADOQuery 与 select (MSSQL) 一起使用并与 TClientDataSet 链接。我必须插入大约百万条记录和 ApplyUpdates。

那么我在 SQL Server Profiler 中看到了什么?我看到对于每个插入的行,我们有 3 个查询:插入脚本的 sp_prepare、带有一些值的 sp_execute 和 sp_unprepare。

我只想在插入之前为所有记录准备一次sql,然后再取消准备。我该怎么做?

之后添加

在查询中,我有一个用于执行存储过程的脚本:

tmpQuery := DefineQuery(FConnection, [
  'exec up_getOperatorDataSet ',
  '  @tablename     = :tablename, ',
  '  @operator      = :operator, ',
  '  @forappend     = :forappend, ',
  '  @withlinksonly = :withlinksonly, ',
  '  @ids           = :ids '
], [
  Param(ftString, sTableName),
  Param(ftInteger, FOperatorId),
  Param(ftBoolean, opForAppendOnly in OpenParams),
  Param(ftBoolean, opOnlyWithModelLinks in OpenParams),
  Param(ftString, sIds)
], Result);

它使用一些参数从表sTableName中选择所有字段。

从分析器插入的示例:

第1步:

declare @p1 int
set @p1=486
exec sp_prepare @p1 output,N'@P1 int,@P2 int,@P3 datetime,@P4 int,@P5 int,@P6 int,@P7 int,@P8 int,@P9 varchar(128),@P10 bit,@P11 numeric(19,4),@P12 smallint,@P13 smallint,@P14 smallint,@P15 smallint',N'insert into parser_prices
  (operator_id, request_id, date, nights, model_hotel_id, model_meal_id, model_room_id, model_htplace_id, spo, hotelstop, price, frout_econom, frout_business, frback_econom, frback_business)
values
  (@P1, @P2, @P3, @P4, @P5, @P6, @P7, @P8, @P9, @P10, @P11, @P12, @P13, @P14, @P15)
',1
select @p1

第2步:

exec sp_execute 486,21,2000450,'2009-12-04 00:00:00',14,2118,22,-9555,18,'2009-10.MSK.Bali.13.10.09-27.03.10',0,15530.0000,3,3,3,3

第 3 步:

exec sp_unprepare 486

适用于所有新行。

4

4 回答 4

1

因为您调用的是存储过程,而不是代码中的内联查询,所以 SQL Server 将对存储过程的每次调用视为单独的调用,因此每次都对其进行准备和取消准备。我不确定是否有办法解决这个问题。

如果存储过程中发生的任何事情都可以通过代码中的查询来完成,那么您可以使用这样的结构,它只会在第一次准备 SQL 语句:

{Prepare the insert query}
ADOQuery1.SQL.Append('INSERT INTO Tablename');
ADOQuery1.SQL.Append('(StringField1, IntField2)');             {repeat as necessary}
ADOQuery1.SQL.Append('VALUES (:sFieldValue1, :sFieldValue2)'); {repeat as necessary}
ADOQuery1.SQL.Prepare;

{In a For, While, Repeat loop, use:}
ADOQuery1.ParamByName('sFieldValue1').AsString := 'Value for field 1';
ADOQuery1.ParamByName('sFieldValue2').AsInteger := 2;
ADOQuery1.ExecSQL;

抱歉,如果我没有为 ADOQuery 组件完全正确的属性和方法名称,我现在不在我的 Delphi PC 上,而且我通常不使用 TADO 组件,但这个概念仍然适用,因为这是一个TDataSet 概念。

于 2009-11-07T11:46:47.827 回答
0

想法...

  1. 您无需准备存储过程调用。实际上,它已经准备好了。您可以在大多数客户端实现中将其关闭。

  2. 您可能无法一次完成一百万行。您有256 MB 的批量大小限制(例如单个数据库调用)(假设默认为 4k 网络数据包)。

  3. 在其他客户端实现中,您可以设置“批量大小”(与第 2 点不同的概念),例如 10,000,因此您只会进行 100 次调用而不是 100 万次。

于 2009-11-11T20:24:00.563 回答
0

我认为其他答案可能有助于调整性能,尽管我认为您使用什么方法来访问 TClientDataSet 并不重要,因为无论哪种方式,实际的数据库更新都是独立的(并自动生成)。

如果更新作为每一行的单独准备工作,那么这似乎是 Borland 的一个糟糕的设计选择,因为很明显更新许多行将需要相同的查询,只是每次使用不同的参数。

另一方面,TClientDataSet 用于内存数据库,这意味着相对较小的数据库。使用类似一百万行的内容可能超出了预期的用例。

另一方面,此时从应用程序中换出 ClientDataSet 可能会很麻烦。我想说,对于应用程序的性能敏感部分,我会自己跟踪修改的行,并使用上面提到的例程编写手动更新例程。除此之外,您可以尝试修改 TClientDataSet 的源代码以使其更高效,或者对其进行子类化并覆盖应用更改的方法。

(就我个人而言,我在程序中使用 SQLite3 进行存储,所以 ClientDataSet 用处不大,我也没怎么玩过)。

于 2009-11-12T07:04:54.730 回答
0

答案在 TADOConnection 中使用的提供程序中。从 MSDASQL 切换到 SQLOLEDB,现在一切正常,无需任何额外查询。

于 2009-11-12T13:42:48.787 回答