Thay vì hỏi thực hành tiêu chuẩn là gì, vì điều đó thường không rõ ràng và chủ quan, bạn có thể thử tìm đến chính mô-đun để được hướng dẫn. Nói chung, sử dụng with
từ khóa như một người dùng khác đề xuất là một ý tưởng tuyệt vời, nhưng trong trường hợp cụ thể này, nó có thể không cung cấp cho bạn đầy đủ chức năng như mong đợi.
Kể từ phiên bản 1.2.5 của mô-đun, MySQLdb.Connection
triển khai giao thức trình quản lý ngữ cảnh với mã sau ( 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()
Có một số câu hỏi và giải đáp hiện có về with
đã có, hoặc bạn có thể đọc Hiểu câu lệnh "with" của Python , nhưng về cơ bản những gì xảy ra là __enter__
thực thi ở đầu with
khối và __exit__
thực thi khi rời khỏi with
khối. Bạn có thể sử dụng cú pháp tùy chọn with EXPR as VAR
để liên kết đối tượng được trả về __enter__
với một tên nếu bạn định tham chiếu đến đối tượng đó sau này. Vì vậy, với cách triển khai ở trên, đây là một cách đơn giản để truy vấn cơ sở dữ liệu của bạn:
connection = MySQLdb.connect(...)
with connection as cursor:
cursor.execute('select 1;')
result = cursor.fetchall()
print result
Câu hỏi bây giờ là trạng thái của kết nối và con trỏ sau khi thoát ra khỏi with
khối là gì? Các __exit__
phương pháp thể hiện cuộc gọi chỉ trên self.rollback()
hoặc self.commit()
, và không ai trong số những phương pháp tiếp tục gọi close()
phương pháp. Bản thân con trỏ không có __exit__
phương thức nào được xác định - và sẽ không thành vấn đề nếu nó có làm như vậy, vì with
chỉ quản lý kết nối. Do đó, cả kết nối và con trỏ vẫn mở sau khi thoát khỏi with
khối. Điều này dễ dàng được xác nhận bằng cách thêm mã sau vào ví dụ trên:
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'
Bạn sẽ thấy đầu ra "con trỏ đang mở; kết nối đang mở" được in ra stdout.
Tôi tin rằng bạn cần phải đóng con trỏ trước khi thực hiện kết nối.
Tại sao? Các MySQL C API , đó là cơ sở cho MySQLdb
, không thực hiện bất kỳ đối tượng con trỏ, như ngụ ý trong tài liệu mô-đun: "MySQL không hỗ trợ con trỏ, tuy nhiên, con trỏ có thể dễ dàng mô phỏng." Thật vậy, MySQLdb.cursors.BaseCursor
lớp kế thừa trực tiếp từ object
và không áp đặt hạn chế như vậy đối với các con trỏ liên quan đến cam kết / khôi phục. Một nhà phát triển Oracle đã nói điều này :
cnx.commit () trước cur.close () nghe hợp lý nhất với tôi. Có thể bạn có thể thực hiện theo quy tắc: "Đóng con trỏ nếu bạn không cần nó nữa." Do đó, commit () trước khi đóng con trỏ. Cuối cùng, đối với Connector / Python, nó không tạo ra nhiều khác biệt, nhưng hoặc các cơ sở dữ liệu khác thì có thể.
Tôi hy vọng điều đó gần như là bạn sẽ đạt được "thực hành tiêu chuẩn" về chủ đề này.
Có lợi thế đáng kể nào khi tìm các tập hợp giao dịch không yêu cầu cam kết trung gian để bạn không phải nhận con trỏ mới cho mỗi giao dịch không?
Tôi rất nghi ngờ điều đó, và khi cố gắng làm như vậy, bạn có thể mắc thêm lỗi của con người. Tốt hơn nên quyết định một quy ước và gắn bó với nó.
Có rất nhiều chi phí để có được con trỏ mới hay đó không phải là vấn đề lớn?
Chi phí là không đáng kể và hoàn toàn không chạm vào máy chủ cơ sở dữ liệu; nó hoàn toàn nằm trong việc triển khai MySQLdb. Bạn có thể xem BaseCursor.__init__
trên github nếu thực sự tò mò muốn biết điều gì đang xảy ra khi bạn tạo một con trỏ mới.
Quay trở lại trước đó khi chúng ta thảo luận with
, có lẽ bây giờ bạn có thể hiểu tại sao MySQLdb.Connection
lớp __enter__
và __exit__
các phương thức cung cấp cho bạn một đối tượng con trỏ hoàn toàn mới trong mỗi with
khối và không bận tâm theo dõi nó hoặc đóng nó ở cuối khối. Nó khá nhẹ và tồn tại hoàn toàn để thuận tiện cho bạn.
Nếu việc quản lý vi mô đối tượng con trỏ thực sự quan trọng đối với bạn, bạn có thể sử dụng contextlib.closing để bù đắp cho thực tế là đối tượng con trỏ không có __exit__
phương thức xác định . Đối với vấn đề đó, bạn cũng có thể sử dụng nó để buộc đối tượng kết nối tự đóng khi thoát khỏi một with
khối. Điều này sẽ xuất ra "my_curs is close; my_conn is close":
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'
Lưu ý rằng with closing(arg_obj)
sẽ không gọi đối tượng __enter__
và __exit__
phương thức của đối số ; nó sẽ chỉ gọi close
phương thức của đối tượng đối số ở cuối with
khối. (Để xem điều này trong thực tế, chỉ cần xác định một lớp Foo
với __enter__
, __exit__
và close
các phương thức chứa các print
câu lệnh đơn giản và so sánh những gì xảy ra khi bạn làm with Foo(): pass
với những gì sẽ xảy ra khi bạn làm with closing(Foo()): pass
.)
Đầu tiên, nếu chế độ gửi tự động được bật, MySQLdb sẽ BEGIN
là một giao dịch rõ ràng trên máy chủ khi bạn sử dụng with connection
và cam kết hoặc khôi phục giao dịch ở cuối khối. Đây là các hành vi mặc định của MySQLdb, nhằm bảo vệ bạn khỏi hành vi mặc định của MySQL là thực hiện ngay lập tức bất kỳ và tất cả các câu lệnh DML. MySQLdb giả định rằng khi bạn sử dụng trình quản lý ngữ cảnh, bạn muốn một giao dịch và sử dụng rõ ràng BEGIN
để bỏ qua cài đặt tự động gửi trên máy chủ. Nếu bạn đã quen sử dụng with connection
, bạn có thể nghĩ rằng tính năng tự động gửi đã bị vô hiệu hóa trong khi thực sự nó chỉ bị bỏ qua. Bạn có thể nhận được một bất ngờ khó chịu nếu bạn thêmclosing
mã của bạn và mất tính toàn vẹn trong giao dịch; bạn sẽ không thể khôi phục các thay đổi, bạn có thể bắt đầu thấy các lỗi đồng thời và có thể không rõ ràng ngay lập tức tại sao.
Thứ hai, with closing(MySQLdb.connect(user, pass)) as VAR
liên kết với các đối tượng kết nối tới VAR
, trái ngược với with MySQLdb.connect(user, pass) as VAR
, mà liên kết với một đối tượng mới của con trỏ tới VAR
. Trong trường hợp sau, bạn sẽ không có quyền truy cập trực tiếp vào đối tượng kết nối! Thay vào đó, bạn sẽ phải sử dụng connection
thuộc tính con trỏ, thuộc tính này cung cấp quyền truy cập proxy vào kết nối ban đầu. Khi đóng con trỏ, connection
thuộc tính của nó được đặt thành None
. Điều này dẫn đến một kết nối bị bỏ rơi sẽ tồn tại cho đến khi một trong những điều sau xảy ra:
- Tất cả các tham chiếu đến con trỏ đều bị xóa
- Con trỏ đi ra ngoài phạm vi
- Hết thời gian kết nối
- Kết nối được đóng theo cách thủ công thông qua các công cụ quản trị máy chủ
Bạn có thể kiểm tra điều này bằng cách theo dõi các kết nối đang mở (trong Workbench hoặc bằng cách sử dụngSHOW PROCESSLIST
) trong khi thực hiện từng dòng sau:
with MySQLdb.connect(...) as my_curs:
pass
my_curs.close()
my_curs.connection
my_curs.connection.close()
del my_curs