MySQLdb kullanılarak imleçler ne zaman kapatılmalı


87

Bir WSGI web uygulaması oluşturuyorum ve bir MySQL veritabanım var. İfadeleri yürütmek ve sonuçları almak için imleçler sağlayan MySQLdb kullanıyorum. İmleçleri almak ve kapatmak için standart uygulama nedir? Özellikle imleçlerim ne kadar süre dayanmalı? Her işlem için yeni bir imleç almalı mıyım?

Bağlantıyı kurmadan önce imleci kapatmanız gerektiğine inanıyorum. Her işlem için yeni imleçler almak zorunda kalmamak için ara taahhüt gerektirmeyen işlem kümelerini bulmanın önemli bir avantajı var mı? Yeni imleçler elde etmenin çok fazla ek yükü var mı, yoksa bu önemli bir şey değil mi?

Yanıtlar:


81

Standart uygulamanın ne olduğunu sormak yerine, bu genellikle belirsiz ve öznel olduğundan, rehberlik için modülün kendisine bakmayı deneyebilirsiniz. Genel olarak, withanahtar kelimeyi başka bir kullanıcının önerdiği gibi kullanmak harika bir fikirdir, ancak bu özel durumda size beklediğiniz işlevselliği tam olarak vermeyebilir.

Modülün 1.2.5 sürümünden itibaren MySQLdb.Connection, bağlam yöneticisi protokolünü aşağıdaki kodla ( github ) uygular :

def __enter__(self):
    if self.get_autocommit():
        self.query("BEGIN")
    return self.cursor()

def __exit__(self, exc, value, tb):
    if exc:
        self.rollback()
    else:
        self.commit()

Hakkında withhalihazırda var olan birkaç Soru-Cevap var veya Python'un "with" ifadesini Anlama'yı okuyabilirsiniz , ancak esasen olan şey bloğun __enter__başında çalıştırılır ve withbloktan __exit__çıkıldığında yürütülür with. Daha sonra bu nesneye başvurmayı düşünüyorsanız, with EXPR as VARtarafından döndürülen nesneyi __enter__bir ada bağlamak için isteğe bağlı sözdizimini kullanabilirsiniz . Dolayısıyla, yukarıdaki uygulama göz önüne alındığında, veritabanınızı sorgulamanın basit bir yolu:

connection = MySQLdb.connect(...)
with connection as cursor:            # connection.__enter__ executes at this line
    cursor.execute('select 1;')
    result = cursor.fetchall()        # connection.__exit__ executes after this line
print result                          # prints "((1L,),)"

Şimdi soru, withbloktan çıktıktan sonra bağlantının ve imlecin durumları nelerdir? __exit__Sadece aramalar yukarıda gösterilen yöntem self.rollback()veya self.commit()ve ne bu yöntemlerin çağırmak için devam close()yöntemi. İmlecin kendisinin __exit__tanımlanmış bir yöntemi yoktur ve tanımlanmış olması önemli değildir, çünkü withyalnızca bağlantıyı yönetmektedir. Bu nedenle, withbloktan çıktıktan sonra hem bağlantı hem de imleç açık kalır . Bu, yukarıdaki örneğe aşağıdaki kodu ekleyerek kolayca onaylanabilir:

try:
    cursor.execute('select 1;')
    print 'cursor is open;',
except MySQLdb.ProgrammingError:
    print 'cursor is closed;',
if connection.open:
    print 'connection is open'
else:
    print 'connection is closed'

Stdout'a yazdırılan "imleç açık; bağlantı açık" çıktısını görmelisiniz.

Bağlantıyı kurmadan önce imleci kapatmanız gerektiğine inanıyorum.

Neden? MySQL Cı API temelidir, MySQLdbherhangi bir imleç nesnesi uygulamaz, modül belgelerinde ima edilmiştir: "MySQL imleçler desteklemez, ancak imleçler kolayca taklit vardır." Aslında, MySQLdb.cursors.BaseCursorsınıf doğrudan objectteslim etme / geri alma ile ilgili olarak imleçlerden miras alır ve bu tür bir kısıtlama uygulamaz. Bir Oracle geliştiricisi şunu söylemişti :

cnx.commit () cur.close () 'dan önce bana en mantıklı geliyor. Belki şu kurala göre gidebilirsiniz: "Artık ihtiyacınız yoksa imleci kapatın." Böylece imleci kapatmadan önce commit (). Sonunda, Connector / Python için çok fazla fark yaratmaz, ancak veya diğer veritabanları için olabilir.

Bu konuda "standart uygulamaya" en yakın olacağınızı umuyorum.

Her işlem için yeni imleçler almak zorunda kalmamak için ara taahhüt gerektirmeyen işlem kümelerini bulmanın önemli bir avantajı var mı?

Bundan çok şüpheliyim ve bunu yapmaya çalışırken, ek insan hatası da ortaya çıkabilir. Bir kongreye karar vermek ve ona bağlı kalmak daha iyidir.

Yeni imleçler elde etmenin çok fazla ek yükü var mı, yoksa bu önemli bir şey değil mi?

Ek yük ihmal edilebilir düzeydedir ve veritabanı sunucusuna hiç dokunmaz; tamamen MySQLdb'nin uygulanması içindedir. Sen olabilir bakmak BaseCursor.__init__github size yeni imleç oluştururken neler olduğunu bilmek gerçekten merak eğer.

Daha önce tartıştığımız zamana geri dönersek with, belki şimdi MySQLdb.Connectionsınıfın __enter__ve __exit__yöntemlerin size neden her withblokta yepyeni bir imleç nesnesi verdiğini ve onu takip etmek veya bloğun sonunda kapatmakla uğraşmadığını anlayabilirsiniz . Oldukça hafif ve tamamen sizin rahatınız için var.

İmleç nesnesinin mikro yönetimini yapmak sizin için gerçekten bu kadar önemliyse , imleç nesnesinin tanımlanmış bir yöntemi olmadığı gerçeğini telafi etmek için contextlib.closing'i kullanabilirsiniz __exit__. Bu nedenle, bağlantı nesnesini bir withbloktan çıktıktan sonra kendini kapatmaya zorlamak için de kullanabilirsiniz . Bu, "my_curs kapalı; my_conn kapalı" çıktısını almalıdır:

from contextlib import closing
import MySQLdb

with closing(MySQLdb.connect(...)) as my_conn:
    with closing(my_conn.cursor()) as my_curs:
        my_curs.execute('select 1;')
        result = my_curs.fetchall()
try:
    my_curs.execute('select 1;')
    print 'my_curs is open;',
except MySQLdb.ProgrammingError:
    print 'my_curs is closed;',
if my_conn.open:
    print 'my_conn is open'
else:
    print 'my_conn is closed'

Not with closing(arg_obj)argüman nesne en aramayacağım __enter__ve __exit__yöntemler; o olacak sadece argüman nesnenin çağrı closesonunda yöntemini withbloğu. (Sadece bir sınıf tanımlamak, Bu eylemi görmek için Fooile __enter__, __exit__ve closeyöntemler basit içeren printifadeleri ve siz ne olacağını karşılaştırmak with Foo(): passbunu yaptığında olanlara with closing(Foo()): pass.) Bu iki önemli etkileri vardır:

İlk olarak, otomatik yürütme modu etkinleştirilmişse, MySQLdb , bloğun sonunda işlemi BEGINkullandığınızda with connectionve gerçekleştirdiğinizde veya geri aldığınızda sunucuda açık bir işlem yapar . Bunlar MySQLdb'nin varsayılan davranışlarıdır ve sizi MySQL'in her türlü DML ifadesini hemen işleme koyma şeklindeki varsayılan davranışından korumayı amaçlamaktadır. MySQLdb, bir bağlam yöneticisi kullandığınızda, bir işlem istediğinizi varsayar BEGINve sunucudaki otomatik tamamlama ayarını atlamak için açık olanı kullanır . Kullanmaya with connectionalışkınsanız, aslında yalnızca atlandığında otomatik tamamlamanın devre dışı bırakıldığını düşünebilirsiniz. Eklersen hoş olmayan bir sürprizle karşılaşabilirsinclosingkodunuza ve işlem bütünlüğünü kaybetmenize; değişiklikleri geri alamazsınız, eşzamanlılık hataları görmeye başlayabilirsiniz ve bunun nedeni hemen belli olmayabilir.

İkincisi, with closing(MySQLdb.connect(user, pass)) as VARbağlanan bağlantı nesnesini için VARaksine, with MySQLdb.connect(user, pass) as VARbağlar, yeni imleç nesnesi için VAR. İkinci durumda, bağlantı nesnesine doğrudan erişiminiz olmazdı! Bunun yerine, connectionorijinal bağlantıya proxy erişimi sağlayan imlecin özniteliğini kullanmanız gerekir . İmleç kapatıldığında, connectionniteliği olarak ayarlanır None. Bu, aşağıdakilerden biri olana kadar devam eden terk edilmiş bir bağlantıyla sonuçlanır:

  • İmlecin tüm referansları kaldırılır
  • İmleç kapsam dışına çıkıyor
  • Bağlantı zaman aşımına uğradı
  • Bağlantı, sunucu yönetim araçları aracılığıyla manuel olarak kapatılır

Bunu , aşağıdaki satırları tek tek çalıştırırken açık bağlantıları izleyerek (Workbench'te veya kullanarakSHOW PROCESSLIST ) test edebilirsiniz :

with MySQLdb.connect(...) as my_curs:
    pass
my_curs.close()
my_curs.connection          # None
my_curs.connection.close()  # throws AttributeError, but connection still open
del my_curs                 # connection will close here

15
Gönderiniz çok kapsamlıydı, ancak birkaç kez yeniden okuduktan sonra bile, imleçleri kapatma konusunda kendimi hala şaşkın buluyorum. Konuyla ilgili sayısız gönderiden yola çıkarak, ortak bir kafa karışıklığı noktası gibi görünüyor. Benim çıkardığım şey, imleçlerin .close () 'un çağrılmasını - hiçbir zaman - GEREKTİRMEMESİ. Öyleyse neden bir .close () yöntemi var?
SMGreenfield

6
Kısa cevap, özellikle MySQL göz önünde bulundurularak yazılmayan Python DB API'sinin bircursor.close() parçasıdır .
Hava

1
Del my_curs'dan sonra bağlantı neden kapanacak?
BAE

@ChengchengPei my_curs, connectionnesneye son başvuruyu tutar . Bu referans artık yok Bir kere, connectionnesne olmalıdır çöp toplanacak.
Hava

Bu harika bir cevap, teşekkürler. withVe MySQLdb.Connection'ler __enter__ve __exit__işlevlerinin mükemmel açıklaması . Tekrar teşekkürler @Air.
Eugene

33

'With' anahtar kelimesini kullanarak yeniden yazmak daha iyidir. 'İle', imleci (yönetilmeyen bir kaynak olduğu için önemlidir) otomatik olarak kapatmaya dikkat edecektir. Bunun faydası, istisna durumunda da imleci kapatmasıdır.

from contextlib import closing
import MySQLdb

''' At the beginning you open a DB connection. Particular moment when
  you open connection depends from your approach:
  - it can be inside the same function where you work with cursors
  - in the class constructor
  - etc
'''
db = MySQLdb.connect("host", "user", "pass", "database")
with closing(db.cursor()) as cur:
    cur.execute("somestuff")
    results = cur.fetchall()
    # do stuff with results

    cur.execute("insert operation")
    # call commit if you do INSERT, UPDATE or DELETE operations
    db.commit()

    cur.execute("someotherstuff")
    results2 = cur.fetchone()
    # do stuff with results2

# at some point when you decided that you do not need
# the open connection anymore you close it
db.close()

withFlask veya başka bir web çerçevesinde kullanmak istiyorsanız, bunun iyi bir seçenek olduğunu düşünmüyorum . Durum http://flask.pocoo.org/docs/patterns/sqlite3/#sqlite3böyleyse sorunlar çıkacaktır.
James King

@ james-king Flask ile çalışmadım, ancak sizin örneğinizde Flask db bağlantısını kendisi kapatacak. Aslında benim kodda ben biraz farklı yaklaflın ben kullanımını kullanmak ile yakın imleçler için with closing(self.db.cursor()) as cur: cur.execute("UPDATE table1 SET status = %s WHERE id = %s",(self.INTEGR_STATUS_PROCESSING, id)) self.db.commit()
Roman Podlinov

@RomanPodlinov Evet, imleçle kullanırsanız işler iyi olur.
James King

7

Not: Bu yanıt, MySQLdb'nin yerini alan ve MySQLdb'nin bakımı durdurulduğundan beri MySQLdb'nin en son sürümü olan PyMySQL içindir. Buradaki her şeyin eski MySQLdb için de doğru olduğuna inanıyorum , ancak henüz kontrol edilmedi.

Öncelikle bazı gerçekler:

  • Python'un withsözdizimi __enter__, withbloğun gövdesini çalıştırmadan önce bağlam yöneticisinin yöntemini ve __exit__daha sonra yöntemini çağırır .
  • Bağlantıların __enter__bir imleç oluşturma ve geri döndürmenin yanı sıra hiçbir şey yapmayan bir __exit__yöntemi ve (bir istisnanın atılıp atılmadığına bağlı olarak) taahhüt eden veya geri dönen bir yöntemi vardır. Bağlantıyı kapatmaz .
  • PyMySQL'deki imleçler tamamen Python'da uygulanan bir soyutlamadır; MySQL'de eşdeğer bir kavram yoktur. 1
  • İmleçler __enter__hiçbir şey yapmayan bir __exit__yönteme ve imleci "kapatan" bir yönteme sahiptir (bu sadece imlecin ana bağlantısına olan referansını boşa çıkarmak ve imleçte depolanan verileri atmak anlamına gelir).
  • İmleçler, onları oluşturan bağlantıya bir referans tutar, ancak bağlantılar oluşturdukları imleçlere bir referans tutmaz.
  • Bağlantıların __del__onları kapatan bir yöntemi vardır
  • Https://docs.python.org/3/reference/datamodel.html başına , CPython (varsayılan Python uygulaması) referans sayma kullanır ve referans sayısı sıfıra ulaştığında bir nesneyi otomatik olarak siler.

Bunları bir araya getirdiğimizde, bunun gibi saf bir kodun teoride sorunlu olduğunu görüyoruz :

# Problematic code, at least in theory!
import pymysql
with pymysql.connect() as cursor:
    cursor.execute('SELECT 1')

# ... happily carry on and do something unrelated

Sorun, bağlantıyı hiçbir şeyin kapatmamasıdır. Aslında, yukarıdaki kodu bir Python kabuğuna yapıştırır ve ardından SHOW FULL PROCESSLISTbir MySQL kabuğunda çalıştırırsanız , oluşturduğunuz boşta bağlantıyı görebilirsiniz. MySQL'in varsayılan bağlantı sayısı 151 olduğundan bu çok büyük değildir , bu bağlantıları açık tutan birçok işleminiz varsa teorik olarak sorunlarla karşılaşmaya başlayabilirsiniz.

Bununla birlikte, CPython'da, yukarıdaki örneğim gibi kodun muhtemelen bir sürü açık bağlantı bırakmanıza neden olmayacağını garanti eden bir tasarruf zarafeti vardır . Bu tasarruf yetkisi, cursorkapsam dışına çıkar çıkmaz (örneğin, oluşturulduğu işlev biter veya cursorkendisine atanan başka bir değer alır), referans sayısının sıfıra ulaşması ve bağlantının referans sayısını bırakarak silinmesine neden olmasıdır. sıfıra, bağlantının __del__yönteminin çağrılmasına neden olan hangi bağlantıyı zorla kapatır. Yukarıdaki kodu Python kabuğunuza zaten yapıştırdıysanız, şimdi bunu çalıştırarak simüle edebilirsiniz cursor = 'arbitrary value'; Bunu yaptığınız anda açtığınız bağlantı SHOW PROCESSLISTçıkıştan kaybolacaktır .

Bununla birlikte, buna güvenmek mantıksızdır ve teorik olarak CPython dışındaki Python uygulamalarında başarısız olabilir. Temizleyici, teorik olarak, açıkça .close()bağlantı (Python'un nesneyi yok etmesini beklemeden veritabanı üzerinde bir bağlantıyı serbest bırakmak) olacaktır. Bu daha sağlam kod şuna benzer:

import contextlib
import pymysql
with contextlib.closing(pymysql.connect()) as conn:
    with conn as cursor:
        cursor.execute('SELECT 1')

Bu çirkin, ancak Python'un (sınırlı sayıda mevcut) veritabanı bağlantılarınızı serbest bırakmak için nesnelerinizi yok etmesine güvenmiyor.

Kapanış unutmayın imleci zaten böyle açıkça bağlantı kapatılıyor eğer, tamamen anlamsızdır.

Son olarak, buradaki ikincil soruları cevaplamak için:

Yeni imleçler elde etmenin çok fazla ek yükü var mı, yoksa bu önemli bir şey değil mi?

Hayır, bir imlecin somutlaştırılması MySQL'e hiç çarpmaz ve temelde hiçbir şey yapmaz .

Her işlem için yeni imleçler almak zorunda kalmamak için ara taahhüt gerektirmeyen işlem kümelerini bulmanın önemli bir avantajı var mı?

Bu durumsaldır ve genel bir yanıt vermek zordur. As https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html koyar onu, "saniyede binlerce kez işlerse bir uygulama kudreti karşılaşma performans sorunları ve farklı performans sorunları varsa sadece 2-3 saatte bir taahhüt eder " . Her kayıt için bir performans ek yükü ödersiniz, ancak işlemleri daha uzun süre açık bırakarak, diğer bağlantıların kilitleri beklemek için zaman harcama olasılığını artırır, kilitlenme riskinizi artırır ve diğer bağlantılar tarafından gerçekleştirilen bazı aramaların maliyetini potansiyel olarak artırırsınız. .


1 MySQL yapar bir çağıran bir yapıya sahiptir: imleç ama sadece içinde saklanan prosedürler vardır; PyMySQL imleçlerinden tamamen farklıdırlar ve burada alakaları yoktur.


5

Tüm yürütmeleriniz için tek bir imleç kullanmaya çalışmak ve kodunuzun sonunda onu kapatmaktan daha iyi olacağınızı düşünüyorum. Birlikte çalışmak daha kolay ve aynı zamanda verimlilik faydaları da olabilir (benden alıntı yapmayın).

conn = MySQLdb.connect("host","user","pass","database")
cursor = conn.cursor()
cursor.execute("somestuff")
results = cursor.fetchall()
..do stuff with results
cursor.execute("someotherstuff")
results2 = cursor.fetchall()
..do stuff with results2
cursor.close()

Mesele şu ki, bir imlecin yürütmesinin sonuçlarını başka bir değişkende saklayabilir, böylece imlecinizi ikinci bir yürütme yapmak için serbest bırakabilirsiniz. Bu şekilde, yalnızca fetchone () kullanıyorsanız ve ilk sorgudaki tüm sonuçları yinelemeden önce ikinci bir imleç yürütme yapmanız gerekiyorsa sorunlarla karşılaşırsınız.

Aksi takdirde, imleçlerinizi onlardan tüm verileri almayı bitirir bitirmez kapatın derim. Bu şekilde, kodunuzun ilerleyen bölümlerinde yarım kalmış işleri halletme konusunda endişelenmenize gerek kalmaz.


Teşekkürler - Bir güncelleme / ekleme işlemi yapmak için imleci kapatmanız gerektiğini düşünürsek, bunu güncellemeler / eklemeler için yapmanın kolay bir yolu, her arka plan programı için bir imleç almak, uygulamak için imleci kapatmak ve hemen yeni bir imleç almak olacaktır. bu yüzden bir dahaki sefere hazırsınız. Bu mantıklı geliyor mu?
jmilloy

1
Hey, sorun değil. İmleçlerinizi kapatarak güncellemeyi / eklemeyi gerçekten bilmiyordum, ancak çevrimiçi hızlı bir arama şunu gösteriyor: conn = MySQLdb.connect (arguments_go_here) cursor = MySQLdb.cursor () cursor.execute (mysql_insert_statement_here) try: conn. commit () dışında: conn.rollback () # hata oluşursa yapılan değişiklikleri geri al. Bu şekilde, veritabanının kendisi değişiklikleri gerçekleştirir ve imleçlerin kendileri için endişelenmenize gerek kalmaz. O zaman her zaman sadece 1 imleci açık tutabilirsiniz. Buraya bir göz atın: tutorialspoint.com/python/python_database_access.htm
nct25

Evet, eğer bu işe yararsa, o zaman yanılıyorum ve bağlantıyı sağlamak için imleci kapatmam gerektiğini düşünmeme neden olan başka bir sebep vardı.
jmilloy

Evet, bilmiyorum, gönderdiğim bağlantı bana bunun işe yaradığını düşündürüyor. Sanırım biraz daha fazla araştırma bunun kesinlikle işe yarayıp yaramadığını söyleyecektir, ama bence muhtemelen onunla devam edebilirsiniz. Umarım sana yardımcı olmuşumdur!
nct25

imleç iş parçacığı için güvenli değildir, aynı imleci birçok farklı iş parçacığı arasında kullanırsanız ve bunların tümü db'den sorguluyorsa, fetchall () rastgele veri verecektir.
ospider

-6

Bunu php ve mysql gibi yapmanızı öneririm. İlk veriyi yazdırmadan önce i kodunuzun başından başlayın. Dolayısıyla, bir bağlantı hatası alırsanız, bir 50x(Dahili hatanın ne olduğunu hatırlamayın) hata mesajı görüntüleyebilirsiniz. Ve tüm oturum boyunca açık tutun ve artık ihtiyacınız olmayacağını bildiğiniz zaman kapatın.


MySQLdb'de, bir bağlantı ile bir imleç arasında bir fark vardır. İstek başına bir kez bağlanıyorum (şimdilik) ve bağlantı hatalarını erken tespit edebiliyorum. Peki ya imleçler?
jmilloy

IMHO bu doğru bir tavsiye değil. Duruma bağlı. Kodunuz bağlantıyı uzun süre koruyacaksa (örneğin, DB'den bir miktar veri alır ve sonra 1-5-10 dakika boyunca sunucuda bir şeyler yapar ve bağlantıyı sürdürürse) ve çok iş parçacıklı bir uygulama ise, kısa sürede bir sorun yaratacaktır (siz izin verilen maksimum bağlantıyı aşacak).
Roman Podlinov
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.