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 withkata 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.Connectionmengimplementasikan 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 withsudah ada, atau Anda dapat membaca Memahami pernyataan "dengan" Python , tetapi pada dasarnya apa yang terjadi adalah yang __enter__mengeksekusi di awal withblok, dan __exit__mengeksekusi setelah meninggalkan withblok. Anda dapat menggunakan sintaks opsional with EXPR as VARuntuk 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 withblok? 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 withhanya mengelola koneksi. Oleh karena itu, koneksi dan kursor tetap terbuka setelah keluar dari withblokir. 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.BaseCursorkelas tersebut mewarisi langsung dari objectdan 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.Connectionkelas __enter__dan __exit__metode memberi Anda objek kursor baru di setiap withblok 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 withblok. 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 closedi akhir withblok. (Untuk melihat ini beraksi, cukup tentukan kelas Foodengan __enter__,, __exit__dan closemetode yang berisi printpernyataan sederhana , dan bandingkan apa yang terjadi ketika Anda melakukan with Foo(): passapa yang terjadi ketika Anda melakukannya with closing(Foo()): pass.) Ini memiliki dua implikasi signifikan:
Pertama, jika mode autocommit diaktifkan, MySQLdb akan BEGINmelakukan transaksi eksplisit di server saat Anda menggunakan with connectiondan 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 BEGINuntuk 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 menambahkanclosingke 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 VARmengikat 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 connectionatribut kursor , yang menyediakan akses proxy ke koneksi asli. Saat kursor ditutup, connectionatributnya 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