1

我正在使用 ADO 将数据保存到 MS Access 数据库。将数据保存到文件需要相当长的时间(大约 7 秒 - 这对于我们的目的来说太长了)。我查看了正在运行的 SQL 查询的数量,大约是 4200;虽然没有一大堆数据。

数据库连接似乎是瓶颈。您是否知道减少所需时间的任何方法?或者通过某种方式将多个语句组合成一个以减少开销,或者一些 ADO/MS-Access 技巧?

例如,您能否一次将多行插入到一个表中,这样会明显更快吗?

额外信息:

我们有这么多查询的一个原因是我们插入了一行,然后有另一个查询来检索其自动递增的 ID;然后使用此 ID 插入更多行,将它们链接到第一行

回应几个评论和回复:我将连接一直打开,并使用 BeginTransaction() 和 CommitTransaciton() 作为单个事务执行

4

10 回答 10

6

有些人已经发布了这@@IDENTITY会很快,所以这里有一个证明(使用 VBA)我的INSERT INTO两个表如何通过一个VIEW技巧一次比做两个 INSERTS 并每次抓取值快大约三倍@@IDENTITY......这不足为奇,因为后者涉及三个Execute陈述,而前者只涉及一个:)

在我的机器上进行 4200 次迭代,这个VIEW技巧用了 45 秒,而@@IDENTITY方法用了 127 秒:

Sub InitInerts()
  On Error Resume Next
  Kill Environ$("temp") & "\DropMe.mdb"
  On Error GoTo 0
  Dim cat
  Set cat = CreateObject("ADOX.Catalog")
  With cat
    .Create _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

    With .ActiveConnection

      Dim Sql As String

      Sql = _
      "CREATE TABLE TableA" & vbCr & "(" & vbCr & "   ID IDENTITY NOT" & _
      " NULL UNIQUE, " & vbCr & "   a_col INTEGER NOT NULL" & vbCr & ")"
      .Execute Sql

      Sql = _
      "CREATE TABLE TableB" & vbCr & "(" & vbCr & "   ID INTEGER NOT" & _
      " NULL UNIQUE" & vbCr & "      REFERENCES TableA (ID)," & _
      "  " & vbCr & "   b_col INTEGER NOT NULL" & vbCr & ")"
      .Execute Sql

      Sql = _
      "CREATE VIEW TestAB" & vbCr & "(" & vbCr & "   a_ID, a_col, " & vbCr & " " & _
      "  b_ID, b_col" & vbCr & ")" & vbCr & "AS " & vbCr & "SELECT A1.ID, A1.a_col," & _
      " " & vbCr & "       B1.ID, B1.b_col" & vbCr & "  FROM TableA AS" & _
      " A1" & vbCr & "       INNER JOIN TableB AS B1" & vbCr & "    " & _
      "      ON A1.ID = B1.ID"
      .Execute Sql

    End With
    Set .ActiveConnection = Nothing
  End With
End Sub

Sub TestInerts_VIEW()

  Dim con
  Set con = CreateObject("ADODB.Connection")
  With con
    .Open _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

    Dim timer As CPerformanceTimer
    Set timer = New CPerformanceTimer
    timer.StartTimer

    Dim counter As Long
    For counter = 1 To 4200
      .Execute "INSERT INTO TestAB (a_col, b_col) VALUES (" & _
                   CStr(counter) & ", " & _
                   CStr(counter) & ");"
    Next

    Debug.Print "VIEW = " & timer.GetTimeSeconds

  End With

End Sub

Sub TestInerts_IDENTITY()

  Dim con
  Set con = CreateObject("ADODB.Connection")
  With con
    .Open _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

    Dim timer As CPerformanceTimer
    Set timer = New CPerformanceTimer
    timer.StartTimer

    Dim counter As Long
    For counter = 1 To 4200
      .Execute "INSERT INTO TableA (a_col) VALUES (" & _
          CStr(counter) & ");"

      Dim identity As Long
      identity = .Execute("SELECT @@IDENTITY;")(0)

      .Execute "INSERT INTO TableB (ID, b_col) VALUES (" & _
                   CStr(identity) & ", " & _
                   CStr(counter) & ");"

    Next

    Debug.Print "@@IDENTITY = " & timer.GetTimeSeconds

  End With

End Sub

这表明现在的瓶颈是与执行多个语句相关的开销。如果我们可以在一个语句中做到这一点呢?好吧,你猜怎么着,用我人为的例子,我们可以。首先,创建一个唯一整数的序列表,这是一个标准的 SQL 技巧(每个数据库都应该有一个,IMO):

Sub InitSequence()

  Dim con
  Set con = CreateObject("ADODB.Connection")
  With con
    .Open _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

    Dim sql As String

    sql = _
        "CREATE TABLE [Sequence]" & vbCr & "(" & vbCr & "   seq INTEGER NOT NULL" & _
        " UNIQUE" & vbCr & ");"
    .Execute sql

    sql = _
        "INSERT INTO [Sequence] (seq) VALUES (-1);"
    .Execute sql

    sql = _
        "INSERT INTO [Sequence] (seq) SELECT Units.nbr + Tens.nbr" & _
        " + Hundreds.nbr + Thousands.nbr AS seq FROM ( SELECT" & _
        " nbr FROM ( SELECT 0 AS nbr FROM [Sequence] UNION" & _
        " ALL SELECT 1 FROM [Sequence] UNION ALL SELECT 2 FROM" & _
        " [Sequence] UNION ALL SELECT 3 FROM [Sequence] UNION" & _
        " ALL SELECT 4 FROM [Sequence] UNION ALL SELECT 5 FROM" & _
        " [Sequence] UNION ALL SELECT 6 FROM [Sequence] UNION" & _
        " ALL SELECT 7 FROM [Sequence] UNION ALL SELECT 8 FROM" & _
        " [Sequence] UNION ALL SELECT 9 FROM [Sequence] ) AS" & _
        " Digits ) AS Units, ( SELECT nbr * 10 AS nbr FROM" & _
        " ( SELECT 0 AS nbr FROM [Sequence] UNION ALL SELECT" & _
        " 1 FROM [Sequence] UNION ALL SELECT 2 FROM [Sequence]" & _
        " UNION ALL SELECT 3 FROM [Sequence] UNION ALL SELECT" & _
        " 4 FROM [Sequence] UNION ALL SELECT 5 FROM [Sequence]" & _
        " UNION ALL SELECT 6 FROM [Sequence] UNION ALL SELECT" & _
        " 7 FROM [Sequence] UNION ALL SELECT 8 FROM [Sequence]" & _
        " UNION ALL SELECT 9 FROM [Sequence] ) AS Digits )" & _
        " AS Tens, ( SELECT nbr * 100 AS nbr FROM ( SELECT" & _
        " 0 AS nbr FROM [Sequence] UNION ALL SELECT 1 FROM" & _
        " [Sequence] UNION ALL SELECT 2 FROM [Sequence] UNION"
    sql = sql & _
        " ALL SELECT 3 FROM [Sequence] UNION ALL SELECT 4 FROM" & _
        " [Sequence] UNION ALL SELECT 5 FROM [Sequence] UNION" & _
        " ALL SELECT 6 FROM [Sequence] UNION ALL SELECT 7 FROM" & _
        " [Sequence] UNION ALL SELECT 8 FROM [Sequence] UNION" & _
        " ALL SELECT 9 FROM [Sequence] ) AS Digits ) AS Hundreds," & _
        " ( SELECT nbr * 1000 AS nbr FROM ( SELECT 0 AS nbr" & _
        " FROM [Sequence] UNION ALL SELECT 1 FROM [Sequence]" & _
        " UNION ALL SELECT 2 FROM [Sequence] UNION ALL SELECT" & _
        " 3 FROM [Sequence] UNION ALL SELECT 4 FROM [Sequence]" & _
        " UNION ALL SELECT 5 FROM [Sequence] UNION ALL SELECT" & _
        " 6 FROM [Sequence] UNION ALL SELECT 7 FROM [Sequence]" & _
        " UNION ALL SELECT 8 FROM [Sequence] UNION ALL SELECT" & _
        " 9 FROM [Sequence] ) AS Digits ) AS Thousands;"
    .Execute sql

  End With

End Sub

然后使用序列表枚举从 1 到 42000 的值,并在单个 INSERT INTO..SELECT 语句中构造行:

Sub TestInerts_Sequence()

  Dim con
  Set con = CreateObject("ADODB.Connection")
  With con
    .Open _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

    Dim timer As CPerformanceTimer
    Set timer = New CPerformanceTimer
    timer.StartTimer

    .Execute "INSERT INTO TestAB (a_col, b_col) " & _
             "SELECT seq, seq " & _
             "FROM Sequence " & _
             "WHERE seq BETWEEN 1 AND 4200;"

    Debug.Print "Sequence = " & timer.GetTimeSeconds



  End With

End Sub

这可以在 0.2 秒内在我的机器上执行!

于 2009-05-08T07:42:40.130 回答
2

更新版本的 Access 支持 @@IDENTITY 变量。您可以在插入后使用它来检索标识列,而无需进行查询。

INSERT INTO mytable (field1,field2) VALUES (val1,val2);
SELECT @@IDENTITY;

请参阅此知识库文章

于 2009-05-07T11:24:31.057 回答
0

我们插入一行,然后有另一个查询来检索其自动递增的 ID;然后使用此 ID 插入更多行,将它们链接到第一行

这是一张桌子,两张桌子还是两张以上的桌子?

如果一个表,您可以考虑不同的设计,例如您可以生成自己的随机标识符,使用嵌套集模型而不是邻接列表模型等。很难知道您是否不会分享您的设计;-)

如果有两个表,假设列FOREIGN KEY上的表之间有一个AUTOINCREMENT/IDENTITY,您可以创建一个VIEWINNER JOIN这两个表,INSERT INTO并且VIEWAUTOINCREMENT/IDENTITY将被“复制”到引用表。此 Stack Overflow 答案(下面的链接)中的更多详细信息和工作示例。

如果有多个表,则该VIEW技巧不会超出两个表 AFAIK,因此您可能只需要忍受较差的性能或更改技术,例如 DAO 的报告速度比 ADO 快,SQL Server 可能比 ACE/Jet 快,等等. 再次,请随意分享您的设计,可能会有“跳出框框思考”的解决方案。

如何插入数据?

于 2009-05-07T11:35:16.757 回答
0

对于您的情况,最好使用 ADO 执行插入操作,而不是运行 SQL 命令。

for i = 1 to 100
   rs.AddNew
   rs("fieldOne") = "value1"
   rs("fieldOne") = "value2"
   rs.Update
   id = rs("uniqueIdColumn")
   'do stuff with id...
next

我知道使用 ADO 很慢,但它比打开 4200 个连接要快很多倍...

于 2009-05-07T11:54:17.600 回答
0

一些建议(甚至可以组合):

  • 为什么在插入行之前不获取自动递增的 ID,这样您的值已经可用于下一个查询?这应该可以节省您一些时间。
  • 还必须检查连接的关闭/打开。
  • 您是否考虑过操作记录集(使用 Visual Basic 代码来更新数据)而不是表(使用发送到数据库的 SQL 指令)?
  • 另一种解决方案(如果您确定连接是瓶颈)是在本地创建表,然后在工作完成后将其导出到访问文件。
  • 当您使用 ADO 时,您甚至可以将数据保存为 XML 文件,从该 XML 文件加载记录集,使用 VBA 对其进行操作,并将其导出到 Access 数据库
于 2009-05-07T14:25:59.763 回答
0

不意味着要成为一个聪明的屁股......但是,有继续使用 Access 的理由吗?SQL Server Express 是免费的,速度更快,功能更强大......

于 2009-05-07T23:06:12.587 回答
0

听起来您正在导入数据,而 Access 有更好的工具可用于导入数据。是的,它一次插入许多记录,而且速度会更快。

你能再描述一下这个应用程序吗?

于 2009-05-08T01:01:39.157 回答
0

您可以为数据库提供的最便宜和(通常是每次)最好的速度提升是将不变的数据保留在缓存中。

不知道它是否适用于你的情况,但它很简单。您可以只插入一个(免费)库来执行此操作,或者保留已阅读项目的本地集合。例子 :

  • 想阅读第 X 项。
  • 项目 X 是否在本地集合中?
  • 否:从数据库中读取项目 X 并将其放入本地集合中。
  • 是的:只需返回项目 X 的​​本地副本。

当然,当您运行具有大量服务器的网站时,它可能会稍微复杂一些。在这种情况下,只需使用应用程序块或缓存。

于 2009-05-08T01:22:17.330 回答
0

请允许我反驳以下断言:

SELECT @@IDENTITY 建议——与打开 AddOnly 记录集、更新字段、保存记录和存储 Autonumber 值相比,插入数据和检索 Autonumber 值绝对是一种更快的方法。SQL INSERT 总是比使用逐行记录集更快。

我认为这两个记录集方法可能比@@IDENTITY 方法快一点,因为我怀疑它涉及更少的数据库往返。在我的测试中,它在 1.2 秒时快了很多,而 @@IDENTITY 方法为 127 秒。这是我的代码(创建 .mdb 的代码发布在另一个答案中):

Sub TestInerts_rs()

  Dim con
  Set con = CreateObject("ADODB.Connection")
  con.Open _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

  Dim timer As CPerformanceTimer
  Set timer = New CPerformanceTimer
  timer.StartTimer

  Dim rs1
  Set rs1 = CreateObject("ADODB.Recordset")
  With rs1
    .ActiveConnection = con
    .CursorType = 1  ' keyset
    .LockType = 3  ' optimistic
    .Source = "SELECT a_col, ID FROM TableA;"
    .Open
  End With

  Dim rs2
  Set rs2 = CreateObject("ADODB.Recordset")
  With rs2
    .ActiveConnection = con
    .CursorType = 1  ' keyset
    .LockType = 3  ' optimistic
    .Source = "SELECT b_col, ID FROM TableB;"
    .Open
  End With

  Dim counter As Long
  For counter = 1 To 4200
    rs1.AddNew "a_col", counter

    Dim identity As Long
    identity = rs1.Fields("ID").value

    rs2.AddNew Array(0, 1), Array(counter, identity)

  Next

  Debug.Print "rs = " & timer.GetTimeSeconds

End Sub
于 2009-05-11T07:28:57.407 回答
-1

一些可能有用也可能没用的想法:

  1. 让我支持 SELECT @@IDENTITY 建议——与打开 AddOnly 记录集、更新字段、保存记录和存储 Autonumber 值相比,插入数据和检索 Autonumber 值绝对是一种更快的方法。SQL INSERT 总是比使用逐行记录集更快。

  2. 使用 DAO 事务,您可能能够将多个此类插入转换为一次执行的批处理。我不确定这一点,因为我不确切知道您在做什么以及您正在使用多少张桌子。关键是您将在事务中运行一系列 SQL INSERT,然后在最后执行 .Commit,这样对真实数据库文件的实际写入将作为单个操作发生,而不是 4200(或不管多少)单独的操作。

于 2009-05-07T22:57:53.090 回答