Remarque
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.
Le pilote mssql-python prend en charge le contrôle complet des transactions, y compris les niveaux de commit, rollback, configuration autocommit et isolation des transactions.
Informations de base sur les transactions
Une transaction regroupe une séquence d’opérations de base de données en une seule unité de travail. Les transactions suivent les propriétés ACID :
- Atomicité : Toutes les opérations réussissent ou toutes échouent.
- Cohérence : La base de données reste en état valide.
- Isolation : Les transactions concurrentes ne s’interfèrent pas entre elles.
- Durabilité : Les changements engagés survivent aux pannes système.
Mode de validation automatique
Le autocommit paramètre détermine si les modifications sont automatiquement engagées.
Commit automatique désactivé (par défaut)
Par défaut, autocommit=False. Vous devez explicitement engager des modifications.
import mssql_python
conn = mssql_python.connect(connection_string)
print(conn.autocommit) # False
cursor = conn.cursor()
cursor.execute("CREATE TABLE #TxnBasic (Name NVARCHAR(50))")
cursor.execute("INSERT INTO #TxnBasic (Name) VALUES ('Widget')")
cursor.execute("INSERT INTO #TxnBasic (Name) VALUES ('Gadget')")
# Changes are staged but not visible to other connections
conn.commit() # Now changes are permanent
conn.close()
Si vous ne validez pas, le pilote annule les modifications lorsque la connexion est fermée.
Autocommit activé
Lorsque vous définissez autocommit=True, le pilote envoie chaque instruction immédiatement :
conn = mssql_python.connect(connection_string, autocommit=True)
# OR
conn.setautocommit(True)
cursor = conn.cursor()
cursor.execute("CREATE TABLE #AutoDemo (Name NVARCHAR(50))")
cursor.execute("INSERT INTO #AutoDemo (Name) VALUES ('Widget')")
# Immediately committed - no explicit commit needed
Caution
Lorsque l’autocommit est activé, vous ne pouvez pas revenir en arrière sur plusieurs instructions en groupe. N’utilise l’autocommit que lorsque c’est approprié pour ton cas d’usage.
Validation et annulation
Validations
Appel commit() pour rendre permanents les changements en attente :
cursor.execute("CREATE TABLE #CommitDemo (Name NVARCHAR(50), Price DECIMAL(10,2), CategoryID INT)")
cursor.execute("INSERT INTO #CommitDemo VALUES ('A',10,1),('B',20,1)")
cursor.execute("UPDATE #CommitDemo SET Price = Price * 1.1 WHERE CategoryID = 1")
conn.commit() # The update is now permanent
Retour arrière
Pour annuler les modifications en attente, appelez rollback():
try:
cursor.execute("CREATE TABLE #RollDemo (Name NVARCHAR(50), Price DECIMAL(10,2), CategoryID INT)")
cursor.execute("INSERT INTO #RollDemo VALUES ('A',10,1),('B',20,1)")
cursor.execute("UPDATE #RollDemo SET Price = Price * 1.1 WHERE CategoryID = 1")
# Verify the update
cursor.execute("SELECT AVG(Price) FROM #RollDemo WHERE CategoryID = 1")
avg_price = cursor.fetchval()
if avg_price > 100:
conn.rollback() # Price too high, undo both updates
print("Rolled back: average price would exceed limit")
else:
conn.commit()
except Exception as e:
conn.rollback() # Undo on error
raise
Validation et annulation au niveau du curseur
Pour plus de commodité, vous pouvez appeler commit() et rollback() sur les curseurs :
cursor = conn.cursor()
cursor.execute("CREATE TABLE #CursorDemo (Name NVARCHAR(50))")
cursor.execute("INSERT INTO #CursorDemo (Name) VALUES ('Widget')")
cursor.commit() # Delegates to connection
cursor.execute("DELETE FROM #CursorDemo WHERE Name = 'Widget'")
cursor.rollback() # Delegates to connection
Note
Les opérations de validation et d’annulation effectuées au niveau du curseur affectent tous les curseurs sur la même connexion, et pas seulement celui à partir duquel vous les appelez.
Gestionnaires de contexte
Le gestionnaire de contexte de connexion envoie la transaction à la sortie propre et la revient en arrière si une exception survient. La connexion se coupe toujours à la sortie. Lorsque vous définissez autocommit=True, les appels d’engagement et de retour en arrière n’ont aucun effet.
with mssql_python.connect(connection_string) as conn:
cursor = conn.cursor()
cursor.execute("CREATE TABLE #CtxDemo (Name NVARCHAR(50))")
cursor.execute("INSERT INTO #CtxDemo (Name) VALUES ('Widget')")
cursor.execute("INSERT INTO #CtxDemo (Name) VALUES ('Gadget')")
# Transaction is committed and connection is closed on exit
En cas d’exception, la transaction est annulée :
try:
with mssql_python.connect(connection_string) as conn:
cursor = conn.cursor()
cursor.execute("CREATE TABLE #TxnDemo (Name NVARCHAR(50))")
cursor.execute("INSERT INTO #TxnDemo (Name) VALUES ('Widget')")
raise ValueError("Something went wrong")
except ValueError:
pass
# Transaction is rolled back and connection is closed on exit
Niveaux d’isolation des transactions
Les niveaux d’isolation contrôlent la manière dont les transactions interagissent avec les transactions concurrentes. Définissez le niveau d’isolation en utilisant set_attr():
import mssql_python
conn = mssql_python.connect(connection_string)
# Set isolation level
conn.set_attr(
mssql_python.SQL_ATTR_TXN_ISOLATION,
mssql_python.SQL_TXN_SERIALIZABLE
)
Niveaux d’isolement disponibles
| Constante | Description |
|---|---|
SQL_TXN_READ_UNCOMMITTED |
Peut lire les modifications non validées provenant d’autres transactions (lectures sales autorisées) |
SQL_TXN_READ_COMMITTED |
Lit uniquement les données validées (par défaut dans SQL Server) |
SQL_TXN_REPEATABLE_READ |
Garantit une lecture cohérente dans la transaction |
SQL_TXN_SERIALIZABLE |
Extrême isolement ; Les transactions semblent s’exécuter de manière séquentielle |
Choisissez un niveau d’isolement
| Cas d’utilisation | Niveau recommandé |
|---|---|
| Charges de travail générales de l’OLTP |
READ_COMMITTED (valeur par défaut) |
| Rapports nécessitant des instantanés cohérents |
REPEATABLE_READ ou capture instantanée |
| Calculs financiers nécessitant de la précision | SERIALIZABLE |
| Charges de travail à forte dominante de lecture tolérant des données obsolètes | READ_UNCOMMITTED |
Isolation des instantanés
Pour l’isolation des instantanés, utilisez Transact-SQL (T-SQL). L’isolation des instantanés utilise le versionnement par lignes dans tempdb, ce qui peut augmenter les besoins de stockage sous des charges d’écriture lourdes.
# Enable snapshot isolation on the database (one-time setup, requires autocommit)
conn.commit()
conn.autocommit = True
cursor.execute("ALTER DATABASE AdventureWorks2022 SET ALLOW_SNAPSHOT_ISOLATION ON")
# Set isolation level while still in autocommit, then start the transaction
cursor.execute("SET TRANSACTION ISOLATION LEVEL SNAPSHOT")
conn.autocommit = False
cursor.execute("SELECT TOP 5 Name, ListPrice FROM Production.Product")
rows = cursor.fetchall()
for row in rows:
print(row.Name, row.ListPrice)
conn.commit()
Transactions imbriquées et points de sauvegarde
SQL Server prend en charge les points de sauvegarde pour un retour partiel en arrière au sein d’une transaction.
cursor = conn.cursor()
cursor.execute("BEGIN TRANSACTION")
cursor.execute("CREATE TABLE #SaveDemo (Name NVARCHAR(50))")
cursor.execute("INSERT INTO #SaveDemo (Name) VALUES ('Widget')")
cursor.execute("SAVE TRANSACTION SaveDemoPoint")
cursor.execute("INSERT INTO #SaveDemo (Name) VALUES ('Gadget')")
# Roll back to savepoint, keeping first insert
cursor.execute("ROLLBACK TRANSACTION SaveDemoPoint")
cursor.execute("COMMIT TRANSACTION")
Gérer les interblocages
Les blocages surviennent lorsque deux transactions attendent les verrous de l’autre. SQL Server détecte automatiquement les blocages et termine une transaction.
import time
def execute_with_retry(conn, cursor, sql, params=None, max_retries=3):
"""Execute SQL with deadlock retry logic."""
for attempt in range(max_retries):
try:
cursor.execute(sql, params)
return
except mssql_python.OperationalError as e:
if "1205" in str(e): # Deadlock error number
if attempt < max_retries - 1:
conn.rollback() # Clear the failed transaction
time.sleep(0.1 * (2 ** attempt)) # Exponential backoff
continue
raise
raise Exception(f"Failed after {max_retries} attempts")
Bonnes pratiques
Gardez les transactions courtes pour minimiser la durée du verrouillage et le potentiel de blocage.
Utilisez autocommit=False pour les transactions à plusieurs instructions devant être atomiques.
Gérez toujours les exceptions avec un retour en arrière :
conn = None try: conn = mssql_python.connect(connection_string) cursor = conn.cursor() cursor.execute("CREATE TABLE #RollbackPattern (ID INT, Name NVARCHAR(50))") cursor.execute("INSERT INTO #RollbackPattern (ID, Name) VALUES (1, 'Widget')") cursor.execute("UPDATE #RollbackPattern SET Name = 'Updated Widget' WHERE ID = 1") conn.commit() except Exception: if conn is not None: conn.rollback() raise finally: if conn is not None: conn.close()Utilisez des gestionnaires de contexte pour gérer automatiquement les transactions. Le gestionnaire de contexte valide en cas de sortie normale et annule en cas d’exception.
with mssql_python.connect(connection_string) as conn: cursor = conn.cursor() cursor.execute("CREATE TABLE #ContextManagerDemo (ID INT, Name NVARCHAR(50))") cursor.execute("INSERT INTO #ContextManagerDemo (ID, Name) VALUES (1, 'Widget')") cursor.execute("UPDATE #ContextManagerDemo SET Name = 'Committed Widget' WHERE ID = 1") # Committed automatically on exitChoisissez des niveaux d’isolation appropriés en fonction de vos exigences de cohérence par rapport aux besoins de performance.
Utilisez des indications de verrouillage pour les scénarios de lecture-modification-écriture afin d’éviter la perte de mises à jour. Lorsque vous lisez une valeur qui sera mise à jour dans la même transaction, utilisez des indices comme
WITH (UPDLOCK, ROWLOCK)sur le SELECT pour acquérir les verrous tôt et établir un ordre cohérent des verrous, ce qui réduit le risque de blocage.# Good: Acquire lock during read to prevent lost update pattern cursor.execute(""" SELECT Balance FROM Accounts WITH (UPDLOCK, ROWLOCK) WHERE ID = %(id)s """, {"id": account_id}) balance = cursor.fetchval() if balance >= amount: cursor.execute(""" UPDATE Accounts SET Balance = Balance - %(amount)s WHERE ID = %(id)s """, {"amount": amount, "id": account_id})Mettez en place une logique de retentative pour les échecs transitoires comme les blocages.
Exemple : Transfert de fonds (opération atomique)
Cet exemple démontre la logique de transfert atomique avec des indices de verrouillage pour éviter les mises à jour perdues dans des scénarios concurrents :
def transfer_funds(conn, from_account, to_account, amount):
"""Transfer funds atomically between accounts."""
cursor = conn.cursor()
try:
# Read balance with lock hint to prevent lost updates
cursor.execute(
"SELECT Balance FROM Accounts WITH (UPDLOCK, ROWLOCK) WHERE AccountID = %(account_id)s",
{"account_id": from_account}
)
balance = cursor.fetchval()
if balance is None:
raise ValueError(f"Source account {from_account} not found")
if balance < amount:
raise ValueError("Insufficient funds")
# Debit source account
cursor.execute(
"UPDATE Accounts SET Balance = Balance - %(amount)s WHERE AccountID = %(account_id)s",
{"amount": amount, "account_id": from_account}
)
# Credit destination account
cursor.execute(
"UPDATE Accounts SET Balance = Balance + %(amount)s WHERE AccountID = %(account_id)s",
{"amount": amount, "account_id": to_account}
)
if cursor.rowcount != 1:
raise ValueError(f"Destination account {to_account} not found")
conn.commit()
print(f"Transferred ${amount} from {from_account} to {to_account}")
except Exception:
conn.rollback()
raise
Les indications WITH (UPDLOCK, ROWLOCK) de verrouillage sur l’instruction SELECT garantissent que le verrou est acquis dès le début. Cela empêche une autre transaction de lire simultanément le même solde et de créer un scénario de mise à jour perdue où les deux transactions lisent l’ancien solde, effectuent des mises à jour séparées, et seule la dernière mise à jour persiste.
Portée de la table temporaire
Les tables temporaires (#tablename) ont une portée limitée à la session, mais leur création s’inscrit dans la transaction en cours. Si vous créez une table temporaire et que la transaction est annulée, la table temporaire est supprimée :
conn = mssql_python.connect(connection_string) # autocommit=False
cursor = conn.cursor()
cursor.execute("CREATE TABLE #Staging (ID INT, Name NVARCHAR(50))")
cursor.execute("INSERT INTO #Staging VALUES (1, 'test')")
# Rollback removes the temp table entirely
conn.rollback()
# This fails: Invalid object name '#Staging'
try:
cursor.execute("SELECT * FROM #Staging")
except mssql_python.ProgrammingError:
print("Temp table was dropped by rollback")
Pour garder une table temporaire indépendante de votre transaction de données, validez après l’avoir créée :
cursor.execute("CREATE TABLE #Staging (ID INT, Name NVARCHAR(50))")
conn.commit() # Temp table persists regardless of later rollbacks
# Now data operations can roll back without losing the table
cursor.execute("INSERT INTO #Staging VALUES (1, 'test')")
conn.rollback() # Data is gone, but #Staging still exists
Instructions DDL nécessitant une validation automatique
Certaines instructions DDL, telles que CREATE DATABASE, ALTER DATABASE, et DROP DATABASE, ne peuvent pas s’exécuter à l’intérieur d’une transaction. Définissez autocommit=True avant de les exécuter :
conn.autocommit = True
cursor.execute("CREATE DATABASE TestDB")
conn.autocommit = False # Return to transactional mode
Si vous exécutez CREATE DATABASE avec autocommit=False, vous obtenez une erreur : CREATE DATABASE statement not allowed within multi-statement transaction.