Sdílet prostřednictvím


Transakce

Transakce umožňují seskupit více příkazů SQL do jedné jednotky operace, která je potvrzena do databáze jako jedna atomická jednotka. Pokud některý příkaz v transakci selže, změny provedené předchozími příkazy lze vrátit zpět. Počáteční stav databáze při spuštění transakce je zachován. Použití transakce může také zvýšit výkon SQLite při provádění mnoha změn v databázi najednou.

Konkurentnost

V nástroji SQLite může mít v databázi najednou čekající změny pouze jedna transakce. Z tohoto důvodu může na BeginTransaction dojít k tomu, že volání metod Execute a SqliteCommand vyprší časový limit, pokud dokončení jiné transakce trvá příliš dlouho.

Další informace o uzamykání, opakování a vypršení časových limitů najdete v tématu Chyby databáze.

Úrovně izolace

Transakce jsou serializovatelné ve výchozím nastavení v SQLite. Tato úroveň izolace zaručuje, že všechny změny provedené v rámci transakce jsou zcela izolované. Jiné příkazy provedené mimo transakci nejsou ovlivněny změnami transakce.

SQLite také podporuje nepotvrzené čtení při použití sdílené mezipaměti. Tato úroveň umožňuje špinavé čtení, neopakovatelné čtení a fantomy:

  • Špinavé čtení nastane, když změny čekající v jedné transakci jsou vráceny dotazem mimo transakci, ale změny v transakci jsou vráceny zpět. Výsledky obsahují data, která nebyla nikdy ve skutečnosti potvrzena do databáze.

  • Neopakovatelné čtení nastane, když transakce dotazuje stejný řádek dvakrát, ale výsledky jsou odlišné, protože byly změněny mezi těmito dvěma dotazy jinou transakcí.

  • Přízraky jsou řádky, které se během transakce změní nebo přidají, aby splňovaly klauzuli WHERE v dotazu. Pokud je dotaz povolen, může stejný dotaz vrátit různé řádky, když je proveden dvakrát ve stejné transakci.

Microsoft.Data.Sqlite považuje úroveň IsolationLevel předanou do BeginTransaction za minimální úroveň. Skutečná úroveň izolace se zvýší na čtení, které není povoleno, nebo serializovatelné.

Následující kód simuluje špinavé čtení. Poznámka: Připojovací řetězec musí obsahovat 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();
}

Odložené transakce

Od Microsoft.Data.Sqlite verze 5.0 je možné transakce odložit. Tím se odkládá vytvoření skutečné transakce v databázi, dokud není vykonán první příkaz. Také způsobí, že se transakce postupně upgraduje z transakce čtení na transakci zápisu dle požadavků jeho příkazů. To může být užitečné pro povolení souběžného přístupu k databázi během transakce.

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();
}

Výstraha

Příkazy uvnitř odložené transakce mohou selhat, pokud způsobí, že transakce se upgraduje z transakce čtení na transakci zápisu, zatímco je databáze uzamčena. V takovém případě bude aplikace muset opakovat celou transakci.

Savepoints

Verze 6.0 Microsoft.Data.Sqlite podporuje savepointy. K vytváření vnořených transakcí lze použít uzlové body. Savepointy se dají vrátit zpět, aniž by to mělo vliv na jiné části transakce, a přestože může být savepoint potvrzen, jeho změny mohou být později vráceny zpět jako součást nadřazené transakce.

Následující kód znázorňuje použití modelu Optimistic Offline Lock k detekci souběžných aktualizací a řešení konfliktů v rámci bodu uložení v rámci větší transakce.

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();
}