Bagikan melalui


Transaksi

Transaksi memungkinkan Anda mengelompokkan beberapa pernyataan SQL ke dalam satu unit kerja yang berkomitmen pada database sebagai satu unit atom. Jika ada pernyataan dalam transaksi yang gagal, perubahan yang dilakukan oleh pernyataan sebelumnya dapat digulung balik. Status awal database saat transaksi dimulai dipertahankan. Menggunakan transaksi juga dapat meningkatkan performa pada SQLite saat membuat banyak perubahan pada database sekaligus.

Konkurensi

Di SQLite, hanya satu transaksi yang diizinkan untuk memiliki perubahan yang tertunda dalam database pada satu waktu. Karena itu, panggilan ke BeginTransaction dan metode Execute pada SqliteCommand mungkin mengalami batas waktu jika transaksi lain membutuhkan waktu terlalu lama untuk diselesaikan.

Untuk informasi selengkapnya tentang penguncian, percobaan ulang, dan batas waktu, lihat Kesalahan database.

Tingkat isolasi

Transaksi dapat diserialisasikan secara default di SQLite. Tingkat isolasi ini menjamin bahwa setiap perubahan yang dilakukan dalam transaksi sepenuhnya terisolasi. Pernyataan lain yang dijalankan di luar transaksi tidak terpengaruh oleh perubahan transaksi.

SQLite juga mendukung baca yang tidak dikomit saat menggunakan cache bersama. Tingkat ini memungkinkan bacaan kotor, bacaan yang tidak konsisten, dan bacaan fantom.

  • Pembacaan kotor terjadi ketika perubahan yang tertunda dalam satu transaksi dikembalikan oleh kueri di luar transaksi, tetapi perubahan dalam transaksi digulung balik. Hasilnya berisi data yang tidak pernah benar-benar diterapkan ke database.

  • Pembacaan yang tidak dapat diulang terjadi ketika transaksi mengambil baris yang sama dua kali, tetapi hasilnya berbeda karena diubah di antara dua kueri oleh transaksi lain.

  • Phantom adalah baris-baris yang diubah atau ditambahkan untuk memenuhi klausa 'where' dari kueri selama transaksi. Jika diizinkan, kueri yang sama dapat mengembalikan baris yang berbeda saat dijalankan dua kali dalam transaksi yang sama.

Microsoft.Data.Sqlite memperlakukan IsolationLevel yang diteruskan ke BeginTransaction sebagai tingkat minimum. Tingkat isolasi aktual akan dinaikkan menjadi read uncommitted atau serializable.

Kode berikut mensimulasikan bacaan kotor. Perhatikan, string koneksi harus menyertakan 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();
}

Transaksi yang ditangguhkan

Dimulai dengan Microsoft.Data.Sqlite versi 5.0, transaksi dapat ditangguhkan. Ini menangguhkan pembuatan transaksi aktual dalam database hingga perintah pertama dijalankan. Ini juga menyebabkan transaksi secara bertahap ditingkatkan dari transaksi baca ke transaksi tulis sesuai dengan perintah-perintahnya. Ini dapat berguna untuk mengaktifkan akses bersamaan ke database selama transaksi.

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

Peringatan

Perintah di dalam transaksi yang ditangguhkan dapat gagal jika menyebabkan transaksi ditingkatkan dari transaksi baca ke transaksi tulis saat database dikunci. Ketika ini terjadi, aplikasi perlu mencoba kembali seluruh transaksi.

Titik simpan

Microsoft.Data.Sqlite versi 6.0 mendukung titik penyimpanan. Titik penyimpanan dapat digunakan untuk membuat transaksi berlapis. Titik penyimpanan dapat dibatalkan tanpa memengaruhi bagian transaksi lainnya, dan meskipun sebuah titik penyimpanan dapat diselesaikan, perubahannya nanti bisa dibatalkan sebagai bagian dari transaksi induknya.

Kode berikut ini mengilustrasikan penggunaan pola Kunci Offline Optimis untuk mendeteksi pembaruan bersamaan dan menyelesaikan konflik dalam titik penyimpanan sebagai bagian dari transaksi yang lebih besar.

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