Delen via


Transacties

Met transacties kunt u meerdere SQL-instructies groeperen in één werkeenheid die als één atomische eenheid aan de database wordt toegewezen. Als een verklaring in de transactie mislukt, kunnen wijzigingen die door de vorige verklaringen 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.

Concurreniteit

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 van SqliteCommand een time-out krijgen als een andere transactie te lang duurt om te voltooien.

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-herhaalbare leesbewerkingen en schijnafbeeldingen mogelijk.

  • Een dirty read treedt op wanneer wijzigingen die binnen een transactie in behandeling zijn, worden geretourneerd door een query buiten de transactie, maar de wijzigingen in de transactie uiteindelijk 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 het aan BeginTransaction doorgegeven isolatieniveau als een minimumniveau. Het daadwerkelijke isolatieniveau wordt verhoogd naar read uncommitted of serializable.

Met de volgende code wordt een vuile leesbewerking gesimuleerd. Opmerking: de verbindingsreeks moet bevatten Cache=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 van een leestransactie naar een schrijftransactie wordt geüpgraded, zoals vereist 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.

Opslagpunten

Versie 6.0 van Microsoft.Data.Sqlite ondersteunt savepoints. Savepoints kunnen worden gebruikt om genestelde 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 Optimistisch Offline Lock-patroon om concurrente updates te detecteren en conflicten binnen een bewaarpunt 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();
}