我正在开发一个服务应用程序(VB.NET),该服务应用程序从源中提取信息并将其导入到SQL Server数据库中
该过程一次可能涉及一个或多个“批次”信息(任何给定的“运行”中的批次数量和大小基于在其他地方维护的队列都是任意的)
为每个批次分配一个标识符(BatchID),以便可以轻松地识别登台表中属于该批次的记录集
每个批次的ETL过程本质上是顺序的;将原始数据批量插入到临时表中,然后一系列存储过程对许多列执行更新,直到准备好导入数据为止
这些存储过程由服务依次调用,通常是简单的UPDATE命令
每个SP都将BatchID作为输入参数,并将其指定为包含在每个UPDATE中的条件,例如:
UPDATE dbo.stgTable
SET FieldOne = (CASE
WHEN S.[FieldOne] IS NULL
THEN T1.FieldOne
ELSE
S.[FieldOne]
END
)
, FieldTwo = (CASE
WHEN S.[FieldTwo] IS NULL
THEN T2.FieldTwo
ELSE
S.[FieldTwo]
END
)
FROM dbo.stgTable AS S
LEFT JOIN dbo.someTable T1 ON S.[SomeField] = T1.[SomeField]
LEFT JOIN dbo.someOtherTable T2 ON S.[SomeOtherField] = T2.[SomeOtherField]
WHERE S.BatchID = @BatchID
一些SP也引用了函数(标量和表值),并且都合并了TRY / CATCH结构,因此我可以从输出参数中判断某个SP是否失败
最终的SP是MERGE操作,用于将丰富的数据从登台表移至生产表(同样,特定于所提供的BatchID)
我想在服务中使用此流程,以便大批处理不会在同一运行中容纳小批处理
我认为这应该没有问题,因为没有线程可以尝试处理登台表中的记录,而该记录可以被另一个线程作为目标(没有竞争条件)
但是,我注意到,当我对进程进行线程化时,任意批处理上的任意步骤似乎都失败了(但是从SP的输出中未记录任何错误)
失败是不一致的;例如有时第2、3和5批次将失败(分别在SP的3、5和7上),其他时候将是不同的批次,每个批次按顺序的不同步骤
当我按顺序导入批次时,它们都可以完美地导入–总是如此!
我无法确定这是否是服务端(VB.NET)的问题-例如是每个线程打开与数据库的独立连接,还是它们可以共享同一线程(我将其设置为每个应该应该独立...”
或者如果问题出在SQL Server方面-例如并发SP调用操作同一表上的数据是否可行,即使如上所述,没有线程/批处理将接触到属于另一个线程/批处理的记录
(在这一点上-我尝试使用CTE来基于BatchID从临时表中创建数据的子集,并将UPDATE应用于这些数据,但是确实发生了相同的行为)
WITH CTE AS (
SELECT *
FROM dbo.stgTable
WHERE BatchID = @BatchID
)
UPDATE CTE...
也许是问题在于多个SP同时调用相同的功能,这就是为什么其中一个或多个失败的原因(我不明白为什么这会成为问题?)
我们将非常感激收到任何建议–我整周都在忙碌着,我无法一生确切地确定问题可能出在哪里!
这是服务类中启动线程的代码
For Each ItemInScope In ScopedItems
With ItemInScope
_batches(_batchCount) = New Batch(.Parameter1, .Parameter2, .ParameterX)
With _batches(_batchCount)
If .Initiate() Then
_doneEvents(_batchCount) = New ManualResetEvent(False)
Dim _batchWriter = New BatchWriter(_batches(_batchCount), _doneEvents(_batchCount))
ThreadPool.QueueUserWorkItem(AddressOf _batchWriter.ThreadPoolCallBack, _batchCount)
Else
_doneEvents(_batchCount) = New ManualResetEvent(True)
End If
End With
End With
_batchCount += 1
Next
WaitHandle.WaitAll(_doneEvents)
这是BatchWriter类
Public Class BatchWriter
Private _batch As Batch
Private _doneEvent As ManualResetEvent
Public Sub New(ByRef batch As Batch, ByVal doneEvent As ManualResetEvent)
_batch = batch
_doneEvent = doneEvent
End Sub
Public Sub ThreadPoolCallBack(ByVal threadContext As Object)
Dim threadIndex As Integer = CType(threadContext, Integer)
With _batch
If .PrepareBatch() Then
If .WriteTextOutput() Then
.ProcessBatch()
End If
End If
End With
_doneEvent.Set()
End Sub
End Class
Batch类的PrepareBatch和WriteTextOutput函数完全包含在服务应用程序中-只有ProcessBatch函数使服务开始与数据库进行交互(通过实体框架)
这是功能
Public Sub ProcessScan()
' Confirm that a file is ready for import
If My.Computer.FileSystem.FileExists(_filePath) Then
Dim dbModel As New DatabaseModel
With dbModel
' Pass the batch to the staging table in the database
If .StageBatch(_batchID, _filePath) Then
' First update (results recorded for event log)
If .UpdateOne(_batchID) Then
_stepOneUpdates = .RetUpdates.Value
' Second update (results recorded for event log)
If .UpdateTwo(_batchID) Then
_stepTwoUpdates = .RetUpdates.Value
' Third update (results recorded for event log)
If .UpdateThree(_batchID) Then
_stepThreeUpdates = .RetUpdates.Value
....
End Sub