Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
De mssql-python-driver ondersteunt volledige transactiecontrole, inclusief commit, rollback, autocommit-configuratie en transactieisolatieniveaus.
Basisprincipes van transacties
Een transactie groepeert een reeks databasebewerkingen in één eenheid werk. Transacties volgen de ACID-eigenschappen:
- Atomiciteit: Alle operaties slagen of mislukken allemaal.
- Consistentie: De database blijft in een geldige staat.
- Isolatie: Gelijktijdige transacties interfereren elkaar niet.
- Duurzaamheid: Toegewijde wijzigingen overleven systeemstoringen.
Modus Automatisch aanpassen
Met de instelling autocommit wordt bepaald of wijzigingen automatisch worden doorgevoerd.
Autocommit uitgeschakeld (standaard)
autocommit=FalseStandaard. Je moet expliciet veranderingen doorvoeren.
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()
Als je de wijzigingen niet vastlegt, verwerpt de driver ze zodra de verbinding wordt gesloten.
Autocommit ingeschakeld
Wanneer je autocommit=True instelt, commit de driver elke instructie onmiddellijk:
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
Waarschuwing
Wanneer autocommmit is ingeschakeld, kun je niet meerdere statements als groep terugrollen. Gebruik autocommit alleen wanneer het geschikt is voor jouw situatie.
Doorvoeren en terugdraaien
Doorvoeren
Bel commit() om lopende wijzigingen permanent te maken:
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
Terugdraaien
Roep rollback() aan om openstaande wijzigingen te verwerpen:
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
Commit en rollback op curniveau
Voor het gemak kun je commit() en rollback() op cursors aanroepen:
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
Opmerking
Cursor-level commit en rollback beïnvloeden alle cursors op dezelfde verbinding, niet alleen de cursor waarop je het aangeeft.
Contextbeheerders
De contextmanager voor de verbinding bevestigt de transactie bij een foutloze afsluiting en draait deze terug als er een uitzondering optreedt. De verbinding wordt altijd gesloten bij afsluiten. Wanneer je autocommit=True instelt, hebben aanroepen van commit en rollback geen effect.
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
Als er een uitzondering optreedt, wordt de transactie teruggedraaid:
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
Isolatieniveaus voor transacties
Isolatieniveaus bepalen hoe transacties interageren met gelijktijdige transacties. Stel het isolatieniveau in met :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
)
Beschikbare isolatieniveaus
| Constante | Beschrijving |
|---|---|
SQL_TXN_READ_UNCOMMITTED |
Kan niet-doorgevoerde wijzigingen van andere transacties lezen (vuile reads toegestaan) |
SQL_TXN_READ_COMMITTED |
Leest alleen toegewezen data (standaard in SQL Server) |
SQL_TXN_REPEATABLE_READ |
Garandeert consistente leesbewerkingen binnen een transactie |
SQL_TXN_SERIALIZABLE |
Hoogste isolatie; Transacties lijken opeenvolgend te verlopen |
Kies een isolatieniveau
| Gebruiksituatie | Aanbevolen niveau |
|---|---|
| Algemene OLTP-workloads |
READ_COMMITTED (standaard) |
| Rapporten die consistente snapshots nodig hebben |
REPEATABLE_READ of snapshot |
| Financiële berekeningen die nauwkeurigheid vereisen | SERIALIZABLE |
| Leesintensieve werklasten die verouderde gegevens tolereren | READ_UNCOMMITTED |
Isolatie van momentopnamen
Voor snapshot-isolatie gebruik je Transact-SQL (T-SQL). Snapshot-isolatie gebruikt rijversiebeheer in tempdb, wat de opslagvereisten kan verhogen bij schrijfintensieve workloads.
# 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()
Geneste transacties en savepoints
SQL Server ondersteunt savepoints voor gedeeltelijke rollback binnen een transactie.
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")
Impasses afhandelen
Deadlocks ontstaan wanneer twee transacties wachten op elkaars vergrendelingen. SQL Server detecteert automatisch deadlocks en beëindigt één transactie.
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")
Beste praktijken
Houd transacties kort om de duur van de vergrendeling en de kans op deadlocks te minimaliseren.
Gebruik autocommit=False voor transacties met meerdere statements die atomair moeten zijn.
Altijd uitzonderingen met rollback behandelen :
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()Gebruik contextmanagers om transacties automatisch te beheren. De contextbeheerder bevestigt bij een correcte afsluiting en rolt terug als er een uitzondering optreedt.
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 exitKies geschikte isolatieniveaus op basis van je consistentie-eisen versus prestatie-behoeften.
Gebruik vergrendelingshints voor read-modify-write-patronen om verloren updates te voorkomen. Wanneer je een waarde leest die binnen dezelfde transactie wordt bijgewerkt, gebruik dan hints zoals
WITH (UPDLOCK, ROWLOCK)op de SELECT om sloten vroegtijdig te verkrijgen en consistente lock-ordering te vestigen, wat het risico op deadlocks vermindert.# 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})Implementeer herkansingslogica voor tijdelijke storingen zoals deadlocks.
Voorbeeld: Overdrachtsfondsen (atomaire operatie)
Dit voorbeeld demonstreert atomaire overdrachtslogica met lock hints om verloren updates in gelijktijdige scenario's te voorkomen:
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
De lock-hints WITH (UPDLOCK, ROWLOCK) op de SELECT zorgen ervoor dat het slot vroeg wordt verkregen. Dit voorkomt dat een andere transactie hetzelfde saldo gelijktijdig leest en een verloren update-scenario creëert waarbij beide transacties het oude saldo lezen, aparte updates uitvoeren en alleen de laatste update blijft bestaan.
Bereik van tijdelijke tabellen
Tijdelijke tabellen (#tablename) zijn beperkt tot de sessie, maar hun creatie maakt deel uit van de huidige transactie. Als je een tijdelijke tabel aanmaakt en de transactie wordt teruggerold, wordt de tijdelijke tabel verwijderd:
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")
Om een tijdelijke tabel onafhankelijk te houden van je gegevenstransactie, voer je na het aanmaken ervan een commit uit:
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
DDL-statements die autocommit vereisen
Sommige DDL-instructies, zoals CREATE DATABASE, ALTER DATABASE, en DROP DATABASE, kunnen niet binnen een transactie worden uitgevoerd. Stel autocommit=True in voordat je deze uitvoert:
conn.autocommit = True
cursor.execute("CREATE DATABASE TestDB")
conn.autocommit = False # Return to transactional mode
Als je met CREATE DATABASEuitvoertautocommit=False, krijg je een foutmelding:CREATE DATABASE statement not allowed within multi-statement transaction.