mssql-python ile işlem yönetimi

mssql-python sürücüsü, commit, rollback, autocommit yapılandırması ve işlem yalıtım düzeyleri dâhil olmak üzere tam işlem denetimini destekler.

İşlem temelleri

Bir işlem, veritabanı işlemlerini tek bir iş biriminde gruplar. İşlemler ACID özelliklerini takip eder:

  • Atomiklik: Tüm operasyonlar başarılı olur ya da hepsi başarısız olur.
  • Tutarlılık: Veritabanı geçerli durumda kalır.
  • İzolasyon: Eşzamanlı işlemler birbirine müdahale etmez.
  • Dayanıklılık: Kararlı değişiklikler sistem arızalarına dayanır.

Otomatik onay modu

Ayar, autocommit değişikliklerin otomatik olarak yapılıp yapılmadığını kontrol eder.

Otomatik commit devre dışı (varsayılan)

Varsayılan olarak, autocommit=False. Açıkça değişiklik yapmalısınız.

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()

Eğer kesinleştirmezseniz, bağlantı kapandığında sürücü değişiklikleri atar.

Otomatik kaydetme etkin

Ayarlarsanız autocommit=True, sürücü her bir ifadeyi hemen uygular:

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

Otomatik commit etkinleştirildiğinde, grup olarak birden fazla ifadeyi geri alamazsınız. Otomatik commit sadece kullanım alanınıza uygun olduğunda kullanın.

İşleme ve geri alma

Taahhüt etmek

Bekleyen değişiklikleri kalıcı hale getirmek için commit() çağırın:

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

Geriye Alma

Bekleyen değişiklikleri iptal etmek için rollback() çağırın:

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

İmleç düzeyinde onaylama ve geri alma

Kolaylık sağlamak için, imleçler üzerinde commit() ve rollback() çağrıları yapabilirsiniz:

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

İmleç seviyesindeki commit ve rollback, aynı bağlantıdaki tüm imleci etkiler, sadece çağırdığınız imleci değil.

Bağlam yöneticileri

Bağlantı bağlam yöneticisi, sorunsuz bir çıkışta işlemi onaylar; bir istisna oluşursa da geri alır. Bağlantı her zaman çıkışta kapanır. autocommit=True ayarlandığında, kesinleştirme ve geri alma çağrıları etkisiz olur.

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

Bir istisna gerçekleşirse, işlem geri alınır:

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

İşlem yalıtım düzeyleri

İzolasyon seviyeleri, işlemlerin eşzamanlı işlemlerle nasıl etkileşime girdiğini kontrol eder. İzolasyon seviyesini şu şekilde set_attr()ayarlayın:

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
)

Mevcut izolasyon seviyeleri

Sabit Açıklama
SQL_TXN_READ_UNCOMMITTED Diğer işlemlerdeki onaylanmamış değişiklikleri okuyabilir (kirli okumalara izin verilir)
SQL_TXN_READ_COMMITTED Yalnızca belirlenmiş verileri okur (SQL Server'da varsayılan)
SQL_TXN_REPEATABLE_READ Bir işlem içinde tutarlı okumaları garanti eder
SQL_TXN_SERIALIZABLE En yüksek izolasyon; İşlemler ardışık olarak çalışıyor gibi görünüyor

İzolasyon seviyesi seçin

Kullanım örneği Önerilen düzey
Genel OLTP iş yükleri READ_COMMITTED (varsayılan)
Tutarlı anlık görüntüler gerektiren raporlar REPEATABLE_READ ya da anlık görüntü
Doğruluk gerektiren finansal hesaplamalar SERIALIZABLE
Eskimiş verileri tolere edebilen, okuma ağırlıklı iş yükleri READ_UNCOMMITTED

Anlık görüntü yalıtımı

Snapshot izolasyonu için Transact-SQL (T-SQL) kullanın. Snapshot izolasyonu, tempdb içinde satır sürümü oluşturma kullanır; bu, yoğun yazma iş yükleri altında depolama gereksinimlerini artırabilir.

# 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()

İç içe işlemler ve kaydetme noktaları

SQL Server, bir işlem içinde kısmi geri alma için kaydetme noktalarını destekler.

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")

Kilitlenmeleri ele alma

Çıkmazlar, iki işlemin birbirinin kilitlerini beklemesiyle oluşur. SQL Server çıkmazları otomatik olarak tespit eder ve bir işlemi sonlandırır.

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")

En iyi uygulamalar

  • İşlemleri kısa tutarak kilit süresini ve çıkmaz potansiyelini en aza indirin.

  • Atomik olması gereken çoklu ifadeli işlemler için autocommit=False kullanın.

  • İstisnaları her zaman ele alın geri alma ile:

    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()
    
  • İşlemleri otomatik olarak yönetmek için bağlam yöneticileri kullanın. Bağlam yöneticisi, sorunsuz çıkışta işlemi onaylar ve bir istisna durumunda geri alır.

    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 exit
    
  • Tutarlılık gereksinimlerinize ve performans ihtiyaçlarınıza göre uygun izolasyon seviyelerini seçin.

  • Güncellemelerin kaybolmasını önlemek için okuma-değiştir-yazma kalıpları için kilitleme ipuçları kullanın. Aynı işlem içinde güncellenecek bir değeri okuduğunuzda, kilitleri erkenden edinmek ve tutarlı bir kilit sıralaması oluşturmak için SELECT üzerinde WITH (UPDLOCK, ROWLOCK) gibi ipuçlarını kullanın; bu, kilitlenme riskini azaltır.

    # 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})
    
  • Geçici hatalar (deadlock) için yeniden deneme mantığı uygulayın.

Örnek: Transfer fonları (atomik operasyon)

Bu örnek, eşzamanlı senaryolarda güncellemelerin kaybolmasını önlemek için kilit ipuçları ile atomik transfer mantığı gösterilir:

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

SELECT üzerindeki kilit ipuçları WITH (UPDLOCK, ROWLOCK) , kilidin erken alınmasını sağlar. Bu, başka bir işlemin aynı bakiyeyi eşzamanlı okumasını ve her iki işlemin eski bakiyeyi okuyup ayrı güncellemeler yapması ve sadece son güncellemenin devam ettiği kayıp bir güncelleme senaryosu yaratmasını engeller.

Geçici tablo kapsamı

Geçici tablolar (#tablename) oturum kapsamındadır, ancak oluşturulmaları geçerli işlemin bir parçasıdır. Geçici bir tablo oluşturursanız ve işlem geri çekilirse, geçici tablo kaldırılır:

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")

Geçici bir tabloyu veri işleminizden bağımsız tutmak için, oluşturduktan sonra commit yapın:

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

Otomatik commit gerektiren DDL ifadeleri

Bazı DDL ifadeleri, örneğin CREATE DATABASE, ALTER DATABASE, ve DROP DATABASE, bir işlem içinde çalışamaz. autocommit=True bunları çalıştırmadan önce ayarlayın:

conn.autocommit = True
cursor.execute("CREATE DATABASE TestDB")
conn.autocommit = False  # Return to transactional mode

Eğer CREATE DATABASE öğesini autocommit=False ile çalıştırırsanız, şu hatayı alırsınız: CREATE DATABASE statement not allowed within multi-statement transaction.