【问题标题】:Delphi - Why am I getting this Access Violation? Is there a limit to ADOQuery parameteres?Delphi - 为什么我会遇到此访问冲突? ADOQuery 参数有限制吗?
【发布时间】:2015-05-18 14:14:23
【问题描述】:

我有这段代码返回访问冲突(“模块“sqloledb.dll”中地址 74417E44 的访问冲突。读取地址 786E3552”),我无法确定问题出在哪里。我唯一的猜测是 ADOQuery 对我们可以传递的参数数量有限制。 代码如下:

With qryInsert do
  begin
    Active := False;
    Close;
    Sql.Clear;
    Sql.Add('Insert Into MyTable(ColumnOne, ');
    Sql.Add('             ColumnTwo,           ');
    Sql.Add('             ColumnThree,         ');
    Sql.Add('             ColumnFour,           ');
    Sql.Add('             ColumnFive,          ');
    Sql.Add('             ColumnSix,        ');
    Sql.Add('             ColumnSeven,        ');
    Sql.Add('             ColumnEight,     ');
    Sql.Add('             ColumnNine,       ');
    Sql.Add('             ColumnTen,       ');
    Sql.Add('             ColumnEleven,     ');
    Sql.Add('             ColumnTwelve,   ');
    if qrySelect.FieldByName('ColumnTwelve').AsSTring = 'Y' then
    begin
      Sql.Add('           ColumnThirteen,   ');
      Sql.Add('           ColumnFourteen,   ');
      Sql.Add('           ColumnFifteen,   ');
    end;
    Sql.Add('             ColumnSixteen,   ');
    if qrySelect.FieldByName('ColumnSixteen').AsSTring = 'Y' then
    begin
      Sql.Add('           ColumnSeventeen,         ');
      Sql.Add('           ColumnEighteen,         ');
      Sql.Add('           ColumnNineteen,         ');
    end;
    if qrySelect.FieldByName('ColumnTwenty').AsSTring = 'Y' then
    begin
      Sql.Add('           ColumnTwenty,  ');
      Sql.Add('           ColumnTwentyOne,        ');
      Sql.Add('           ColumnTwentyTwo,        ');
      Sql.Add('           ColumnTwentyThree,        ');
    end
    else
      Sql.Add('           ColumnTwenty,  ');
    Sql.Add('             ColumnTwentyFour) ');
    Sql.Add('Values(:ColumnOne, :ColumnTwo, :ColumnThree, :ColumnFour, ');
    Sql.Add('       :ColumnFive, ' + dateDB + ', :ColumnSeven,          ');
    Sql.Add('       :ColumnEight, :ColumnNine, :ColumnTen, ');
    Sql.Add('       :ColumnEleven,                                    ');
    Sql.Add('       :ColumnTwelve,                                    ');
    if qrySelect.FieldByName('ColumnTwelve').AsSTring = 'Y' then
      Sql.Add('     :ColumnThirteen, :ColumnFourteen, :ColumnFifteen,              ');
    Sql.Add('       :ColumnSixteen,                                      ');
    if qrySelect.FieldByName('ColumnSixteen').AsSTring = 'Y' then
      Sql.Add('     :ColumnSeventeen, :ColumnEighteen, :ColumnNineteen,                 ');
    if qrySelect.FieldByName('ColumnTwenty').AsSTring = 'S' then
    begin
      Sql.Add('   :ColumnTwenty,                                      ');
      Sql.Add('   :ColumnTwentyOne, :ColumnTwentyTwo, :ColumnTwentyThree,                ');
    end
    else
      Sql.Add('   :ColumnTwenty,                                      ');
    Sql.Add('     :ColumnTwentyFour)                                  ');
    {And then for all the parameteres, pass the value}
    Parameters.ParamByName('ColumnOne').Value := varColumnOne;
    ...
    Parameters.ParamByName('ColumnTwentyFour').Value := varColumnTwentyFour;
    ExecSQL;
  end;

我在这一行得到错误:

Sql.Add('       :ColumnTwelve,                                    ');

这是我插入语句中的第 11 个参数。 如果我评论这一行,我会在下一个参数中得到错误。 如果我像这样直接输入值:

Sql.Add('     ' + varColumnTwelve + ',                            ');

它工作正常,但我在下一个参数中得到错误。

所以这让我想知道:ADOQuery 是否有它可以处理的参数数量的限制?或者,如果这不是真正的问题,有没有人知道我该如何解决这个问题?


注意事项:

  • 我使用的是 Delphi 7 和 Windows 8.1。

  • AV 仅(并且总是)在调试时出现,如果我直接通过其“.exe”执行应用程序,它永远不会出现。

  • 如果我在出现错误后一直按“运行”,它会显示越来越多的 AV(我认为 AV 的数量与 10 号之后添加的参数数量相同),直到应用程序继续正常运行。

  • 在所有 AV 出现在屏幕上之后插入工作。我只是想了解为什么在一切正常的情况下会出现此错误。

【问题讨论】:

  • 你在使用线程吗?
  • @whosrdaddy 不,不使用线程。
  • 仅供参考,ADO 上有 1024 个参数的硬限制,所以这显然不是问题所在。大卫走在正确的轨道上,问题出在你代码的其他地方,听听他的建议......
  • 首先要尝试将查询构建包装在qry.SQL.BeginUpdate;...qry.SQL.EndUpdate 对中。我(曾经,使用 Informix 驱动程序 IIRC)看到过类似的问题。如果没有 BeginUpdate..EndUpdate,TADOQuery 将尝试解析每个 Add() 的参数
  • @Gerry - Begin/EndUpdate 不参与因 SQL 更改而导致的参数解析。这是使用 ParamCheck 实现的。

标签: delphi access-violation tadoquery


【解决方案1】:

更改 TADOQuery 的 SQL 属性会导致 TADOQuery 响应该更改,将修改后的 SQL 重新应用于内部 ADO 组件对象以及重新解析 SQL 以识别任何参数。

因此,不建议以这种方式增量修改 SQL。除了其他任何事情之外,在完全组装之前一遍又一遍地应用和解析 SQL 是非常低效的。

在这种情况下,当您添加第 11 个参数时,该 SQL 已被应用和解析 28 次!

随后产生的 AV 发生在 SQLOLEDB.DLL 中的事实表明,无论发生什么问题都是由于对应用于内部 ADO 对象而不是在用于识别参数等的 VCL 处理。因此,您无法解决问题。你能做的最好的就是避免它。

您可以通过在修改 SQL 时设置 ParamCheck := FALSE 来消除部分处理。这将防止 VCL 尝试重新解析修改后的 SQL 以识别参数。但是,它不会阻止 SQL 被重新应用于底层 ADO 组件以响应每次更改。

作为诊断练习,您可以在修改 SQL 时尝试设置 ParamCheck := FALSE。完成后,调用 Parameters.Refresh 方法以确保更新参数集合以反映完成的 SQL:

qryInsert.ParamCheck := FALSE;
qryInsert.SQL.Add(..);
qryInsert.SQL.Add(..);
qryInsert.SQL.Add(..);
qryInsert.SQL.Add(..);

qryInsert.Parameters.Refresh;

注意: 将 ParamCheck 设置为 FALSE,您必须在尝试设置任何参数值之前调用 Parameters.Refresh,否则参数将不存在在参数集合中!

如果在此更改后 AV 仍然出现,则这更强烈地表明内部 ADO 组件在响应 SQL 的重复更改时表现不佳,可能是由于未能正确处理不完整(语法不正确) SQL。

但是,您可以通过以下两种方式之一完全避免触发更改机制。

也许最简单的方法是在构建 SQL 的代码周围的 TADOQuery SQL 字符串列表中使用 BeginUpdate/EndUpdate

qryInsert.SQL.BeginUpdate;
try
  qryInsert.SQL.Add(..);
  qryInsert.SQL.Add(..);
  qryInsert.SQL.Add(..);

finally
  qryInsert.SQL.EndUpdate;
end;

这具有抑制 ADO 查询对象内部的内部 OnChange 事件的效果,直到调用 EndUpdate,此时 SQL 将应用于内部 ADO对象和查询对象的参数已更新。

或者,您可以将 SQL 组装到一个完全独立的字符串列表中,然后将其应用于 TADOQuery SQL 属性,作为对 SQL.Text 的单个直接更改属性:

sql := TStringList.Create;
try
  sql.Add(..);
  sql.Add(..);
  sql.Add(..);
  sql.Add(..);
  sql.Add(..);
  sql.Add(..);

  qryInsert.SQL.Text := sql.Text;

finally
  sql.Free;
end;

无论哪种方式,结果都是 VCL 将解析参数,并且内部 ADO 对象将仅更新一次,并使用完整且(希望)语法正确的 SQL 语句。

第二种方法可能涉及更少的“样板” - try..finally 这里纯粹是为了管理临时字符串列表。如果您为此目的在更广泛的范围内重新使用对象,或者使用生成简单字符串的 SQL 构建器帮助程序类(就像我一样),那么就不需要这个特定的 try.. 终于,让应用更方便、更简洁:

SQLBuilder.Insert('MyTable');
SQLBuilder.AddColumn('ColumnOne');
SQLBuilder.AddColumn('ColumnTwo');

qryInsert.SQL.Text := SQLBuilder.SQL;

// qryInsert.SQL == INSERT INTO MyTable (ColumnOne, ColumnTwo)
//                  VALUES (:ColumnOne, :ColumnTwo)    

例如。

字符串与 TStringList

如果您构建 SQL 的首选技术产生的是字符串列表而不是简单字符串,您可能会想直接分配字符串列表:

  qryInsert.SQL := sql;

但请注意,这会执行 sql 字符串列表的 Assign(),从而有效地执行“深度复制”。您仍然需要确保分配的字符串列表(上述代码中的 sql)被适当地释放。

还请注意,这也不太有效,因为它还复制了字符串列表的其他属性,包括与列表中每个字符串关联的任何对象。在这种情况下,如果您只对复制字符串列表的 Text 内容感兴趣,则无需产生(轻微的)不必要的开销。

【讨论】:

  • 在这种情况下Query.Sql.BeginUpdate 不是更好吗?
  • @EProgrammer - 大声笑 - 是的,完全正确。我已经习惯了使用我的 SQLBuilder 类(它产生了一个完整的 SQL 语句来分配给“SQL.Text”,我忽略了在这种情况下对 Begin/EndUpdate 的简单使用。我已经更新了答案以突出显示这个替代方案. 谢谢
  • @Deltics FWIW,我认为除了调试时的不便之外,这里真的没有问题。这只是外部库处理的异常。
  • 将 ParamCheck 设置为 False,我得到“找不到参数”错误(我在设置任何值之前调用了 Parameters.Refresh。我想我必须在那之后创建参数?)。使用 Begin/EndUpdate 我遇到了与最初相同的问题,但只是在 EndUpdate 命令上。不过,+1 表示有很多选择。非常感谢!
【解决方案2】:

AV 只(并且总是)在调试时出现,如果我直接通过它的“.exe”执行应用程序,它就不会出现。

....

在所有 AV 出现在屏幕上后,插入才起作用。我只是想了解为什么在一切正常的情况下会出现此错误。

访问冲突是在外部模块中引发的,该模块以 Delphi 以外的语言实现。外部代码很可能按照设计正确运行,并且访问冲突是预期的。

这听起来可能很奇怪,但外部代码清楚地处理了异常,因为控制权不会传递给代码的异常处理程序。正如您所观察到的,该程序运行正常。

这就是所谓的first chance exception。调试器收到通知并中断。但随后控制权返回给程序,在这种情况下,程序处理异常并继续。代码引发第一次机会访问冲突异常,但仍然正常运行,这是完全正常的,尽管可能违反直觉。作为该声明的证据,请参阅由 VS 开发团队成员撰写的 article

为什么 VS 调试器不会在第一次出现访问冲突时停止(默认情况下)?

....

第一次机会 AV 的默认设置不会停止的原因是 有时 Windows 调用会 AV,然后捕获异常 自己并快乐地继续。如果我们确实默认停止 第一次机会我们会阻止用户在某个奇怪的地方说 kernel32.dll 和很多人会很困惑。

所以就正确性而言,我认为没有什么可担心的。但这确实使调试变得困难。尝试@Deltics 提出的各种建议。如果通过进行这些更改,您碰巧避免了异常,那就太好了。否则,您可能需要至少暂时禁止调试器中断异常。

【讨论】:

  • 当外部异常是 访问冲突 时,是否真的可以如此放松?对我来说,这有一股难闻的气味。
  • @Deltics 它确实发生了。调试模式下的 FastMM 是在正常执行过程中产生第一次机会 AV 的代码的主要示例。我认为现在很清楚我所说的是真的。显然异常是由外部代码处理的。
  • FastMM 是一个内存管理器。 FastMM 的异常与 ADO 提供程序内部发生的 AV 之间存在世界的差异。但无论哪种情况,AV都不像“除零”等。我无法想到事后可以正确“处理”AV。正确的解决方案是首先避免获得它们!由于我们无法更改 SQLOLEDB 中的代码来修复可能存在的任何错误,因此我们可以而且应该做的最好的事情是确定我们正在做什么导致/促成 AV 并停止这样做 .
  • @Deltics 我不知道为什么那个外部模块会产生它然后处理的第一个机会 AV。你也不知道。你认为它必须表明一个实际的问题,但你不知道。尚无证据支持您怀疑存在实际问题。为什么外部代码会费心处理异常?据提问者说,程序运行正常。如果代码生成的 AV 然后被处理,这不是先验的缺陷。
  • 嗯,我想这回答了我最初的问题“我为什么要得到这个 AV”。现在我将尝试使用@Deltics 建议的“技巧”来压制消息。谢谢大家!
【解决方案3】:

如果 qrySelect 没有 'ColumnTwelve' 则

if qrySelect.FieldByName('ColumnTwelve').AsSTring = 'Y' then

将引发异常,因为 FieldByName 将返回 nil

【讨论】:

  • 但问题表明 AV 发生在其他地方,并不总是发生。如果 FieldByName 返回 nil,则错误总是会发生。
  • 是的,这会引发异常,但是EDatabaseError
  • qrySelect 是在 qryInsert 之前运行的另一个 TADOQuery。这不是问题。
  • @Keith - 是的,它会引发异常,但该异常将是 EDatabaseError(未找到字段),不是访问冲突。 “异常”和“访问冲突”不是同义词。
猜你喜欢
  • 1970-01-01
  • 2017-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-28
  • 1970-01-01
  • 2022-01-09
相关资源
最近更新 更多