Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Les transactions vous permettent de regrouper plusieurs instructions SQL en une seule unité de travail validée dans la base de données en tant qu’unité atomique. Si une instruction dans la transaction échoue, les modifications apportées par les instructions précédentes peuvent être annulées. L’état initial de la base de données lors du démarrage de la transaction est conservé. L’utilisation d’une transaction peut également améliorer les performances sur SQLite lors de nombreuses modifications apportées à la base de données à la fois.
Concurrence
Dans SQLite, une seule transaction est autorisée à avoir des modifications en attente dans la base de données à la fois. En raison de cela, les appels à BeginTransaction et les méthodes Execute
sur SqliteCommand peuvent expirer si une autre transaction prend trop de temps.
Pour plus d’informations sur le verrouillage, les nouvelles tentatives et les délais d’expiration, consultez les erreurs de base de données.
Niveaux d’isolation
Les transactions sont sérialisables par défaut dans SQLite. Ce niveau d’isolation garantit que toutes les modifications apportées au sein d’une transaction sont complètement isolées. Les autres instructions exécutées en dehors de la transaction ne sont pas affectées par les modifications de la transaction.
SQLite prend également en charge la lecture non validée lors de l’utilisation d’un cache partagé. Ce niveau permet les lectures erronées, les lectures non reproductibles et les fantômes :
Une lecture erronée se produit lorsque les modifications en attente dans une transaction sont retournées par une requête en dehors de la transaction, mais que les modifications apportées à la transaction sont annulées. Les résultats contiennent des données qui n’ont jamais été réellement validées dans la base de données.
Une lecture non répécable se produit lorsqu’une transaction interroge la même ligne deux fois, mais les résultats sont différents, car il a été modifié entre les deux requêtes par une autre transaction.
Les fantômes sont des lignes qui sont modifiées ou ajoutées pour satisfaire à la clause WHERE d’une requête pendant une transaction. Si elle est autorisée, la même requête peut retourner différentes lignes lorsqu’elle est exécutée deux fois dans la même transaction.
Microsoft.Data.Sqlite traite IsolationLevel passé à BeginTransaction comme un niveau minimal. Le niveau d’isolement réel sera promu au niveau de lecture non validée ou au niveau de sérialisation.
Le code suivant simule une lecture sale. Notez que la chaîne de connexion doit inclure 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();
}
Transactions différées
À compter de Microsoft.Data.Sqlite version 5.0, les transactions peuvent être différées. Cela reporte la création de la transaction réelle dans la base de données jusqu’à ce que la première commande soit exécutée. Elle entraîne également la mise à niveau progressive de la transaction de lecture vers une transaction d’écriture en fonction des besoins de ses commandes. Cela peut être utile pour activer l’accès simultané à la base de données pendant la transaction.
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();
}
Avertissement
Les commandes à l’intérieur d’une transaction différée peuvent échouer s’ils provoquent la mise à niveau de la transaction de lecture vers une transaction d’écriture pendant que la base de données est verrouillée. Lorsque cela se produit, l’application doit réessayer toute la transaction.
Points de sauvegarde
La version 6.0 de Microsoft.Data.Sqlite prend en charge les points d’enregistrement. Les points de sauvegarde peuvent être utilisés pour créer des transactions imbriquées. Les points d’enregistrement peuvent être restaurer sans affecter d’autres parties de la transaction, et même si un point d’enregistrement est confirmé (publié), ses modifications peuvent être annulées ultérieurement dans le cadre de sa transaction parent.
Le code suivant illustre l’utilisation du patron de verrouillage optimiste hors ligne pour détecter les mises à jour simultanées et résoudre les conflits au sein d’un point de sauvegarde dans le cadre d’une transaction plus étendue.
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();
}