SQL查询耗时太长

时间:2009-05-07 08:16:28

标签: sql ms-access optimization ado

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

数据库连接似乎是瓶颈。你知道如何减少这个时间吗?要么通过某种方式将多个语句合并为一个以减少开销,或者某些ADO / MS-Access技巧?

例如,您可以一次向表中插入多行,这会明显加快吗?

额外信息:

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

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

10 个答案:

答案 0 :(得分:5)

有些人发布@@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

这表明现在的瓶颈是与执行多个语句相关的开销。如果我们能在一个声明中做到这一点怎么办?好吧,猜猜是什么,用我惯用的例子,我们可以。首先,创建一个唯一整数的Sequence表,这是一个标准的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

然后使用Sequence表枚举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秒内在我的机器上执行!

答案 1 :(得分:2)

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

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

请参阅此knowledge base article

答案 2 :(得分:0)

  我们插入一行,然后再插入一行   查询以检索其自动增量   ID;然后使用此ID插入几个   更多行,将它们链接到第一个

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

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

如果两个表(假设FOREIGN KEY列的表之间有AUTOINCREMENT/IDENTITY),您可以创建VIEW INNER JOIN两个表和{{} 1}} INSERT INTOVIEW值将被'复制'到引用表。此Stack Overflow答案中的更多详细信息和工作示例(链接如下)。

如果有多个表,AUTOINCREMENT/IDENTITY技巧不会扩展到两个表AFAIK之外,那么您可能不得不忍受性能不佳或改变技术,例如:据报道DAO比ADO更快,SQL Server可能比ACE / Jet更快等等。再次,随意分享您的设计,可能会有一个“开箱即用”的解决方案。

How to Insert Data?

答案 3 :(得分: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个连接要快许多倍......

答案 4 :(得分:0)

一些提案(甚至可以合并):

  • 为什么不自动增加 插入行之前的ID,所以 你已经拥有了价值 可用于您的下一个查询?这个 应该给你一些时间。
  • 关闭/打开连接 还要检查。
  • 你有没有想过操纵 记录集(使用Visual基本代码) 更新您的数据)而不是表格 (用SQL指令发送到 数据库)?
  • 另一种解决方案(如果你确定的话) 连接是 瓶颈)将是创造 在本地表,然后将其导出到 作业完成后的访问文件。
  • 当您使用ADO时,您甚至可以 将您的数据保存为XML文件,加载一个 来自此XML文件的记录集, 用VBA操纵它,然后导出它 到Access数据库

答案 5 :(得分:0)

并不意味着成为聪明人......但是,有理由继续使用Access吗? SQL Server Express是免费的,更快速,更强大的......

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

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

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

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

当然,当您运行具有大量服务器的网站时,它可能会稍微复杂一些。在这种情况下,juste使用Application Blocks或Cached。

答案 8 :(得分:0)

请允许我驳斥以下断言:

  

SELECT @@ IDENTITY建议 -   绝对是一个快得多的方式   插入数据并检索   自动编号值比打开AddOnly   记录集,更新字段,保存   记录和存储自动编号   值。 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

答案 9 :(得分:-1)

一些可能有用或可能没有帮助的想法:

  1. 让我来看看SELECT @@ IDENTITY建议 - 绝对是一种非常快捷的插入数据和检索自动编号值的方法,而不是打开AddOnly记录集,更新字段,保存记录和存储自动编号值。 SQL INSERT总是比使用逐行记录集更快。

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