1

Mit .Net 4.52 und SQL Server 2014 mit FILESTREAMGleichzeitige Lesevorgänge mit SQLFileStream?

Wir haben ein Webservice, der auf gleichzeitige versagt mit liest „System.InvalidOperationException:. Der Prozess kann die angegebene Datei nicht zugreifen, da sie in einer anderen Transaktion geöffnet wurde“

Ich habe den Code isoliert, um den Fehler in einem Testprogramm zu reproduzieren, das 10 gleichzeitige Aufgaben hervorbringt, die dieselben Daten mit IsolationLevel.ReadCommitted und IO.FileAccess.Read lesen. Mein Verständnis ist, dass es eine gemeinsame Sperre sowohl in der Datenbank als auch im Dateisystem geben wird, und es sollte keine "Blockierung" geben.

Mit einer einzigen Aufgabe funktioniert der Code konsistent. Manchmal funktioniert es mit 2-3 Aufgaben. Bei 10 Aufgaben scheitert der Code fast durchgängig - ab und zu funktioniert es. Ich habe erwogen, dass andere Programmierer möglicherweise auf die Daten zugreifen, da sich die Datenbank auf einem unserer Entwicklungsserver befindet, aber das würde den nahezu konsistenten Fehler bei 10 Tasks nicht erklären.

Irgendwelche Vorschläge betreffs, was den Ausfall verursachen könnte, würde sehr geschätzt werden!

Der Code der Testfahrten:

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) 

Die Klasse im Test:

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 

End Class

Update: Ich habe sah diese frage aber die issu e ist anders, weil sie sich innerhalb einer Transaktion aufteilen: Threading and SqlFileStream. The process cannot access the file specified because it has been opened in another transaction In meinem Beispiel startet jede Aufgabe ihre eigene Transaktion.

Update2 Wenn ich an einem Haltepunkt innerhalb der Transaktion zu stoppen und DBCC OPENTRAN in einem Abfragefenster führe das Ergebnis „Keine aktiven offenen Transaktionen“ Es scheint, wie SqlConnection.BeginTransaction nicht tatsächlich eine Transaktion in der Datenbank zu öffnen.

Update3 Auch aus Transaktionsprotokoll gelesen (nach der Transaktion zu benennen):

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 

Keine Transaktion des Namen im Protokoll zeigt I zur Verfügung gestellt.

+0

Sieht aus wie ein Stück Code wurde auf 'Mit SqlFileStream ...' – Clay

+0

Dank abgeschnitten, ja der eigentliche Code ist: Mit SqlFileStream = New SqlFileStream (Bild, transactionContext, IO.FileAccess.Read, FileOptions.SequentialScan, 0) –

+0

Wie kommt dieser Leser dazu, einen Transaktionskontext zu enthalten? Nie zuvor gesehen.Es gibt eine ganz andere Möglichkeit, es [hier] zu bekommen (https://msdn.microsoft.com/en-us/library/bb934014.aspx) – Clay

Antwort

0

Hinweis: Dies ist nur für Lösungen geeignet, bei denen die Atomarität der abgerufenen Menge nicht kritisch ist. Ich schlage ein TransactionScope für bessere Atomarität vor.

Es scheint, dass die Transaktionswarteschlange/der Sperrmechanismus verwirrt wird, wenn viele Dateien unter der Transaktion abgerufen werden (die While Reader.Read-Schleife). Ich habe den Dateiabruf für die Verwendung einer neuen Transaktion oder für jeden Dateiabruf abgebrochen und kann 100 parallele Aufgaben mit derselben hierarchischen Gruppe von Profilen für ein einzelnes Elternelement ausführen.

Verwandte Themen