Transaktioner
Med transaktioner kan du gruppera flera SQL-instruktioner i en enda arbetsenhet som checkas in i databasen som en atomisk enhet. Om någon instruktion i transaktionen misslyckas kan ändringar som gjorts av föregående instruktioner återställas. Databasens ursprungliga tillstånd när transaktionen startades bevaras. Om du använder en transaktion kan du också förbättra prestandan för SQLite när du gör många ändringar i databasen samtidigt.
Samtidighet
I SQLite tillåts endast en transaktion ha väntande ändringar i databasen i taget. På grund av detta kan anrop till BeginTransaction och metoderna på SqliteCommand Execute
timeout om en annan transaktion tar för lång tid att slutföra.
Mer information om låsning, återförsök och tidsgränser finns i Databasfel.
Isoleringsnivåer
Transaktioner kan serialiseras som standard i SQLite. Den här isoleringsnivån garanterar att alla ändringar som görs i en transaktion är helt isolerade. Andra instruktioner som körs utanför transaktionen påverkas inte av transaktionens ändringar.
SQLite har också stöd för icke-bekräftad läsning när du använder en delad cache. Den här nivån tillåter smutsiga läsningar, icke-läsbara läsningar och fantomer:
En felaktig läsning inträffar när ändringar som väntar i en transaktion returneras av en fråga utanför transaktionen, men ändringarna i transaktionen återställs. Resultaten innehåller data som aldrig har checkats in i databasen.
En icke-läsbar läsning inträffar när en transaktion frågar samma rad två gånger, men resultatet skiljer sig eftersom den ändrades mellan de två frågorna av en annan transaktion.
Fantomer är rader som ändras eller läggs till för att uppfylla where-satsen för en fråga under en transaktion. Om det tillåts kan samma fråga returnera olika rader när den körs två gånger i samma transaktion.
Microsoft.Data.Sqlite behandlar IsolationLevel som skickas till BeginTransaction som en miniminivå. Den faktiska isoleringsnivån höjs till antingen okommenterad eller serialiserbar.
Följande kod simulerar en felaktig läsning. Observera att anslutningssträng måste innehålla 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();
}
Uppskjutna transaktioner
Från och med Microsoft.Data.Sqlite version 5.0 kan transaktioner skjutas upp. Detta defererar skapandet av den faktiska transaktionen i databasen tills det första kommandot körs. Det gör också att transaktionen gradvis uppgraderas från en lästransaktion till en skrivtransaktion efter behov av dess kommandon. Detta kan vara användbart för att aktivera samtidig åtkomst till databasen under transaktionen.
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();
}
Varning
Kommandon i en uppskjuten transaktion kan misslyckas om de gör att transaktionen uppgraderas från en lästransaktion till en skrivtransaktion medan databasen är låst. När detta händer måste programmet försöka utföra hela transaktionen igen.
Spara poäng
Version 6.0 av Microsoft.Data.Sqlite stöder sparande. Du kan använda sparandepunkter för att skapa kapslade transaktioner. Savepoints kan återställas utan att påverka andra delar av transaktionen, och även om en sparandepunkt kan checkas in (frisläpps) kan ändringarna senare återställas som en del av den överordnade transaktionen.
Följande kod visar hur du använder mönstret Optimistiskt offlinelås för att identifiera samtidiga uppdateringar och lösa konflikter inom en sparande punkt som en del av en större transaktion.
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();
}