Daripada menanyakan apa itu praktik standar, karena sering kali tidak jelas dan subjektif, Anda dapat mencoba melihat modul itu sendiri sebagai panduan. Secara umum, menggunakan with
kata kunci seperti yang disarankan pengguna lain adalah ide bagus, tetapi dalam keadaan khusus ini mungkin tidak memberikan fungsionalitas yang Anda harapkan.
Pada versi 1.2.5 modul, MySQLdb.Connection
mengimplementasikan protokol manajer konteks dengan kode berikut ( github ):
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()
Ada beberapa Tanya Jawab yang with
sudah ada, atau Anda dapat membaca Memahami pernyataan "dengan" Python , tetapi pada dasarnya apa yang terjadi adalah yang __enter__
mengeksekusi di awal with
blok, dan __exit__
mengeksekusi setelah meninggalkan with
blok. Anda dapat menggunakan sintaks opsional with EXPR as VAR
untuk mengikat objek yang dikembalikan oleh __enter__
nama jika Anda bermaksud untuk mereferensikan objek itu nanti. Jadi, dengan penerapan di atas, berikut adalah cara sederhana untuk membuat kueri database Anda:
connection = MySQLdb.connect(...)
with connection as cursor:
cursor.execute('select 1;')
result = cursor.fetchall()
print result
Pertanyaannya sekarang adalah, apa status koneksi dan kursor setelah keluar dari with
blok? The __exit__
Metode yang ditunjukkan di atas panggilan hanya self.rollback()
atau self.commit()
, dan tak satu pun dari metode tersebut pergi untuk memanggil close()
metode. Kursor itu sendiri tidak memiliki __exit__
metode yang ditentukan - dan tidak masalah jika demikian, karena with
hanya mengelola koneksi. Oleh karena itu, koneksi dan kursor tetap terbuka setelah keluar dari with
blokir. Ini mudah dikonfirmasi dengan menambahkan kode berikut ke contoh di atas:
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'
Anda akan melihat output "kursor terbuka; koneksi terbuka" dicetak ke stdout.
Saya yakin Anda perlu menutup kursor sebelum melakukan koneksi.
Mengapa? The MySQL C API , yang merupakan dasar untuk MySQLdb
, tidak mengimplementasikan objek kursor, seperti yang tersirat dalam dokumentasi modul: "MySQL tidak mendukung kursor, namun kursor yang mudah ditiru." Memang, MySQLdb.cursors.BaseCursor
kelas tersebut mewarisi langsung dari object
dan tidak memberlakukan pembatasan seperti itu pada kursor terkait dengan commit / rollback. Pengembang Oracle mengatakan ini :
cnx.commit () sebelum cur.close () terdengar paling logis bagi saya. Mungkin Anda dapat mengikuti aturan: "Tutup kursor jika Anda tidak membutuhkannya lagi." Jadi komit () sebelum menutup kursor. Pada akhirnya, untuk Connector / Python, itu tidak membuat banyak perbedaan, tetapi mungkin untuk database lain.
Saya berharap itu sedekat Anda dengan "praktik standar" tentang subjek ini.
Adakah keuntungan yang signifikan untuk menemukan rangkaian transaksi yang tidak memerlukan komitmen perantara sehingga Anda tidak perlu mendapatkan kursor baru untuk setiap transaksi?
Saya sangat meragukannya, dan saat mencoba melakukannya, Anda mungkin menyebabkan kesalahan manusia tambahan. Lebih baik memutuskan konvensi dan mematuhinya.
Apakah ada banyak biaya tambahan untuk mendapatkan kursor baru, atau itu bukan masalah besar?
Overhead dapat diabaikan, dan tidak menyentuh server database sama sekali; itu sepenuhnya dalam implementasi MySQLdb. Anda dapat melihat di BaseCursor.__init__
github jika Anda benar-benar ingin tahu apa yang terjadi saat Anda membuat kursor baru.
Kembali ke awal ketika kita berdiskusi with
, mungkin sekarang Anda dapat memahami mengapa MySQLdb.Connection
kelas __enter__
dan __exit__
metode memberi Anda objek kursor baru di setiap with
blok dan tidak repot-repot melacak atau menutupnya di akhir blok. Ini cukup ringan dan hanya ada untuk kenyamanan Anda.
Jika benar-benar penting bagi Anda untuk mengatur mikro objek kursor, Anda dapat menggunakan contextlib.closing untuk menggantikan fakta bahwa objek kursor tidak memiliki __exit__
metode yang ditentukan . Dalam hal ini, Anda juga dapat menggunakannya untuk memaksa objek koneksi menutup sendiri saat keluar dari with
blok. Ini harus menghasilkan "my_curs ditutup; my_conn ditutup":
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'
Perhatikan bahwa with closing(arg_obj)
tidak akan memanggil objek argumen __enter__
dan __exit__
metode; itu hanya akan memanggil metode objek argumen close
di akhir with
blok. (Untuk melihat ini beraksi, cukup tentukan kelas Foo
dengan __enter__
,, __exit__
dan close
metode yang berisi print
pernyataan sederhana , dan bandingkan apa yang terjadi ketika Anda melakukan with Foo(): pass
apa yang terjadi ketika Anda melakukannya with closing(Foo()): pass
.) Ini memiliki dua implikasi signifikan:
Pertama, jika mode autocommit diaktifkan, MySQLdb akan BEGIN
melakukan transaksi eksplisit di server saat Anda menggunakan with connection
dan melakukan atau mengembalikan transaksi di akhir blok. Ini adalah perilaku default MySQLdb, yang dimaksudkan untuk melindungi Anda dari perilaku default MySQL yang segera melakukan setiap dan semua pernyataan DML. MySQLdb mengasumsikan bahwa ketika Anda menggunakan manajer konteks, Anda menginginkan transaksi, dan menggunakan eksplisit BEGIN
untuk melewati pengaturan autocommit di server. Jika Anda terbiasa menggunakan with connection
, Anda mungkin mengira autocommit dinonaktifkan padahal sebenarnya hanya dilewati. Anda mungkin mendapatkan kejutan yang tidak menyenangkan jika menambahkanclosing
ke kode Anda dan kehilangan integritas transaksional; Anda tidak akan dapat membatalkan perubahan, Anda mungkin mulai melihat bug konkurensi dan alasannya mungkin tidak langsung diketahui.
Kedua, with closing(MySQLdb.connect(user, pass)) as VAR
mengikat objek koneksi ke VAR
, berbeda dengan with MySQLdb.connect(user, pass) as VAR
, yang mengikat objek kursor baru ke VAR
. Dalam kasus terakhir, Anda tidak akan memiliki akses langsung ke objek koneksi! Sebagai gantinya, Anda harus menggunakan connection
atribut kursor , yang menyediakan akses proxy ke koneksi asli. Saat kursor ditutup, connection
atributnya disetel ke None
. Ini menghasilkan koneksi yang ditinggalkan yang akan bertahan sampai salah satu hal berikut terjadi:
- Semua referensi ke kursor dihapus
- Kursor keluar dari ruang lingkup
- Waktu koneksi habis
- Koneksi ditutup secara manual melalui alat administrasi server
Anda dapat mengujinya dengan memantau koneksi terbuka (di Workbench atau dengan menggunakanSHOW PROCESSLIST
) saat menjalankan baris berikut satu per satu:
with MySQLdb.connect(...) as my_curs:
pass
my_curs.close()
my_curs.connection
my_curs.connection.close()
del my_curs
cursor.close()
adalah bagian dari Python DB API , yang tidak ditulis secara khusus dengan MySQL.my_curs
memegang referensi terakhir keconnection
objek tersebut. Setelah referensi tersebut tidak ada lagi,connection
objek tersebut harus dikumpulkan sampahnya.with
danMySQLdb.Connection
's__enter__
dan__exit__
fungsi. Sekali lagi, terima kasih @Air.Lebih baik menulis ulang menggunakan kata kunci 'dengan'. 'Dengan' akan menangani penutupan kursor (penting karena sumber daya tidak terkelola) secara otomatis. Manfaatnya adalah akan menutup kursor jika ada pengecualian juga.
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()
sumber
with
ini bukan pilihan yang baik jika Anda ingin menggunakannya di Flask atau kerangka web lain. Jika situasinya demikianhttp://flask.pocoo.org/docs/patterns/sqlite3/#sqlite3
maka akan ada masalah.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()
Catatan: jawaban ini untuk PyMySQL , yang merupakan pengganti MySQLdb dan secara efektif merupakan versi terbaru MySQLdb karena MySQLdb tidak lagi dipertahankan. Saya percaya semua yang ada di sini juga berlaku untuk MySQLdb lama, tetapi belum diperiksa.
Pertama-tama, beberapa fakta:
with
Sintaks Python memanggil metode pengelola konteks__enter__
sebelum mengeksekusi badanwith
blok, dan__exit__
metode sesudahnya.__enter__
metode yang tidak melakukan apa pun selain membuat dan mengembalikan kursor, dan__exit__
metode yang melakukan atau memutar balik (tergantung pada apakah pengecualian dilemparkan). Itu tidak menutup koneksi.__enter__
metode yang tidak melakukan apa pun dan__exit__
metode yang "menutup" kursor (yang berarti membatalkan referensi kursor ke koneksi induknya dan membuang semua data yang disimpan di kursor).__del__
metode untuk menutupnyaMenyatukan hal-hal ini, kami melihat bahwa kode naif seperti ini secara teori bermasalah:
# Problematic code, at least in theory! import pymysql with pymysql.connect() as cursor: cursor.execute('SELECT 1') # ... happily carry on and do something unrelated
Masalahnya adalah tidak ada yang menutup koneksi. Memang, jika Anda menempelkan kode di atas ke shell Python dan kemudian menjalankannya
SHOW FULL PROCESSLIST
di shell MySQL, Anda akan dapat melihat koneksi idle yang Anda buat. Karena jumlah koneksi default MySQL adalah 151 , yang tidak besar , Anda secara teoritis dapat mulai mengalami masalah jika Anda memiliki banyak proses yang menjaga koneksi ini tetap terbuka.Namun, di CPython, ada anugrah yang memastikan bahwa kode seperti contoh saya di atas mungkin tidak akan menyebabkan Anda meninggalkan banyak koneksi terbuka. Yang menyelamatkan adalah bahwa segera setelah
cursor
keluar dari ruang lingkup (misalnya fungsi di mana itu dibuat selesai, ataucursor
mendapatkan nilai lain yang ditetapkan untuk itu), jumlah referensinya mencapai nol, yang menyebabkannya dihapus, menghilangkan jumlah referensi koneksi ke nol, menyebabkan metode koneksi__del__
dipanggil yang memaksa menutup koneksi. Jika Anda sudah menempelkan kode di atas ke dalam shell Python Anda, maka Anda sekarang dapat mensimulasikannya dengan menjalankancursor = 'arbitrary value'
; segera setelah Anda melakukan ini, koneksi yang Anda buka akan hilang dariSHOW PROCESSLIST
output.Namun, mengandalkan ini tidak elegan, dan secara teoritis mungkin gagal dalam implementasi Python selain CPython. Cleaner, dalam teori, akan secara eksplisit
.close()
menghubungkan (untuk membebaskan koneksi pada database tanpa menunggu Python menghancurkan objek). Kode yang lebih kuat ini terlihat seperti ini:import contextlib import pymysql with contextlib.closing(pymysql.connect()) as conn: with conn as cursor: cursor.execute('SELECT 1')
Ini jelek, tetapi tidak bergantung pada Python yang merusak objek Anda untuk membebaskan (jumlah terbatas) koneksi database Anda.
Perhatikan bahwa menutup kursor , jika Anda sudah menutup koneksi secara eksplisit seperti ini, sama sekali tidak ada gunanya.
Terakhir, untuk menjawab pertanyaan sekunder di sini:
Tidak, membuat kursor tidak mengenai MySQL sama sekali dan pada dasarnya tidak melakukan apa-apa .
Ini situasional dan sulit untuk diberikan jawaban umum. Seperti yang dikatakan https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html , "aplikasi mungkin mengalami masalah kinerja jika melakukan ribuan kali per detik, dan masalah kinerja yang berbeda jika itu hanya dilakukan setiap 2-3 jam " . Anda membayar overhead kinerja untuk setiap komit, tetapi dengan membiarkan transaksi terbuka lebih lama, Anda meningkatkan kemungkinan koneksi lain harus menghabiskan waktu menunggu kunci, meningkatkan risiko kebuntuan, dan berpotensi meningkatkan biaya beberapa pencarian yang dilakukan oleh koneksi lain .
1 MySQL memang memiliki konstruksi yang disebut kursor tetapi mereka hanya ada di dalam prosedur tersimpan; mereka sangat berbeda dengan kursor PyMySQL dan tidak relevan di sini.
sumber
Saya pikir Anda akan lebih baik mencoba menggunakan satu kursor untuk semua eksekusi Anda, dan menutupnya di akhir kode Anda. Lebih mudah untuk bekerja dengannya, dan mungkin juga memiliki manfaat efisiensi (jangan mengutip saya untuk yang itu).
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()
Intinya adalah Anda dapat menyimpan hasil eksekusi kursor di variabel lain, sehingga Anda dapat membebaskan kursor untuk melakukan eksekusi kedua. Anda mengalami masalah dengan cara ini hanya jika Anda menggunakan fetchone (), dan perlu melakukan eksekusi kursor kedua sebelum Anda mengulang semua hasil dari kueri pertama.
Jika tidak, saya akan mengatakan tutup kursor Anda segera setelah Anda selesai mendapatkan semua data dari mereka. Dengan begitu, Anda tidak perlu khawatir tentang menyelesaikan masalah nanti dalam kode Anda.
sumber
Saya menyarankan untuk melakukannya seperti php dan mysql. Mulai i di awal kode Anda sebelum mencetak data pertama. Jadi jika Anda mendapatkan kesalahan koneksi, Anda dapat menampilkan
50x
pesan kesalahan (Tidak ingat apa itu kesalahan internal). Dan tetap buka untuk seluruh sesi dan tutup saat Anda tahu Anda tidak akan membutuhkannya lagi.sumber