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 kiếm 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 mà bạn 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ố Hỏi và Đáp hiện tại về with
rồi, 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 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ề bởi __enter__
đến 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: # connection.__enter__ executes at this line
cursor.execute('select 1;')
result = cursor.fetchall() # connection.__exit__ executes after this line
print result # prints "((1L,),)"
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 khỏi with
khối? __exit__
phương thức hiển thị ở trên chỉ gọi self.rollback()
hoặc self.commit()
và không có phương thức nào trong số đó 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 được xác định - và sẽ không thành vấn đề nếu có, 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 đóng con trỏ trước khi thực hiện kết nối.
Tại sao? MySQL C API
, là cơ sở cho MySQLdb
, không triển khai bất kỳ đối tượng con trỏ nào, như được ngụ ý trong tài liệu mô-đun: "MySQL không hỗ trợ con trỏ; tuy nhiên, con trỏ dễ dàng được 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 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 có vẻ hợp lý nhất đối 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, forConnector / 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 rằng bạn sắp đạ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 do con người gây ra. Tốt hơn bạ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í không đáng kể và hoàn toàn không liên quan đến 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 bạn 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 lại lúc trước khi chúng ta thảo luận về 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
chặn 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__
được xác định phương pháp. Đố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 with
khối. Điều này sẽ xuất ra "my_curs đã đóng; my_conn đã đóng":
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 __enter__
của đối tượng và __exit__
các phương pháp; nó sẽ chỉ gọi close
của đối tượng đối số ở cuối with
khối. (Để xem điều này hoạt động, 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 print
đơn giản và so sánh những gì sẽ xảy ra khi bạn thực hiện with Foo(): pass
điều gì sẽ xảy ra khi bạn thực hiện with closing(Foo()): pass
.) Điều này có hai ý nghĩa quan trọng:
Đầu tiên, nếu chế độ tự động gửi được bật, MySQLdb sẽ BEGIN
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 BEGIN
rõ ràng để 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 autocommit 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êm close
mã của bạn và mất tính toàn vẹn của 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 đối tượng kết nối thành VAR
, ngược lại với with MySQLdb.connect(user, pass) as VAR
, liên kết với một đối tượng con trỏ mới thành 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
của 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 con trỏ được đóng, kết nối connection
thuộc tính đượ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 khỏi phạm vi
- Kết nối hết thời gian chờ
- 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 sử dụng SHOW 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 # None
my_curs.connection.close() # throws AttributeError, but connection still open
del my_curs # connection will close here