Transacties
Met transacties kunt u meerdere SQL-instructies groeperen in één werkeenheid die als één atomische eenheid aan de database wordt toegewezen. Als een instructie in de transactie mislukt, kunnen wijzigingen die door de vorige instructies zijn aangebracht, worden teruggedraaid. De initiële status van de database toen de transactie werd gestart, blijft behouden. Het gebruik van een transactie kan ook de prestaties van SQLite verbeteren wanneer u meerdere wijzigingen in de database tegelijk aanbrengt.
Gelijktijdigheid
In SQLite mag slechts één transactie tegelijkertijd wijzigingen hebben die in de database in behandeling zijn. Hierdoor kunnen aanroepen naar BeginTransaction en de Execute
methoden op SqliteCommand een time-out optreden als het te lang duurt voordat een andere transactie is voltooid.
Zie Databasefouten voor meer informatie over vergrendeling, nieuwe pogingen en time-outs.
Isolatieniveaus
Transacties zijn standaard serialiseerbaar in SQLite. Dit isolatieniveau garandeert dat alle wijzigingen die in een transactie zijn aangebracht, volledig geïsoleerd zijn. Andere instructies die buiten de transactie worden uitgevoerd, worden niet beïnvloed door de wijzigingen van de transactie.
SQLite biedt ook ondersteuning voor niet-verzonden leesbewerkingen bij het gebruik van een gedeelde cache. Dit niveau maakt vuile leesbewerkingen, niet-herpeatbare leesbewerkingen en phantoms mogelijk:
Een vuile leesbewerking treedt op wanneer wijzigingen die in één transactie in behandeling zijn, worden geretourneerd door een query buiten de transactie, maar de wijzigingen in de transactie worden teruggedraaid. De resultaten bevatten gegevens die nooit daadwerkelijk zijn doorgevoerd in de database.
Een niet-opnieuw leesbare leesbewerking vindt plaats wanneer een transactie twee keer dezelfde rij opvraagt, maar de resultaten verschillen omdat deze is gewijzigd tussen de twee query's door een andere transactie.
Phantoms zijn rijen die worden gewijzigd of toegevoegd om te voldoen aan de where-component van een query tijdens een transactie. Als dit is toegestaan, kan dezelfde query verschillende rijen retourneren wanneer deze twee keer in dezelfde transactie worden uitgevoerd.
Microsoft.Data.Sqlite behandelt isolatieniveau als BeginTransaction een minimumniveau. Het werkelijke isolatieniveau wordt gepromoveerd tot het lezen van niet-verzonden of serialiseerbaar.
Met de volgende code wordt een vuile leesbewerking gesimuleerd. Houd er rekening mee dat de verbindingsreeks moet bevattenCache=Shared
.
using (var firstTransaction = firstConnection.BeginTransaction())
{
var updateCommand = firstConnection.CreateCommand();
updateCommand.CommandText =
@"
UPDATE data
SET value = 'dirty'
";
updateCommand.ExecuteNonQuery();
// Without ReadUncommitted, the command will time out since the table is locked
// while the transaction on the first connection is active
using (secondConnection.BeginTransaction(IsolationLevel.ReadUncommitted))
{
var queryCommand = secondConnection.CreateCommand();
queryCommand.CommandText =
@"
SELECT *
FROM data
";
var value = (string)queryCommand.ExecuteScalar();
Console.WriteLine($"Value: {value}");
}
firstTransaction.Rollback();
}
Uitgestelde transacties
Vanaf Microsoft.Data.Sqlite versie 5.0 kunnen transacties worden uitgesteld. Hierdoor wordt het maken van de werkelijke transactie in de database uitgesteld totdat de eerste opdracht wordt uitgevoerd. Het zorgt er ook voor dat de transactie geleidelijk wordt bijgewerkt van een leestransactie naar een schrijftransactie, indien nodig door de opdrachten. Dit kan handig zijn voor het inschakelen van gelijktijdige toegang tot de database tijdens de transactie.
using (var transaction = connection.BeginTransaction(deferred: true))
{
// Before the first statement of the transaction is executed, both concurrent
// reads and writes are allowed
var readCommand = connection.CreateCommand();
readCommand.CommandText =
@"
SELECT *
FROM data
";
var value = (long)readCommand.ExecuteScalar();
// After a the first read statement, concurrent writes are blocked until the
// transaction completes. Concurrent reads are still allowed
var writeCommand = connection.CreateCommand();
writeCommand.CommandText =
@"
UPDATE data
SET value = $newValue
";
writeCommand.Parameters.AddWithValue("$newValue", value + 1L);
writeCommand.ExecuteNonQuery();
// After the first write statement, both concurrent reads and writes are blocked
// until the transaction completes
transaction.Commit();
}
Waarschuwing
Opdrachten in een uitgestelde transactie kunnen mislukken als ze ertoe leiden dat de transactie wordt bijgewerkt van een leestransactie naar een schrijftransactie terwijl de database is vergrendeld. Wanneer dit gebeurt, moet de toepassing de hele transactie opnieuw proberen.
Savepoints
Versie 6.0 van Microsoft.Data.Sqlite ondersteunt savepoints. Savepoints kunnen worden gebruikt om geneste transacties te maken. Savepoints kunnen worden teruggedraaid zonder dat dit van invloed is op andere onderdelen van de transactie, en hoewel een savepoint kan worden doorgevoerd (vrijgegeven), kunnen de wijzigingen later worden teruggedraaid als onderdeel van de bovenliggende transactie.
De volgende code illustreert het gebruik van het patroon Optimistisch offlinevergrendeling om gelijktijdige updates te detecteren en conflicten binnen een opslagpunt op te lossen als onderdeel van een grotere transactie.
using (var transaction = connection.BeginTransaction())
{
// Transaction may include additional statements before the savepoint
var updated = false;
do
{
// Begin savepoint
transaction.Save("optimistic-update");
var insertCommand = connection.CreateCommand();
insertCommand.CommandText =
@"
INSERT INTO audit
VALUES (datetime('now'), 'User updates data with id 1')
";
insertCommand.ExecuteScalar();
var updateCommand = connection.CreateCommand();
updateCommand.CommandText =
@"
UPDATE data
SET value = 2,
version = $expectedVersion + 1
WHERE id = 1
AND version = $expectedVersion
";
updateCommand.Parameters.AddWithValue("$expectedVersion", expectedVersion);
var recordsAffected = updateCommand.ExecuteNonQuery();
if (recordsAffected == 0)
{
// Concurrent update detected! Rollback savepoint and retry
transaction.Rollback("optimistic-update");
// TODO: Resolve update conflicts
}
else
{
// Update succeeded. Commit savepoint and continue with the transaction
transaction.Release("optimistic-update");
updated = true;
}
}
while (!updated);
// Additional statements may be included after the savepoint
transaction.Commit();
}