次の方法で共有


トランザクション

トランザクションを使用すると、複数の SQL ステートメントを、1 つのアトミック単位としてデータベースにコミットされる単一の作業単位にグループ化できます。 トランザクション内のステートメントが失敗した場合は、前のステートメントによって行われた変更をロールバックできます。 トランザクションの開始時のデータベースの初期状態は保持されます。 トランザクションを使用すると、一度にデータベースに多数の変更を加えたときに SQLite のパフォーマンスを向上させることもできます。

コンカレンシー

SQLite では、データベース内で一度に 1 つのトランザクションに変更を保留することが許可されます。 このため、別のトランザクションの完了に時間がかかりすぎると、BeginTransactionExecuteSqliteCommand メソッドの呼び出しがタイムアウトする可能性があります。

ロック、再試行、タイムアウトの詳細については、「 データベース エラー」を参照してください。

分離レベル

SQLite では、トランザクションは既定で シリアル化できます 。 この分離レベルでは、トランザクション内で行われた変更が完全に分離されることが保証されます。 トランザクションの外部で実行されるその他のステートメントは、トランザクションの変更の影響を受けません。

SQLite は、共有キャッシュを使用する場合に 未コミット読み取り をサポートします。 このレベルでは、ダーティリード、ノンリピータブルリード、およびファントムリードが発生する可能性があります。

  • ダーティ リードは、あるトランザクション内で保留中の変更がトランザクション外のクエリによって返され、その後、変更がロールバックされる場合に発生します。 結果には、データベースに実際にコミットされなかったデータが含まれます。

  • 反復 不可能な読み取り は、トランザクションが同じ行に 2 回クエリを実行したときに発生しますが、別のトランザクションによって 2 つのクエリ間で変更されたため、結果は異なります。

  • ファントム は、トランザクション中にクエリの where 句を満たすように変更または追加される行です。 許可されている場合、同じクエリが同じトランザクションで 2 回実行されると、異なる行が返される可能性があります。

Microsoft.Data.Sqlite は、 BeginTransaction に渡された IsolationLevel を最小レベルとして扱います。 実際の分離レベルは、read uncommitted または serializable に昇格されます。

次のコードは、ダーティ リードをシミュレートしています。 接続文字列には 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();
}

遅延トランザクション

Microsoft.Data.Sqlite バージョン 5.0 以降では、トランザクションを遅延させることができます。 これにより、最初のコマンドが実行されるまで、データベースでの実際のトランザクションの作成が延期されます。 また、コマンドで必要に応じて、トランザクションが読み取りトランザクションから書き込みトランザクションに徐々にアップグレードされます。 これは、トランザクション中にデータベースへの同時アクセスを有効にする場合に役立ちます。

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

Warnung

遅延トランザクション内のコマンドは、データベースのロック中にトランザクションを読み取りトランザクションから書き込みトランザクションにアップグレードすると失敗する可能性があります。 この場合、アプリケーションはトランザクション全体を再試行する必要があります。

セーブポイント

Microsoft.Data.Sqlite のバージョン 6.0 では、セーブポイントがサポートされています。 セーブポイントを使用して、入れ子になったトランザクションを作成できます。 セーブポイントは、トランザクションの他の部分に影響を与えずにロールバックできます。セーブポイントをコミット (解放) しても、その変更は後で親トランザクションの一部としてロールバックされる可能性があります。

次のコードは、オプティミスティック オフライン ロック パターンを使用して同時更新を検出し、大規模なトランザクションの一部としてセーブポイント内の競合を解決する方法を示しています。

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