使用SQLFileStream并发读取?

时间:2016-05-04 14:43:29

标签: sql-server vb.net concurrency isolation-level sqlfilestream

将.Net 4.52和SQL Server 2014与FILESTREAM一起使用

我们有一个web服务,并没有使用" System.InvalidOperationException并发读取:该进程无法访问指定的文件,因为它已在另一个事务中打开。"

我已经隔离了代码,以便在测试程序中重现失败,该程序产生10个并发任务,这些任务使用IsolationLevel.ReadCommitted和IO.FileAccess.Read读取相同的数据。我的理解是,在数据库和文件系统中都会存在共享锁,并且应该没有"阻塞"。

通过单个任务,代码可以始终如一地运行。有时它适用于2-3个任务。通过10个任务,代码几乎始终如一地失败 - 每隔一段时间它就能运行。我认为其他程序员可能正在访问数据,因为数据库位于我们的某个开发服务器上,但这并不能解释10个任务几乎一致的失败。

对于可能导致失败的原因提出任何建议都将不胜感激!

推动测试的代码:

    Dim profileKey As Guid = New Guid("DC3F1949-37DB-4D47-B204-0170FA4A40CD")
    Dim taskList As List(Of Task) = New List(Of Task)
    For x = 1 To 10
        Dim tsa As New TestSqlFileStream
        taskList.Add(Task.Run(Function() tsa.GetProfiles(profileKey, True)))
    Next

    Task.WaitAll(taskList.ToArray)

受测试的课程:

Public Class TestSqlFileStream

Public Function GetProfiles(profileKey As Guid, getSmallVersionOfImage As Boolean) As List(Of Profile)

    Dim retProfiles = New List(Of Profile)

    Using conn As New SqlConnection("server=blah,1435; initial catalog=blah;Trusted_Connection=Yes;")

        conn.Open()

        Dim cmd = conn.CreateCommand()
        Dim iso As IsolationLevel = IsolationLevel.ReadCommitted

        cmd.Transaction = conn.BeginTransaction(iso)

        Try
            cmd.CommandText = "GetProfiles"
            cmd.CommandType = CommandType.StoredProcedure
            cmd.Parameters.Add(New SqlParameter("@profileKey", SqlDbType.UniqueIdentifier)).Value = profileKey

            Using reader As SqlDataReader = cmd.ExecuteReader
                retProfiles = MapGetProfiles(reader, getSmallVersionOfImage)
            End Using

            cmd.Transaction.Commit()

        Catch ex As Exception
            cmd.Transaction.Rollback()
            Throw
        Finally
            conn.Close()
        End Try

    End Using

    Return retProfiles

End Function

Public Function MapGetProfiles(reader As SqlDataReader, getSmallVersionOfImage As Boolean) _
                       As List(Of Profile)

    Dim profiles As New List(Of Profile)
    Dim transactionToken As Byte()
    Try
        While reader.Read()

            Dim profile As New ServiceTypes.SubTypes.Profile

            profile.ParentKey = reader("ParentKey")
            profile.ProfileKey = reader("ProfileKey")
            profile.ProfileType = ConvertToProfileType(reader("ProfileType"))
            If reader("Active") Is Nothing Then profile.Active = False Else profile.Active = reader("Active")
            If IsDBNull(reader("Data")) Then profile.Data = Nothing Else profile.Data = reader("Data")

            Dim imagePath
            If getSmallVersionOfImage Then imagePath = reader("ImageThumbnailPath") Else imagePath = reader("ImagePath")
            transactionToken = DirectCast(reader("transactionContext"), Byte())

            If Not IsDBNull(imagePath) Then

                If Not transactionToken.Equals(DBNull.Value) Then
                    LoadImage(profile, imagePath, transactionToken)
                End If

            End If

            profiles.Add(profile)

        End While

    Catch ex As Exception

        Throw
    Finally
        reader.Close()
    End Try


    Return profiles

End Function

Public Sub LoadImage(ByRef profile As Profile, image As String, transactionContext As Byte())

    Using sqlFileStream = New SqlFileStream(image, transactionContext, IO.FileAccess.Read, FileOptions.SequentialScan, 0)                                    
        Dim retrievedImage = New Byte(sqlFileStream.Length - 1) {}
        sqlFileStream.Read(retrievedImage, 0, sqlFileStream.Length)
        profile.Image = retrievedImage
        sqlFileStream.Close()

    End Using

End Sub

Private Function ConvertToProfileType(profileType As String) As ProfileType
    Dim type = ServiceTypes.SubTypes.ProfileType.None
    Select Case profileType
        Case Nothing
            type = ServiceTypes.SubTypes.ProfileType.None
    End Select
    Return type
End Function

结束班

更新:我看过这个问题,但问题不同,因为它们在事务中分裂为并行:Threading and SqlFileStream. The process cannot access the file specified because it has been opened in another transaction 在我的示例中,每个任务都会启动自己的事务。

Update2 当我在事务中的断点处停止并在查询窗口中运行DBCC OPENTRAN时,结果是"没有活动的打开事务"看起来SqlConnection.BeginTransaction实际上并没有在数据库中打开一个事务。

Update3 也从事务日志中读取(在命名事务之后):

Use myDB
GO
select top 1000 [Current LSN],
       [Operation],
       [Transaction Name],
       [Transaction ID],
       [Transaction SID],
       [SPID],
       [Begin Time]
FROM   fn_dblog(null,null)
order by [Begin Time] desc

我提供的名称的任何交易都没有显示在日志中。

1 个答案:

答案 0 :(得分:0)

注意:这仅适用于检索到的集合的原子性不重要的解决方案。我建议使用TransactionScope以获得更好的原子性。

当在事务(While Reader.Read循环)下检索到许多文件时,似乎事务队列/锁定机制会混淆。我打破了文件检索以使用新事务或每个文件检索,并且可以针对单个父母对同一层次的配置文件集运行100个并行任务。