既然不知道什么是标准做法,因为这通常是不清楚和主观的,因此您可以尝试向模块本身寻求指导。通常,将with
关键字用作其他用户的建议是一个不错的主意,但是在这种特定情况下,它可能无法提供预期的功能。
从模块的1.2.5版本开始,使用以下代码(github)MysqLdb.Connection
实现上下文管理器协议:
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
,或者您可以阅读了解Python的“ with”语句,但是实质上发生的是__enter__
在with
块的开头__exit__
执行,并在离开with
块时执行。如果打算以后引用该对象,则可以使用可选的语法with EXPR as VAR
将by返回的对象绑定 __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 APIMysqLdb
没有实现任何游标对象:“ MySQL不支持游标;但是,游标易于仿真。” 实际上,MysqLdb.cursors.BaseCursor
该类直接继承object
于游标,并且对提交/回滚没有任何限制。Oracle开发人员曾这样说:
在cur.close()之前的cnx.commit()对我来说最合乎逻辑。也许您可以遵循以下规则:“如果不再需要,请关闭光标。” 因此,在关闭游标之前,先执行commit()。最后,对于Connector / Python而言,它并没有多大区别,但是对于其他数据库而言,则可能没有什么不同。
我希望这与您达到该主题的“标准实践”一样近。
查找不需要中间提交的事务集是否有任何显着的优势,这样您就不必为每个事务获取新的游标?
我对此非常怀疑,在尝试这样做时,您可能会引入其他人为错误。最好决定约定并坚持执行。
获取新的游标是否有很多开销,还是不重要?
开销可以忽略不计,完全不涉及数据库服务器;它完全在MysqLdb的实现中。如果您真的想知道创建新游标时发生了什么,可以在BaseCursor.__init__
github上查看。
回到前面的讨论中with
,也许现在您可以理解为什么MysqLdb.Connection
类__enter__
和__exit__
方法在每个with
块中为您提供了一个全新的游标对象,而不必理会它或在块末尾将其关闭。它相当轻巧,纯粹是为了您的方便而存在。
如果对微管理光标对象确实很重要,则可以使用contextlib.closing来弥补光标对象没有定义__exit__
方法的事实。为此,还可以使用它强制连接对象在退出with
块时自行关闭。这应该输出“ my_curs已关闭; my_conn已关闭”:
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__
方法。它只会close
在with
块的末尾调用参数对象的方法。(要查看实际情况,只需Foo
使用__enter__
,__exit__
和close
包含简单print
语句的方法定义一个类,然后将执行时的操作with Foo(): pass
与执行时的操作进行比较with closing(Foo()): pass
。)这有两个重要的含义:
首先,如果启用了自动提交模式,则BEGIN
当您with connection
在块末尾使用并提交或回滚事务时,MysqLdb将在服务器上进行显式事务。这些是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中或使用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