Megosztás:


Tranzakciók

A tranzakciók lehetővé teszik több SQL-utasítás csoportosítását egyetlen munkaegységbe, amely egy atomi egységként van lekötve az adatbázishoz. Ha a tranzakció bármely utasítása meghiúsul, az előző utasítások módosításai visszaállíthatók. A tranzakció elindításakor az adatbázis kezdeti állapota megmarad. A tranzakció használata az SQLite teljesítményét is javíthatja, ha egyszerre számos módosítást hajt végre az adatbázisban.

Konkurencia

Az SQLite-ben egyszerre csak egy tranzakció rendelkezhet függőben lévő módosításokkal az adatbázisban. Emiatt előfordulhat, hogy a BeginTransaction és Execute metódusok hívásai SqliteCommand időtúllépést szenvedhetnek, ha egy másik tranzakció túl sokáig tart.

További információ a zárolásról, az újrapróbálkozásokról és az időtúllépésekről: Adatbázishibák.

Elkülönítési szintek

A tranzakciók alapértelmezés szerint szerializálhatók az SQLite-ben. Ez az elkülönítési szint garantálja, hogy a tranzakción belül végrehajtott módosítások teljesen el vannak különítve. A tranzakción kívül végrehajtott egyéb utasításokat a tranzakció módosításai nem befolyásolják.

Az SQLite támogatja a nem véglegesített olvasást is megosztott gyorsítótár használatakor. Ez a szint lehetővé teszi a piszkos olvasásokat, a nem megismételhető olvasásokat és a fantomokat:

  • A piszkos olvasás akkor fordul elő, ha az egyik tranzakcióban függőben lévő módosításokat egy, a tranzakción kívüli lekérdezés adja vissza, de a tranzakció módosításait a rendszer visszaállítja. Az eredmények olyan adatokat tartalmaznak, amelyeket soha nem véglegesítettek az adatbázishoz.

  • Nem megismételhető olvasás akkor fordul elő, ha egy tranzakció kétszer lekérdezi ugyanazt a sort, de az eredmények eltérnek, mert a két lekérdezés között egy másik tranzakció módosította.

  • A fantomok olyan sorok, amelyek módosulnak vagy hozzáadódnak egy tranzakció során, hogy megfeleljenek a lekérdezés WHERE záradékának. Ha engedélyezve van, ugyanaz a lekérdezés eltérő sorokat adhat vissza, ha kétszer hajtják végre ugyanabban a tranzakcióban.

A Microsoft.Data.Sqlite minimális szintként kezeli az átadott BeginTransaction elkülönítési szintet. A rendszer előlépteti a tényleges elkülönítési szintet a nem véglegesített vagy szerializálható olvasási szintre.

Az alábbi kód egy piszkos olvasást szimulál. Vegye figyelembe, hogy a kapcsolati sztringnek tartalmaznia kell a következőt 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();
}

Halasztott tranzakciók

A Microsoft.Data.Sqlite 5.0-s verziójától kezdve a tranzakciók elhalaszthatók. Ez az adatbázis tényleges tranzakciójának létrehozását ellenjavallja az első parancs végrehajtásáig. Azt is eredményezi, hogy a tranzakció fokozatosan frissül az olvasási tranzakcióról egy írási tranzakcióra a parancsai által szükséges módon. Ez hasznos lehet az adatbázishoz való egyidejű hozzáférés engedélyezéséhez a tranzakció során.

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

Figyelmeztetés

A késleltetett tranzakción belüli parancsok meghiúsulhatnak, ha a tranzakciót olvasási tranzakcióról írási tranzakcióra frissítik, miközben az adatbázis zárolva van. Ha ez történik, az alkalmazásnak újra meg kell próbálkoznia a teljes tranzakcióval.

Mentési pontok

A Microsoft.Data.Sqlite 6.0-s verziója támogatja a mentési pontokat. A mentési pontok beágyazott tranzakciók létrehozásához használhatók. A mentési pontok a tranzakció más részeinek befolyásolása nélkül is visszaállíthatók, és bár a mentési pontok véglegesítésre (kiadásra) kerülhetnek, a módosítások később visszahozhatók a szülőtranzakció részeként.

Az alábbi kód bemutatja, hogy az optimista offline zárolási minta használatával észlelheti az egyidejű frissítéseket, és feloldhatja a mentési pontok ütközéseit egy nagyobb tranzakció részeként.

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