关于 Delphi:Delphi – 为什么我会遇到访问冲突? ADOQuery 参数有限制吗? | 珊瑚贝

Delphi – Why am I getting this Access Violation? Is there a limit to ADOQuery parameteres?


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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;

我在这一行得到错误:

1
Sql.Add(‘       :ColumnTwelve,                                    ‘);

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

1
Sql.Add(‘     ‘ + varColumnTwelve + ‘,                            ‘);

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

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

注释:

  • 我正在使用 Delphi 7 和 Windows 8.1。

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

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

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

  • 你在使用线程吗?
  • @whosrdaddy 不,不使用线程。
  • 仅供参考,ADO 上有 1024 个参数的硬限制,所以这显然不是这里的问题。大卫走在正确的Rails上,问题出在你代码的其他地方,听听他的建议……
  • 首先要尝试的是将查询构建package在 qry.SQL.BeginUpdate;…qry.SQL.EndUpdate 对中。我(曾经,使用 Informix 驱动程序 IIRC)看到了类似的问题。如果没有 BeginUpdate..EndUpdate,TADOQuery 将尝试解析每个 Add() 的参数
  • @Gerry – Begin/EndUpdate 不参与由 SQL 更改导致的参数解析。这是使用 ParamCheck 实现的。
  • @Deltics – 抱歉,内存失败 – 通过 FSql.OnChange 事件处理程序更改 SQL 调用 TADOQuery.QueryChanged 。然后调用 TCustomADODataSet.SetCommandText,后者又调用 TADOCommand.SetCommandText -> TADOCommand.AssignCommandText。这有一个本地过程 InitParams,如果 ParamCheck 属性为真,则调用该过程。 (截至 D2007)
  • 此外,我几年前遇到的错误与参数无关,而是由于某种原因解析(不完整)SQL 时 OLEDB 驱动程序层出现问题。 BeginUpdate/EndUpdate 修复了 SQL 在 CommandTExt 更新之前完成的问题


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

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

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

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

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

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

1
2
3
4
5
6
7
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:

1
2
3
4
5
6
7
8
9
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 属性的直接更改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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..finally,让它多一点使用方便、清洁:

1
2
3
4
5
6
7
8
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 的首选技术产生一个字符串列表而不是一个简单的字符串,您可能会想直接分配字符串列表:

1
  qryInsert.SQL := sql;

但请注意,这会执行 sql 字符串列表的 Assign(),有效地执行 \\’deep copy\\’。您仍然需要确保分配的字符串列表(上述代码中的 sql)被适当地释放。

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

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


The AV only (and always) appears when debugging, it does never appear if I execute the application directly through its”.exe”.

….

The insert works after all the AVs appeared on the screen. I just want to understand why am I getting this error when everything looks fine.

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

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

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

Why the VS Debugger does not stop on first chance Access Violations (by default)?

….

The reason the default for first-chance AVs does not stop is that
sometimes Windows calls will AV, and then catch the exception
themselves and carry on happily. If we did default to stopping on
first chance AVs we would stop users in some strange place in say
kernel32.dll and many would be very confused.

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

  • 当它是访问冲突时,对外部异常如此放松真的可取吗?对我来说,这有一股难闻的气味。
  • @Deltics 它确实发生了。调试模式下的 FastMM 是在正常执行过程中生成第一次机会 AV 的代码的主要示例。我认为现在很清楚我所说的是真的。显然,异常是由外部代码处理的。
  • FastMM 是一个内存管理器。 FastMM 的异常与 ADO 提供程序内部发生的 AV 之间存在天壤之别。但无论哪种情况,AV 都不像”除零”等。我无法想到事后可以正确”处理”AV。正确的解决方案是首先避免获得它们!由于我们无法更改 SQLOLEDB 中的代码来修复可能存在的任何错误,因此我们可以而且应该做的最好的事情是确定我们正在做什么导致/促成 AV 并停止这样做。
  • @Deltics 我不知道为什么那个外部模块会产生它然后处理的第一个机会 AV。你也不知道。你认为它必须表明一个实际的问题,但你不知道。尚无证据支持您怀疑存在实际问题。为什么外部代码会费心处理异常?据提问者说,程序运行正常。如果代码产生然后处理的 AV,则不是先验缺陷。
  • @Deltics 最后一点。如果您尝试了答案中的所有技巧都无济于事,您会怎么做。第一次机会异常仍然由外部库引发和处理,但在程序中没有发现任何错误行为。你会放弃并决定有一个无法解决的问题吗?
  • 好吧,我想这回答了我最初的”为什么我得到这个 AV”的问题。现在我将尝试使用建议的”技巧”@Deltics 来抑制消息。多谢你们!
  • 没有 Joao – 它没有回答你为什么要获得 AV。正如大卫指出的那样,如果不查看 SQLOLEDB 的来源,我们就无法知道这一点。但大卫认为它可能是良性的说法是错误的。除了代码中的意外失败之外,访问冲突(对受保护内存的无效访问)可能是故意或有意义的,根本没有任何明智的情况。
  • @David – 我不止一次发现开发人员无法找到 AV 原因的代码已经放弃并决定简单地将问题package在 try..except { do nothing end;.众所周知,这就是 SQLOLEDB 内部发生的事情。或者,可能是他们遵循不允许例外”逃脱”提供者的政策。即所有异常都被捕获,这与说它们都在被明智地处理的意义上被处理是不同的。您是一位经验丰富的开发人员,不得不向您解释这类事情让我感到很惊讶。
  • @David – 我的最后一点是你的最后一点。如果已尽一切可能避免该问题,但发现问题仍然存在,那么是的,当然没有什么可做的,只能忍受它(并将潜在问题报告给所涉及的 DLL 的提供者)。但是你不能直接跳到那个场景,即使这样做也不会改变对问题原因的思考。不得不忍受它并不意味着有问题的代码的开发者打算让你忍受它。
  • @Deltics 这取决于。这种异常可能是故意的和正常的。我认为您错误地认为所有此类例外都必须是缺陷。 VS 开发人员的文章与您的看法相矛盾。你读过那篇文章吗?
  • @Joo 我并不是说肯定会出现这种第一次机会 AV。正如 Deltics 认为的那样,这可能是一个缺陷。然而,他断然错误地做出绝对确定这表明存在缺陷的陈述。他根本不知道。他说所有第一次机会 AV 都是缺陷是完全错误的。我链接到的文章是足够的证据。
  • @Deltics 你知道这个外部库是主流的 MS 库。你真的相信他们在这样的代码中使用了 Pokemon 异常处理吗?我认为他们的开发人员比这要好得多。这不是我们正在谈论的低质量的业余软件!
  • 只是提供反馈:我按照 Deltics 的建议放置了 BeginUpdate/EndUpdate,而 AV 现在只出现在 EndUpdate 中。但是和之前一样,SQL 正常工作,之后程序继续正常工作,应用程序上没有一条错误消息,只是在 Delphi 上。所以我接受了大卫的解释并继续前进。


如果 qrySelect 没有 \\’ColumnTwelve\\’ 那么

if qrySelect.FieldByName(‘ColumnTwelve’).AsSTring = ‘Y’ then

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

  • 但问题表明 AV 发生在其他地方,并不总是发生。如果 FieldByName 返回 nil,则错误将始终发生。
  • 是的,这会引发异常,但是 EDatabaseError。
  • qrySelect 是在 qryInsert 之前运行的另一个 TADOQuery。那不是问题。
  • @Keith – 是的,它会引发异常,但该异常将是 EDatabaseError (未找到字段),而不是访问冲突。”异常”和”访问冲突”不是同义词。


来源:https://www.codenong.com/30305578/

微信公众号
手机浏览(小程序)

Warning: get_headers(): SSL operation failed with code 1. OpenSSL Error messages: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed in /mydata/web/wwwshanhubei/web/wp-content/themes/shanhuke/single.php on line 57

Warning: get_headers(): Failed to enable crypto in /mydata/web/wwwshanhubei/web/wp-content/themes/shanhuke/single.php on line 57

Warning: get_headers(https://static.shanhubei.com/qrcode/qrcode_viewid_9835.jpg): failed to open stream: operation failed in /mydata/web/wwwshanhubei/web/wp-content/themes/shanhuke/single.php on line 57
0
分享到:
没有账号? 忘记密码?