トランザクションを使用すると、複数の SQL ステートメントを、1 つのアトミック単位としてデータベースにコミットされる単一の作業単位にグループ化できます。 トランザクション内のステートメントが失敗した場合は、前のステートメントによって行われた変更をロールバックできます。 トランザクションの開始時のデータベースの初期状態は保持されます。 トランザクションを使用すると、一度にデータベースに多数の変更を加えたときに SQLite のパフォーマンスを向上させることもできます。
コンカレンシー
SQLite では、データベース内で一度に 1 つのトランザクションに変更を保留することが許可されます。 このため、別のトランザクションの完了に時間がかかりすぎると、BeginTransactionとExecute
のSqliteCommand メソッドの呼び出しがタイムアウトする可能性があります。
ロック、再試行、タイムアウトの詳細については、「 データベース エラー」を参照してください。
分離レベル
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();
}
.NET