【问题标题】:Getting ODBC - System Resources Exceeded (Rutime error 3035)获取 ODBC - 超出系统资源(运行时错误 3035)
【发布时间】:2022-06-15 02:22:10
【问题描述】:

需要一些帮助。我在 How to increase performance for bulk INSERTs to ODBC linked tables in Access? 处获取了 Gord Thompson 的代码并对其进行了修改以适合我的情况。

我正在尝试将名为“bulk_insert”的查询(基于 MS Access DB 中的本地表)的内容复制到名为 dbo_tblCVR_Matching_tmp 的 SQL 链接表中。该查询没有计算字段或函数或什么都没有,只有 102 列普通数据。我目前正在测试 6K 到 10K 记录范围内的文件。

在我收到此线程标题中的错误之前,代码执行并复制了许多记录。我环顾四周,但没有什么可以帮助我解决我的特定问题。不确定我是否必须清除或刷新某些内容。这是我正在使用的 2 个例程:

'==============================================================
'Gord Thompson  Stackoverflow: https://stackoverflow.com/questions/25863473/how-to-increase-performance-for-bulk-inserts-to-odbc-linked-tables-in-access
'==============================================================

Sub bulk_insert()
    Dim cdb As DAO.Database
    Dim rst As DAO.Recordset
    Dim t0 As Single
    Dim i As Long
    Dim c As Long
    Dim valueList As String
    Dim separator As String
    Dim separator2 As String

t0 = Timer
Set cdb = CurrentDb
Set rst = cdb.OpenRecordset("SELECT * FROM bulk_insert", dbOpenSnapshot)
i = 0
valueList = ""
separator = ""

Do Until rst.EOF
    i = i + 1
    valueList = valueList & separator & "("
    separator2 = ""
    For c = 0 To rst.Fields.Count - 1
        
        valueList = valueList & separator2 & "'" & rst.Fields(c) & "'"
        If c = 0 Then
            separator2 = ","
        End If
    Next c
    valueList = valueList & ")"
    
    If i = 1 Then
        separator = ","
    End If
    If i = 1000 Then
        SendInsert valueList
        i = 0
        valueList = ""
        separator = ""
    End If
    rst.MoveNext
Loop

If i > 0 Then
    SendInsert valueList
End If
rst.Close
Set rst = Nothing
Set cdb = Nothing
Debug.Print "Elapsed time " & Format(Timer - t0, "0.0") & " seconds."
End Sub

'=============================================== ================

Sub SendInsert(valueList As String)
Dim cdb As DAO.Database
Dim qdf As DAO.QueryDef

Set cdb = CurrentDb
Set qdf = cdb.CreateQueryDef("")

qdf.Connect = cdb.TableDefs("dbo_tblCVR_Matching_tmp").Connect
qdf.ReturnsRecords = False
qdf.sql = "INSERT INTO dbo.tblCVR_Matching_tmp (" & _
"Associate_Id , Recd_Date, Price_Sheet_Eff_Date, VenAlpha, Mfg_Name, Mfg_Model_Num, Fei_Alt1_Code, Mfg_Product_Num, Base_Model_Num, Product_Description," & _
"Qty_Base_UOM , Price_Invoice_UOM, Mfr_Pub_Sugg_List_Price, Mfr_Net_Price, IMAP_Pricing, Min_Order_Qty, UPC_GTIN, Each_Weight, Each_Length, Each_Width," & _
"Each_Height, Inner_Pack_GTIN_Num, Inner_Pack_Qty, Inner_Pack_Weight, Inner_Pack_Length, Inner_Pack_Width, Inner_Pack_Height, Case_GTIN_Num, Case_Qty," & _
"Case_Weight, Case_Length, Case_Width, Case_Height, Pallet_GTIN_Num, Pallet_Qty, Pallet_Weight, Pallet_Length, Pallet_Width, Pallet_Height, Pub_Price_Sheet_Eff_Date," & _
"Price_Sheet_Name_Num, Obsolete_YN, Obsolete_Date, Obsolete_Stock_Avail_YN, Direct_Replacement, Substitution, Shelf_Life_YN, Shelf_Life_Time, Shelf_Life_UOM," & _
"Serial_Num_Req_YN, LeadLaw_Compliant_YN, LeadLaw_3rd_Party_Cert_YN, LeadLaw_NonPotable_YN, Compliant_Prod_Sub, Compliant_Prod_Plan_Ship_Date, Green, GPF, GPM," & _
"GPC, Freight_Class, Gasket_Material, Battery_YN, Battery_Type, Battery_Count, MSDS_YN, MSDS_Weblink, Hazmat_YN, UN_NA_Num, Proper_Shipping_Name," & _
"Hazard_Class_Num, Packing_Group, Chemical_Name, ORMD_YN, NFPA_Storage_Class, Kit_YN, Load_Factor, Product_Returnable_YN, Product_Discount_Category," & _
"UNSPSC_Code, Country_Origin, Region_Restrict_YN, Region_Restrict_Regulations, Region_Restrict_States, Prop65_Eligibile_YN, Prop65_Chemical_Birth_Defect," & _
"Prop65_Chemical_Cancer, Prop65_Chemical_Reproductive, Prop65_Warning, CEC_Applicable_YN, CEC_Listed_YN, CEC_Model_Num, CEC_InProcess_YN, CEC_Compliant_Sub," & _
"CEC_Compliant_Sub_Cross_YN, Product_Family_Name, Finish, Kitchen_Bathroom, Avail_Order_Date, FEI_Exclusive_YN, MISC1, MISC2, MISC3" & _
    ") Values " & valueList

'this is the line that is always highlighted when the error occurs
    qdf.Execute dbFailOnError
    Set qdf = Nothing
    Set cdb = Nothing
    
End Sub

这是代码经过一百万次测试后的最终版本,以防万一有人遇到我同样的问题。再次感谢 Albert Kallal 帮助我解决这个问题。

我在代码中添加了一些 cmets 以及其他信息,以便让这件事一次性运行。

就我而言,

  1. 我在查询记录之前处理了所有重复项(即,我创建了一个追加查询以将记录复制到具有主键的本地表)

  2. 创建了一个直通查询“p”

  3. 使用函数帮助我转义单引号字符等字符并处理空值和空白

  4. 集成了一个 dlookup 函数,以防止我对查询中每一列的名称进行硬编码而发疯。还允许过滤空列以最大化使用块大小

    '=============================================== ============== '归功于 Albert Kalal Getting ODBC - System Resources Exceeded (Rutime error 3035) '================================================== ===========

    Sub bulk_insert()

     Dim rstLocal  As DAO.Recordset
     Set rstLocal = CurrentDb.OpenRecordset("bi") 'bi is the name of the query I'm using to list of the records in the bulk
    
     Dim sBASE      As String      ' base sql insert string
     Dim sValues    As String      ' our values() list built up
    
     Dim t As Single
     t = Timer
    
     Dim i          As Long
     Dim j          As Long
     Dim c As Long
     Dim ChunkSize  As Long    ' # length size of "text" to send to server
     Dim separator2 As String
     Dim potentialHeader As String
     Dim test
     Dim filledArray() As Long
    
     ChunkSize = 48000        'chunk size / or number of chars
    
     'Try to programmatically create the insert, we will also remove anything that doesn't have values
    
     With rstLocal
         If Not rstLocal.EOF Then
             sBASE = "INSERT INTO dbo.tblCVR_Matching_tmp ("  'this is where I added my SQL table
             ReDim filledArray(0 To .Fields.Count - 1)
             separator2 = ""
             For c = 0 To .Fields.Count - 1 'using loop to get all the headers in my query
                 potentialHeader = .Fields(c).Name
             test = DLookup(potentialHeader, "bi", potentialHeader & " is not null") 'using the dlookup function to isolate headers from my query that have values in its column
    
             If test <> "" Then
                 filledArray(c) = 1
                 sBASE = sBASE & separator2 & potentialHeader
                 separator2 = ","
             Else
                 filledArray(c) = 0
             End If
         Next c
    
         sBASE = sBASE & ") VALUES "
     End If
    

    结束

    Dim RowsInChunk As Long ' 这将显示适合块的行 Dim RowCountOut 只要 sValues = "" Do While rstLocal.EOF = False RowCountOut = RowCountOut + 1

     If sValues <> "" Then sValues = sValues & ","
    
     RowsInChunk = RowsInChunk + 1
     sValues = sValues & "("
     separator2 = ""
     With rstLocal
         For c = 0 To .Fields.Count - 1
             If filledArray(c) = 1 Then
                 sValues = sValues & separator2 & sql_escape(.Fields(c)) 'using sql_escape function for cells that have 'null' or single quotes... the function helps escape the characters to avoid getting errors on the insert
                 separator2 = ","
             Else
                 'SKIP IF ALL NULLS
             End If
         Next c
     End With
    
     sValues = sValues & ")"
    
     If (Len(sBASE) + Len(sValues)) >= ChunkSize Then
         'send data to server
         With CurrentDb.QueryDefs("p")
             .sql = sBASE & sValues
             .Execute
         End With
    
         Debug.Print "Rows in batch = " & RowsInChunk 'displays the number of rows per batch sent on each bulk insert statement
         RowsInChunk = 0
         sValues = ""
         DoEvents
     End If
    

    rstLocal.MoveNext

    循环

    ' 发送最后一批(如果有) 如果 sValues "" 那么 使用 CurrentDb.QueryDefs("p") ' 在此处使用传递查询。我命名我的'p' .sql = sBASE & sValues 。执行 结束于 sValues = "" 结束如果

    rstLocal.关闭 t = 定时器 - t Debug.Print "done - time = " & t '在即时窗口上显示关于子总持续时间的信息 结束子

====这是sql_escape函数========

' detects if a values is string or null and properly escapes it
Public Function sql_escape(val As Variant)
    If LCase(val) = "null" Or val = "" Or IsNull(val) Then
        sql_escape = "NULL"
    Else
        ' also need to escape "'" for proper sql
        val = Replace(val, "'", "''")
        sql_escape = "'" & val & "'"
    End If
End Function

【问题讨论】:

  • 你的字符串会很大。为什么不把它分成多批插入
  • If i = 1000 Then 可能会减少到 200 左右,然后尝试一下......

标签: vba ms-access


【解决方案1】:

在您的循环中,对值长度进行测试。

我会在大约 4000 个字符时触发插入,也许尝试 8000 个。

另外,您想为此使用传递查询,否则会很慢。

所以,代码会和你一样,但要确保输出格式是 t-sql (sql server) 格式,而不是 JET/ACE sql 格式。

请注意,sql server 确实有插入的简写,我们希望使用这一事实,因为这会大量减少开销(sql 语法)(并且查看您的代码,您似乎确实在这样做)。

所以,我们想要的格式是这样的:

INSERT INTO tblBig (ID, FirstName, LastName, City)

       VALUES (134, 'Albert', 'Kallal', 'Edmonton'),
       VALUES (134, 'Albert', 'Kallal', 'Edmonton'),
       VALUES (134, 'Albert', 'Kallal', 'Edmonton');

注意我们如何只需要一个插入命令来处理多行。

因此,我们的代码存根将如下所示:

Sub TestAppendNeedForSpeed()

  ' I wanted to allow PK inserts
  With CurrentDb.QueryDefs("qryPass1")
      .SQL = "SET IDENTITY_INSERT TBLbIG1 ON;"
      .Execute
  End With


  Dim rstLocal  As dao.Recordset
  Set rstLocal = CurrentDb.OpenRecordset("tblBig")

  Dim sBASE      As String      ' base sql insert string
  Dim sValues    As String      ' our values() list built up

  Dim t As Single
  t = Timer

  Dim i          As Long
  Dim j          As Long
  Dim ChunkSize  As Long    ' # length size of "text" to send to server

  ChunkSize = 4000        ' I don't think going higher will help

  sBASE = "INSERT INTO tblBig1 (ID,FirstName,LastName,City) VALUES "

  Dim RowsInChunk  As Long  ' this will show rows that fit into a chunk - only FYI
  Dim RowCountOut  As Long
  sValues = ""
  Do While rstLocal.EOF = False
    RowCountOut = RowCountOut + 1
  
    If sValues <> "" Then sValues = sValues & ","
    RowsInChunk = RowsInChunk + 1
      With rstLocal
          sValues = sValues & "(" & !ID & "," & qu(!FirstName) & "," & qu(!LastName) & "," & qu(!City) & ")"
      End With
      
      If (Len(sBASE) + Len(sValues)) >= ChunkSize Then
        ' send data to server
        With CurrentDb.QueryDefs("qryPass1")
          .SQL = sBASE & sValues
          .Execute
        End With
    
        Debug.Print "(" & RowCount & ") -- buffer out - " & RowsInChunk
        RowsInChunk = 0
        sValues = ""
        DoEvents
    End If
    
    rstLocal.MoveNext
    
Loop
' send out last batch (if any)
If sValues <> "" Then
  With CurrentDb.QueryDefs("qryPass1")
    .SQL = sBASE & sValues
    .Execute
  End With
  sValues = ""
End If

  rstLocal.Close
    t = Timer - t
   Debug.Print "done - time = " & t

End Sub 

因此,按照我们的布局方式,我们可以设置/调整/测试/尝试最佳块大小。

您甚至不能关闭并在同一个球部分中一次插入 4000 行。尝试大约 4000 个字符,也许是 8000 个。在某些系统中,我看到大约 12000 个字符块大小效果最好。

如前所述,使用上述传递查询的想法 - 它也会运行得更快。

使用上述方法,您可以期待大约 15 到 20 倍的速度提升。因此,代替 120 分钟,您会看到大约 6 分钟的时间。

所以,使用上面的模板和方法。当然,一行值可以是外部子(或函数)调用,但上述方法将为您提供最佳速度。

【讨论】:

  • 非常感谢 Albert 的模板和共享的宝贵信息。我做了一些调整,但最终的解决方案保留了主要组件。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多