不要问什么是标准做法,因为这通常是不清楚和主观的,您可以尝试从模块本身寻求指导。一般来说,按照其他用户的建议使用 with 关键字是个好主意,但在这种特定情况下,它可能无法提供您所期望的功能。
从模块的 1.2.5 版本开始,MySQLdb.Connection 使用以下代码 (github) 实现 context manager protocol:
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()
已经有几个关于with 的现有问答,或者您可以阅读Understanding Python's "with" statement,但基本上发生的情况是__enter__ 在with 块的开头执行,而__exit__ 在离开块时执行with 块。如果您打算稍后引用该对象,您可以使用可选语法with EXPR as VAR 将__enter__ 返回的对象绑定到一个名称。因此,鉴于上述实现,这里有一个查询数据库的简单方法:
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,),)"
现在的问题是,退出with块后,连接和光标的状态是什么?上面显示的__exit__ 方法仅调用self.rollback() 或self.commit(),并且这些方法都不会继续调用close() 方法。游标本身没有定义__exit__ 方法——如果定义了也没关系,因为with 只是管理连接。因此,退出with 块后,连接和游标都保持打开状态。通过在上面的示例中添加以下代码可以很容易地确认这一点:
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'
您应该会看到输出“光标已打开;连接已打开”打印到标准输出。
我认为您需要在提交连接之前关闭光标。
为什么? MySQL C API 是 MySQLdb 的基础,它没有实现任何游标对象,正如模块文档中所暗示的那样:"MySQL does not support cursors; however, cursors are easily emulated." 实际上,MySQLdb.cursors.BaseCursor 类直接继承自 object,并且对关于提交/回滚的游标。甲骨文开发者had this to say:
cur.close() 之前的 cnx.commit() 对我来说听起来最合乎逻辑。可能是你
可以遵循规则:“如果不再需要,请关闭光标。”
因此在关闭游标之前提交()。最后,对于
连接器/Python,没有太大区别,但或其他
可能的数据库。
我希望这与您在此主题上的“标准实践”一样接近。
查找不需要中间提交的事务集是否有任何显着优势,这样您就不必为每个事务获取新游标?
我非常怀疑这一点,并且在尝试这样做时,您可能会引入额外的人为错误。最好决定一个约定并坚持下去。
获取新游标是否有很多开销,还是没什么大不了的?
开销可以忽略不计,根本不涉及数据库服务器;它完全在 MySQLdb 的实现中。如果您真的很想知道创建新光标时发生了什么,您可以look at BaseCursor.__init__ on github。
回到之前我们讨论with 时,也许现在你可以理解为什么MySQLdb.Connection 类__enter__ 和__exit__ 方法在每个with 块中给你一个全新的游标对象并且不要'不必费心跟踪它或在块的末尾关闭它。它相当轻巧,纯粹是为了您的方便而存在。
如果对游标对象的微观管理真的很重要,你可以使用contextlib.closing 来弥补游标对象没有定义__exit__ 方法的事实。就此而言,您还可以使用它来强制连接对象在退出 with 块时自行关闭。这应该输出“my_curs is closed; my_conn is closed”:
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'
注意with closing(arg_obj)不会调用参数对象的__enter__和__exit__方法;它会仅在with 块的末尾调用参数对象的close 方法。 (要查看实际情况,只需使用包含简单print 语句的__enter__、__exit__ 和close 方法定义一个类Foo,并将执行with Foo(): pass 时发生的情况与您执行with Foo(): pass 时发生的情况进行比较做with closing(Foo()): pass。)这有两个重要的含义:
首先,如果启用了自动提交模式,当您使用with connection 并在块末尾提交或回滚事务时,MySQLdb 将在服务器上BEGIN 显式事务。这些是 MySQLdb 的默认行为,旨在保护您免受 MySQL 立即提交任何和所有 DML 语句的默认行为。 MySQLdb 假设当您使用上下文管理器时,您需要一个事务,并使用显式 BEGIN 绕过服务器上的自动提交设置。如果您习惯使用with connection,您可能会认为自动提交被禁用,而实际上它只是被绕过。如果您在代码中添加closing 并失去事务完整性,您可能会感到不快;您将无法回滚更改,您可能会开始看到并发错误,而且原因可能不是很明显。
其次,with closing(MySQLdb.connect(user, pass)) as VAR 将 连接对象 绑定到 VAR,而 with MySQLdb.connect(user, pass) as VAR 将 新光标对象 绑定到 VAR。在后一种情况下,您将无法直接访问连接对象!相反,您必须使用游标的connection 属性,该属性提供对原始连接的代理访问。当游标关闭时,其connection 属性设置为None。这会导致放弃的连接一直存在,直到发生以下情况之一:
- 删除所有对光标的引用
- 光标超出范围
- 连接超时
- 通过服务器管理工具手动关闭连接
您可以通过监视打开的连接(在 Workbench 中或通过 using SHOW PROCESSLIST)来测试这一点,同时逐行执行以下行:
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