Kai*_*Kai 11 excel vba adodb excel-vba excel-2010
我有一个Excel工作簿,它向数据库发出三个查询以填充隐藏工作表上的三个表,然后运行三个"刷新"脚本将这些数据拉到三个可见的演示文稿表(每个查询一个).同步运行它非常慢:刷新的总时间是三个查询中每个查询的时间总和加上每个"刷新"脚本运行的时间总和.
我知道VBA不是多线程的,但我认为可以通过异步触发查询来加快速度(从而允许在执行时完成一些清理工作),并且然后在数据返回时为每个工作表执行填充/刷新工作.
我重写了我的脚本如下(请注意,我必须删除连接字符串,查询字符串等,并使变量通用):
Private WithEvents cnA As ADODB.Connection
Private WithEvents cnB As ADODB.Connection
Private WithEvents cnC As ADODB.Connection
Private Sub StartingPoint()
'For brevity, only listing set-up of cnA here. You can assume identical
'set-up for cnB and cnC
Set cnA = New ADODB.Connection
Dim connectionString As String: connectionString = "<my conn string>"
cnA.connectionString = connectionString
Debug.Print "Firing cnA query: " & Now
cnA.Open
cnA.Execute "<select query>", adAsyncExecute 'takes roughly 5 seconds to execute
Debug.Print "Firing cnB query: " & Now
cnB.Open
cnB.Execute "<select query>", adAsyncExecute 'takes roughly 10 seconds to execute
Debug.Print "Firing cnC query: " & Now
cnC.Open
cnC.Execute "<select query>", adAsyncExecute 'takes roughly 20 seconds to execute
Debug.Print "Clearing workbook tables: " & Now
ClearAllTables
TablesCleared = True
Debug.Print "Tables cleared: " & Now
End Sub
Private Sub cnA_ExecuteComplete(ByVal RecordsAffected As Long, ...)
Debug.Print "cnA records received: " & Now
'Code to handle the recordset, refresh the relevant presentation sheet here,
'takes roughly < 1 seconds to complete
Debug.Print "Sheet1 tables received: " & Now
End Sub
Private Sub cnB_ExecuteComplete(ByVal RecordsAffected As Long, ...)
Debug.Print "cnB records received: " & Now
'Code to handle the recordset, refresh the relevant presentation sheet here,
'takes roughly 2-3 seconds to complete
Debug.Print "Sheet2 tables received: " & Now
End Sub
Private Sub cnC_ExecuteComplete(ByVal RecordsAffected As Long, ...)
Debug.Print "cnC records received: " & Now
'Code to handle the recordset, refresh the relevant presentation sheet here,
'takes roughly 5-7 seconds to complete
Debug.Print "Sheet3 tables received: " & Now
End Sub
Run Code Online (Sandbox Code Playgroud)
典型的预期调试器输出:
Firing cnA query: 21/02/2014 10:34:22
Firing cnB query: 21/02/2014 10:34:22
Firing cnC query: 21/02/2014 10:34:22
Clearing tables: 21/02/2014 10:34:22
Tables cleared: 21/02/2014 10:34:22
cnB records received: 21/02/2014 10:34:26
Sheet2 tables refreshed: 21/02/2014 10:34:27
cnA records received: 21/02/2014 10:34:28
Sheet1 tables refreshed: 21/02/2014 10:34:28
cnC records received: 21/02/2014 10:34:34
Sheet3 tables refreshed: 21/02/2014 10:34:40
Run Code Online (Sandbox Code Playgroud)
当然,这三个查询可以以不同的顺序返回,具体取决于哪个完成,因此有时典型的输出以不同的顺序排序 - 这是预期的.
然而,有时候,一个或两个cnX_ExecuteComplete回调根本不会发射.经过一段时间的调试,我很确定这样做的原因是,如果一个记录集在其中一个回调当前正在执行时返回,则不会发生调用.例如:
cnA_ExecuteComplete触发cnA_ExecuteComplete仍在运行,所以cnB_ExecuteComplete永远不要开火cnA_ExecuteComplete 在8时完成cnC_ExecuteComplete触发我的理论是否正确,这是问题所在?如果是这样,是否可以解决这个问题,或者调用"等待"直到当前代码执行而不是仅仅消失?
一种解决方案是在cnX_ExecuteComplete回调期间做一些非常快速的事情(例如,Set sheet1RS = pRecordset在运行刷新脚本同步之前检查它们是否全部完成),因此它们重叠的几率大约为零,但是想要先知道是否有更好的解决方案.
小智 9
我想我无法解释为什么你的'刷新脚本'并不总是激发.这是一种奇怪的行为,有时它们会运行,有时却不会运行.我无法真正看到您的整个脚本,但我可以向您展示我如何采用您的代码并使其每次都能正常工作.
注意:您的问题与使用adAsyncExecute参数未触发的ExecuteComplete ADODB Connection事件有某种关联
我在SQL服务器上添加了3个存储过程; sp_WaitFor5,sp_WaitFor10,sp_WaitFor20模拟的查询执行时间的延迟.
很简单
CREATE PROCEDURE sp_WaitFor5
AS
WAITFOR DELAY '00:00:05'
Run Code Online (Sandbox Code Playgroud)
所有3个延迟.
然后在我的Module1我添加了一个非常简单的代码来调用自定义类
Option Explicit
Private clsTest As TestEvents
Sub Main()
Cells.ClearContents
Set clsTest = New TestEvents
Call clsTest.StartingPoint
End Sub
Run Code Online (Sandbox Code Playgroud)
然后我将类模块重命名为TestEvents并添加了稍微修改过的代码版本
Option Explicit
Private WithEvents cnA As ADODB.Connection
Private WithEvents cnB As ADODB.Connection
Private WithEvents cnC As ADODB.Connection
Private i as Long
Public Sub StartingPoint()
Dim connectionString As String: connectionString = "Driver={SQL Server};Server=MYSERVER\INST; UID=username; PWD=password!"
Debug.Print "Firing cnA query(10 sec): " & Now
Set cnA = New ADODB.Connection
cnA.connectionString = connectionString
cnA.Open
cnA.Execute "sp_WaitFor10", adExecuteNoRecords, adAsyncExecute
Debug.Print "Firing cnB query(5 sec): " & Now
Set cnB = New ADODB.Connection
cnB.connectionString = connectionString
cnB.Open
cnB.Execute "sp_WaitFor5", adExecuteNoRecords, adAsyncExecute
Debug.Print "Firing cnC query(20 sec): " & Now
Set cnC = New ADODB.Connection
cnC.connectionString = connectionString
cnC.Open
cnC.Execute "sp_WaitFor20", adExecuteNoRecords, adAsyncExecute
End Sub
Private Sub cnA_ExecuteComplete(ByVal RecordsAffected As Long, ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pCommand As ADODB.Command, ByVal pRecordset As ADODB.Recordset, ByVal pConnection As ADODB.Connection)
Debug.Print vbTab & "cnA_executeComplete START", Now
For i = 1 To 55
Range("A" & i) = Rnd(1)
Next i
Debug.Print vbTab & "cnA_executeComplete ENDED", Now
End Sub
Private Sub cnB_ExecuteComplete(ByVal RecordsAffected As Long, ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pCommand As ADODB.Command, ByVal pRecordset As ADODB.Recordset, ByVal pConnection As ADODB.Connection)
Debug.Print vbTab & "cnB_executeComplete START", Now
For i = 1 To 1000000
Range("B" & i) = Rnd(1)
Next i
Debug.Print vbTab & "cnB_executeComplete ENDED", Now
End Sub
Private Sub cnC_ExecuteComplete(ByVal RecordsAffected As Long, ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pCommand As ADODB.Command, ByVal pRecordset As ADODB.Recordset, ByVal pConnection As ADODB.Connection)
Debug.Print vbTab & "cnC_executeComplete START", Now
For i = 1 To 55
Range("C" & i) = Rnd(1)
Next i
Debug.Print vbTab & "cnC_executeComplete ENDED", Now
End Sub
Run Code Online (Sandbox Code Playgroud)
我真的没有太大的改变,除了额外的参数进行Execute一些代码填充activesheet只是花时间.
现在,我可以运行不同的变体/配置.我可以旋转连接对象的执行时间.我可以有cnA5秒,cnB10秒,cnC20秒.我可以交换/调整每个_ExecuteComplete事件的执行时间.
从我自己的测试中我可以向你保证,所有3个都会被执行.
这里有一些基于类似于你的配置的日志
Firing cnA query(10 sec): 24/02/2014 12:59:46
Firing cnB query(5 sec): 24/02/2014 12:59:46
Firing cnC query(20 sec): 24/02/2014 12:59:46
cnB_executeComplete START 24/02/2014 12:59:51
cnB_executeComplete ENDED 24/02/2014 13:00:21
cnA_executeComplete START 24/02/2014 13:00:21
cnA_executeComplete ENDED 24/02/2014 13:00:21
cnC_executeComplete START 24/02/2014 13:00:22
cnC_executeComplete ENDED 24/02/2014 13:00:22
Run Code Online (Sandbox Code Playgroud)
在上面的示例中,您可以看到,所有3个查询都是异步触发的.
cnA5秒后返回句柄,这使得cnB第一个事件("刷新脚本")在层次结构中运行cnC时间最长.
由于首先cnB 回来,它会触发它的cnB_ExecuteComplete事件程序.它cnB_ExecuteComplete本身设置需要花费一些时间执行(迭代100万次并用随机数填充B列.注意:cnA填充A列,cnB col B,cnC col C).查看上面的日志需要30秒才能运行.
当cnB_ExecuteComplete正在执行其工作/占用资源(并且如您所知VBA是单线程的)时,cnA_ExecuteComplete事件将被添加到TODO进程的队列中.所以,你可以把它想象成一个队列.当事情正在被照顾下一件事情必须等到最后.
如果我改变配置; cnA5秒,cnB10秒,cnC20秒,然后让每个'刷新脚本'迭代100万次
Firing cnA query(5 sec): 24/02/2014 13:17:10
Firing cnB query(10 sec): 24/02/2014 13:17:10
Firing cnC query(20 sec): 24/02/2014 13:17:10
one million iterations each
cnA_executeComplete START 24/02/2014 13:17:15
cnA_executeComplete ENDED 24/02/2014 13:17:45
cnB_executeComplete START 24/02/2014 13:17:45
cnB_executeComplete ENDED 24/02/2014 13:18:14
cnC_executeComplete START 24/02/2014 13:18:14
cnC_executeComplete ENDED 24/02/2014 13:18:44
Run Code Online (Sandbox Code Playgroud)
从第一个例子中清楚地证明了我的观点.
此外,尝试cnA5秒,cnB5秒,cnC5秒
Firing cnA query(5 sec): 24/02/2014 13:20:56
Firing cnB query(5 sec): 24/02/2014 13:20:56
Firing cnC query(5 sec): 24/02/2014 13:20:56
one million iterations each
cnB_executeComplete START 24/02/2014 13:21:01
cnB_executeComplete ENDED 24/02/2014 13:21:31
cnA_executeComplete START 24/02/2014 13:21:31
cnA_executeComplete ENDED 24/02/2014 13:22:01
cnC_executeComplete START 24/02/2014 13:22:01
cnC_executeComplete ENDED 24/02/2014 13:22:31
Run Code Online (Sandbox Code Playgroud)
这也完成/执行所有3.
就像我说的那样,我看不到你的整个代码,也许你的代码中某处有一个未处理的错误,也许有些东西误导你认为一个人_ExecuteComplete根本没有执行.尝试更改您的代码以反映我给您的代码并自行运行一些文本.我期待着您的反馈.