Sdílet prostřednictvím


Transakce

Transakce umožňují seskupit více příkazů SQL do jedné jednotky práce, 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.

Souběžnost

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 dojít k BeginTransaction vypršení časového limitu Execute volání a metod v SqliteCommand případě, že 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í.

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

Microsoft.Data.Sqlite považuje úroveň IsolationLevel předanou jako BeginTransaction 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 poškodí vytvoření skutečné transakce v databázi, dokud se nespustí první příkaz. Také způsobí, že transakce postupně upgradovat z transakce čtení na zápis transakce podle potřeby jeho příkazy. 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();
}

Upozorňující

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 body ukládání. K vytváření vnořených transakcí je možné použít body ukládání. Savepoints se dají vrátit zpět, aniž by to mělo vliv na jiné části transakce, a i když může být potvrzen (vydáno) uložit bod, mohou se jeho změny později vrátit 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();
}