7

在工作中,我们必须处理几个 MS Access mdb 文件,因此我们使用 Sun JVM 附带的默认 JdbcOdbcBridge 驱动程序,并且在大多数情况下,它工作得很好。

问题是当我们必须处理一些较大的文件时,我们会多次遇到异常,并显示“无法打开更多表”消息。我们怎样才能避免呢?

我们已经关闭了所有 PreparedStatements 和 RecordSets 实例,甚至将它们的变量设置为 null,但即便如此,这个异常仍然会发生。我们应该做什么?我们怎样才能避免这些讨厌的异常呢?这里有人知道怎么做吗?

我们可以更改 Windows 上的 ODBC 驱动程序的任何其他配置以避免此问题吗?

4

5 回答 5

11

“无法打开更多的表”是比“无法打开更多的数据库”更好的错误消息,在我的经验中更常见。事实上,后者的信息几乎总是掩盖前者。

Jet 4 数据库引擎有 2048 个表句柄的限制。我不完全清楚这在连接的生命周期内是同时发生的还是累积的。我一直认为它是累积的,因为在实践中一次打开更少的记录集似乎可以避免这个问题。

问题是“表句柄”不仅仅指表句柄,而是更多的东西。

考虑使用此 SQL 保存的 QueryDef:

  SELECT tblInventory.* From tblInventory;

运行 QueryDef 使用两个表句柄。

什么?,你可能会问?它只使用一张桌子!但是 Jet 为表使用表句柄,为保存的 QueryDef 使用表句柄。

因此,如果您有这样的 QueryDef:

  SELECT qryInventory.InventoryID, qryAuthor.AuthorName
  FROM qryInventory JOIN qryAuthor ON qryInventory.AuthorID = qryAuthor.AuthorID

...如果您的每个源查询中都有两个表,则您正在使用这些表句柄,每个句柄一个:

  Table 1 in qryInventory
  Table 2 in qryInventory
  qryInventory
  Table 1 in qryAuthor
  Table 2 in qryAuthor
  qryAuthor
  the top-level QueryDef

因此,您可能认为只涉及四个表(因为只有四个基表),但实际上您将使用 7 个表句柄来使用这 4 个基表。

如果在记录集中,然后使用使用 7 个表句柄的已保存 QueryDef,则您已经用完了另一个表句柄,总共 8 个。

回到 Jet 3.5 天,原始表处理限制为 1024,我在设计工作应用程序后复制数据文件时遇到了它。问题在于,某些复制表始终处于打开状态(可能是针对每个记录集?),而这仅使用了足够多的表句柄来将应用程序置于顶部。

在该应用程序的原始设计中,我打开了一堆重量级的表单,其中包含许多子表单、组合框和列表框,当时我使用了很多保存的 QueryDef 来预组装我会在很多地方使用的标准记录集(就像您对任何服务器数据库的视图一样)。解决问题的是:

  1. 仅在显示子窗体时才加载它们。

  2. 仅当它们在屏幕上时才加载组合框和列表框的行源。

  3. 尽可能删除所有保存的 QueryDef 并使用连接原始表的 SQL 语句。

这使我能够比计划只晚一周在伦敦办公室部署该应用程序。当 Jet SP2 出现时,它的桌面句柄数量增加了一倍,这就是我们在 Jet 4 中仍然拥有的(我猜是 ACE)。

在通过 ODBC 从 Java 使用 Jet 方面,我认为关键是:

  1. 在整个应用程序中使用单个连接,而不是根据需要打开和关闭它们(这会使您面临无法关闭它们的危险)。

  2. 仅在需要时打开记录集,并在完成后清理并释放它们的资源。

现在,可能是 JDBC=>ODBC=>Jet 链中的某个地方存在内存泄漏,您认为您正在释放资源,而它们根本没有被释放。我没有针对 JDBC 的任何建议(因为我不使用它——毕竟我是 Access 程序员),但在 VBA 中,我们必须小心明确地关闭我们的对象并释放它们的内存结构,因为VBA 使用引用计数,有时它不知道对对象的引用已被释放,因此当对象超出范围时它不会释放该对象的内存。

所以,在 VBA 代码中,任何时候你这样做:

  Dim db As DAO.Database
  Dim rs As DAO.Recordset

  Set db = DBEngine(0).OpenDatabase("[database path/name]")
  Set rs = db.OpenRecordset("[SQL String]")

...完成您需要做的事情后,您必须完成以下操作:

  rs.Close         ' closes the recordset
  Set rs = Nothing ' clears the pointer to the memory formerly used by it
  db.Close
  Set db = Nothing

...即使您声明的变量在该代码之后立即超出范围(这应该释放它们使用的所有内存,但不会 100% 可靠地这样做)。

现在,我并不是说这就是你在 Java 中所做的,但我只是建议如果你遇到问题并且你认为你正在释放所有资源,也许你需要确定你是否依赖于垃圾收集这样做,而是需要明确地这样做。

如果我在 Java 和 JDBC 方面说了一些愚蠢的话,请原谅我——我只是在报告 Access 开发人员在与 Jet 交互(通过 DAO,而不是 ODBC)时遇到的一些问题,这些问题报告了相同的错误消息您正在获得,希望我们的经验和实践可以为您的特定编程环境提供解决方案。

于 2009-11-28T02:30:00.587 回答
3

最近我尝试了 UCanAccess - 一个用于 MS Access 的纯 java JDBC 驱动程序。查看: http: //sourceforge.net/projects/ucanaccess/ - 也适用于 Linux ;-) 要加载所需的库,需要一些时间。我还没有出于只读目的对其进行测试。

无论如何,我在使用 sun.jdbc.odbc.JdbcOdbcDriver 时遇到了上述问题。在创建语句对象(并在这些对象上调用 executeUpdate)以及 System.gc() 语句后添加 close() 语句后,错误消息停止;-)

于 2014-10-14T10:59:20.913 回答
1

您可能只是用完了免费的网络连接。我们在工作繁忙的系统上遇到了这个问题。

需要注意的是,网络连接虽然已关闭,但在垃圾收集时间之前可能不会释放套接字。你可以用NETSTAT /A /N /P TCP. 如果您在该州有很多连接TIME_WAIT,您可以尝试在连接关闭或定期间隔时强制进行垃圾收集。

于 2009-11-27T11:47:51.933 回答
0

您还应该关闭 Connection 对象。

寻找 jdbc odbc 驱动程序的替代方案也是一个好主意。我自己没有任何替代方案的经验,但这将是一个很好的起点:

有没有使用 sun.jdbc.odbc.JdbcOdbcDriver 的替代方法?

于 2009-11-27T10:44:29.433 回答
0

我有同样的问题,但以上都没有工作。我最终找到了这个问题。我正在使用它来读取表单的值以放回查找列表记录源。

LocationCode = [Forms]![Support].[LocationCode].Column(2)
ContactCode = Forms("Support")("TakenFrom")

将其更改为以下内容,并且可以正常工作。

LocationCode = Forms("Support")("LocationCode")
ContactCode = Forms("Support")("TakenFrom")

我知道我应该写得更好,但我希望这可以帮助处于同样情况的其他人。

谢谢格雷格

于 2013-06-23T08:59:07.017 回答