Optimistická metoda souběžného zpracování
V prostředí s více uživateli existují dva modely pro aktualizaci dat v databázi: optimistická souběžnost a pesimistické souběžnost. Objekt DataSet je navržený tak, aby podporoval použití optimistické souběžnosti pro dlouhotrvající aktivity, jako je vzdálené komunikace dat a interakce s daty.
Pesimistické souběžnosti zahrnuje uzamčení řádků ve zdroji dat, aby ostatní uživatelé nemohli upravovat data způsobem, který má vliv na aktuálního uživatele. Když uživatel v pesimistickém modelu provede akci, která způsobí použití zámku, nemůžou ostatní uživatelé provádět akce, které by byly v konfliktu se zámkem, dokud ho vlastník zámku neuvolní. Tento model se primárně používá v prostředích, kde dochází k velkému kolizí dat, takže náklady na ochranu dat pomocí zámků jsou menší než náklady na vrácení transakcí zpět, pokud dojde ke konfliktům souběžnosti.
Proto v pesimistickém modelu souběžnosti uživatel, který aktualizuje řádek, vytvoří zámek. Dokud uživatel nedokončí aktualizaci a uvolní zámek, nikdo jiný nemůže tento řádek změnit. Z tohoto důvodu je pesimistické souběžnost nejlépe implementována, když časy uzamčení budou krátké, stejně jako při programovém zpracování záznamů. Pesimistické souběžnosti není škálovatelná možnost, když uživatelé pracují s daty a způsobují uzamčení záznamů po relativně velké časové období.
Poznámka:
Pokud potřebujete aktualizovat více řádků ve stejné operaci, vytvoření transakce je škálovatelnější možností než použití pesimistické uzamčení.
Naproti tomu uživatelé, kteří používají optimistickou souběžnost, nezamknou řádek při čtení. Když chce uživatel aktualizovat řádek, musí aplikace určit, jestli jiný uživatel změnil řádek od jeho čtení. Optimistická souběžnost se obecně používá v prostředích s nízkým kolizí dat. Optimistická souběžnost zvyšuje výkon, protože není vyžadováno žádné zamykání záznamů a uzamčení záznamů vyžaduje další prostředky serveru. K udržování zámků záznamů se také vyžaduje trvalé připojení k databázovému serveru. Vzhledem k tomu, že to není případ optimistického modelu souběžnosti, jsou připojení k serveru zdarma obsluhovat větší počet klientů za kratší dobu.
V modelu optimistické souběžnosti se považuje za porušení, ke kterému došlo, pokud uživatel po přijetí hodnoty z databáze změní hodnotu předtím, než se první uživatel pokusí ji upravit. Jak server vyřeší porušení souběžnosti, je nejlepší ukázat nejprve popisem následujícího příkladu.
Následující tabulky sledují příklad optimistické souběžnosti.
V 1:00 p.m., User1 přečte řádek z databáze s následujícími hodnotami:
CustID LastName FirstName
101 Smith Bob
Název sloupce | Původní hodnota | Aktuální hodnota | Hodnota v databázi |
---|---|---|---|
CustID | 101 | 101 | 101 |
LastName | Smith | Smith | Smith |
FirstName | Robert | Robert | Robert |
V 1:01 p.m., User2 přečte stejný řádek.
V 1:03 p.m., User2 změní Jméno z "Bob" na "Robert" a aktualizuje databázi.
Název sloupce | Původní hodnota | Aktuální hodnota | Hodnota v databázi |
---|---|---|---|
CustID | 101 | 101 | 101 |
LastName | Smith | Smith | Smith |
FirstName | Robert | Robert | Robert |
Aktualizace bude úspěšná, protože hodnoty v databázi v době aktualizace odpovídají původním hodnotám, které má Uživatel2.
V 1:05 p.m., User1 změní "Bob"jméno na "James" a pokusí se aktualizovat řádek.
Název sloupce | Původní hodnota | Aktuální hodnota | Hodnota v databázi |
---|---|---|---|
CustID | 101 | 101 | 101 |
LastName | Smith | Smith | Smith |
FirstName | Robert | James | Robert |
V tuto chvíli uživatel User1 narazí na optimistické porušení souběžnosti, protože hodnota v databázi (Robert) už neodpovídá původní hodnotě, kterou Uživatel1 očekával (Bob). Porušení souběžnosti jednoduše informuje, že aktualizace selhala. Rozhodnutí teď musí být provedeno, zda chcete přepsat změny poskytnuté uživatelem User2 změnami zadanými uživatelem User1, nebo zrušit změny uživatelem User1.
Testování porušení optimistické souběžnosti
Existuje několik technik pro testování porušení optimistické souběžnosti. Jedním z nich je zahrnutí sloupce časového razítka v tabulce. Databáze běžně poskytují funkce časového razítka, které lze použít k identifikaci data a času poslední aktualizace záznamu. Pomocí této techniky se do definice tabulky zahrne sloupec časového razítka. Při každé aktualizaci záznamu se časové razítko aktualizuje tak, aby odráželo aktuální datum a čas. V testu pro porušení optimistické souběžnosti se vrátí sloupec časového razítka s libovolným dotazem na obsah tabulky. Při pokusu o aktualizaci se hodnota časového razítka v databázi porovná s původní hodnotou časového razítka obsaženou v upraveném řádku. Pokud se shodují, provede se aktualizace a sloupec časového razítka se aktualizuje aktuálním časem tak, aby odrážel aktualizaci. Pokud se neshodují, došlo k narušení optimistické souběžnosti.
Další technikou pro testování porušení optimistické souběžnosti je ověření, že všechny původní hodnoty sloupců v řádku stále odpovídají hodnotám nalezeným v databázi. Zvažte například následující dotaz:
SELECT Col1, Col2, Col3 FROM Table1
Pokud chcete otestovat porušení optimistické souběžnosti při aktualizaci řádku v tabulce Table1, vydáte následující příkaz UPDATE:
UPDATE Table1 Set Col1 = @NewCol1Value,
Set Col2 = @NewCol2Value,
Set Col3 = @NewCol3Value
WHERE Col1 = @OldCol1Value AND
Col2 = @OldCol2Value AND
Col3 = @OldCol3Value
Pokud původní hodnoty odpovídají hodnotám v databázi, provede se aktualizace. Pokud byla hodnota změněna, aktualizace neupraví řádek, protože klauzule WHERE nenajde shodu.
Upozorňujeme, že v dotazu se doporučuje vždy vrátit jedinečnou hodnotu primárního klíče. V opačném případě může předchozí příkaz UPDATE aktualizovat více než jeden řádek, což nemusí být váš záměr.
Pokud sloupec ve zdroji dat umožňuje hodnoty null, možná budete muset rozšířit klauzuli WHERE, abyste zkontrolovali odpovídající odkaz na hodnotu null v místní tabulce a ve zdroji dat. Například následující příkaz UPDATE ověří, že odkaz null v místním řádku stále odpovídá nulovému odkazu ve zdroji dat nebo že hodnota v místním řádku stále odpovídá hodnotě ve zdroji dat.
UPDATE Table1 Set Col1 = @NewVal1
WHERE (@OldVal1 IS NULL AND Col1 IS NULL) OR Col1 = @OldVal1
Při použití modelu optimistické souběžnosti se také můžete rozhodnout použít méně omezující kritéria. Například použití pouze sloupců primárního klíče v klauzuli WHERE způsobí přepsání dat bez ohledu na to, jestli byly ostatní sloupce aktualizovány od posledního dotazu. Klauzuli WHERE můžete také použít pouze u konkrétních sloupců, což vede k přepsání dat, pokud se od posledního dotazování neaktualizovala konkrétní pole.
Událost DataAdapter.RowUpdated
Událost RowUpdated objektu DataAdapter lze použít ve spojení s technikami popsanými výše a poskytnout oznámení o aplikaci porušení optimistické souběžnosti. RowUpdated nastane po každém pokusu o aktualizaci upraveného řádku z datové sady. Díky tomu můžete přidat speciální kód pro zpracování, včetně zpracování, když dojde k výjimce, přidání vlastních informací o chybách, přidání logiky opakování atd. Objekt RowUpdatedEventArgs vrátí vlastnost RecordsAffected obsahující počet řádků ovlivněných konkrétním aktualizačním příkazem pro upravený řádek v tabulce. Nastavením příkazu update pro testování optimistické souběžnosti vrátí vlastnost RecordsAffected v důsledku toho hodnotu 0, pokud došlo k narušení optimistické souběžnosti, protože nebyly aktualizovány žádné záznamy. V takovém případě dojde k výjimce. Událost RowUpdated umožňuje zpracovat tento výskyt a vyhnout se výjimce nastavením příslušné hodnoty RowUpdatedEventArgs.Status , například UpdateStatus.SkipCurrentRow. Další informace o události RowUpdated naleznete v tématu Zpracování událostí dataAdapter.
Volitelně můžete dataAdapter.ContinueUpdateOnError nastavit na true, před voláním aktualizace a odpovědět na informace o chybě uložené ve vlastnosti RowError konkrétního řádku po dokončení aktualizace. Další informace naleznete v tématu Informace o chybě řádku.
Příklad optimistické souběžnosti
Následuje jednoduchý příklad, který nastaví UpdateCommand dataAdapter na testování pro optimistickou souběžnost a potom použije událost RowUpdated k testování pro optimistická porušení souběžnosti. Když dojde k porušení optimistické souběžnosti, aplikace nastaví chybu řádku, pro který byla aktualizace vydána, aby odrážela porušení optimistické souběžnosti.
Všimněte si, že hodnoty parametrů předané klauzuli WHERE příkazu UPDATE jsou mapovány na původní hodnoty příslušných sloupců.
' Assumes connection is a valid SqlConnection.
Dim adapter As SqlDataAdapter = New SqlDataAdapter( _
"SELECT CustomerID, CompanyName FROM Customers ORDER BY CustomerID", _
connection)
' The Update command checks for optimistic concurrency violations
' in the WHERE clause.
adapter.UpdateCommand = New SqlCommand("UPDATE Customers " &
"(CustomerID, CompanyName) VALUES(@CustomerID, @CompanyName) " & _
"WHERE CustomerID = @oldCustomerID AND CompanyName = " &
"@oldCompanyName", connection)
adapter.UpdateCommand.Parameters.Add( _
"@CustomerID", SqlDbType.NChar, 5, "CustomerID")
adapter.UpdateCommand.Parameters.Add( _
"@CompanyName", SqlDbType.NVarChar, 30, "CompanyName")
' Pass the original values to the WHERE clause parameters.
Dim parameter As SqlParameter = adapter.UpdateCommand.Parameters.Add( _
"@oldCustomerID", SqlDbType.NChar, 5, "CustomerID")
parameter.SourceVersion = DataRowVersion.Original
parameter = adapter.UpdateCommand.Parameters.Add( _
"@oldCompanyName", SqlDbType.NVarChar, 30, "CompanyName")
parameter.SourceVersion = DataRowVersion.Original
' Add the RowUpdated event handler.
AddHandler adapter.RowUpdated, New SqlRowUpdatedEventHandler( _
AddressOf OnRowUpdated)
Dim dataSet As DataSet = New DataSet()
adapter.Fill(dataSet, "Customers")
' Modify the DataSet contents.
adapter.Update(dataSet, "Customers")
Dim dataRow As DataRow
For Each dataRow In dataSet.Tables("Customers").Rows
If dataRow.HasErrors Then
Console.WriteLine(dataRow (0) & vbCrLf & dataRow.RowError)
End If
Next
Private Shared Sub OnRowUpdated( _
sender As object, args As SqlRowUpdatedEventArgs)
If args.RecordsAffected = 0
args.Row.RowError = "Optimistic Concurrency Violation!"
args.Status = UpdateStatus.SkipCurrentRow
End If
End Sub
// Assumes connection is a valid SqlConnection.
SqlDataAdapter adapter = new SqlDataAdapter(
"SELECT CustomerID, CompanyName FROM Customers ORDER BY CustomerID",
connection);
// The Update command checks for optimistic concurrency violations
// in the WHERE clause.
adapter.UpdateCommand = new SqlCommand("UPDATE Customers Set CustomerID = @CustomerID, CompanyName = @CompanyName " +
"WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName", connection);
adapter.UpdateCommand.Parameters.Add(
"@CustomerID", SqlDbType.NChar, 5, "CustomerID");
adapter.UpdateCommand.Parameters.Add(
"@CompanyName", SqlDbType.NVarChar, 30, "CompanyName");
// Pass the original values to the WHERE clause parameters.
SqlParameter parameter = adapter.UpdateCommand.Parameters.Add(
"@oldCustomerID", SqlDbType.NChar, 5, "CustomerID");
parameter.SourceVersion = DataRowVersion.Original;
parameter = adapter.UpdateCommand.Parameters.Add(
"@oldCompanyName", SqlDbType.NVarChar, 30, "CompanyName");
parameter.SourceVersion = DataRowVersion.Original;
// Add the RowUpdated event handler.
adapter.RowUpdated += new SqlRowUpdatedEventHandler(OnRowUpdated);
DataSet dataSet = new DataSet();
adapter.Fill(dataSet, "Customers");
// Modify the DataSet contents.
adapter.Update(dataSet, "Customers");
foreach (DataRow dataRow in dataSet.Tables["Customers"].Rows)
{
if (dataRow.HasErrors)
Console.WriteLine(dataRow [0] + "\n" + dataRow.RowError);
}
protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs args)
{
if (args.RecordsAffected == 0)
{
args.Row.RowError = "Optimistic Concurrency Violation Encountered";
args.Status = UpdateStatus.SkipCurrentRow;
}
}