我正在构建一个WSGI Web应用程序,我有一个MySQL数据库。我正在使用MySQLdb,它提供了执行语句和获取结果的游标。 获取和关闭游标的标准做法是什么?特别是,我的游标应该持续多长时间?我应该为每笔交易获得一个新光标吗?
我相信你需要在提交连接之前关闭游标。查找不需要中间提交的事务集是否有任何显着优势,这样您就不必为每个事务获取新游标?获得新游标是否有很多开销,或者这不是什么大问题?
答案 0 :(得分:67)
不要问什么是标准做法,因为这通常不清楚和主观,你可能会尝试寻找模块本身的指导。一般来说,使用with
关键字作为另一个用户建议是一个好主意,但在这种特定情况下,它可能无法提供您期望的功能。
从模块的1.2.5版开始,MySQLdb.Connection
使用以下代码(context manager protocol)实现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()
现有几个关于with
的Q& A,或者您可以阅读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'
您应该看到输出"光标已打开;连接是开放的"打印到stdout。
我相信你需要在提交连接之前关闭游标。
为什么呢? MySQL C API是MySQLdb
的基础,它不实现任何游标对象,如模块文档中所暗示的那样:"MySQL does not support cursors; however, cursors are easily emulated."实际上,MySQLdb.cursors.BaseCursor
类直接继承自object
{1}}并且在提交/回滚方面对游标没有这样的限制。 Oracle开发人员had this to say:
在cur.close()之前的cnx.commit()听起来对我来说最合乎逻辑。可能是你 可以遵守规则:"如果您不再需要它,请关闭光标。" 因此在关闭游标之前commit()。最后,为 连接器/ Python,它没有太大的区别,但或其他 它可能是数据库。
我希望你能够接近"标准练习"关于这个问题。
找到不需要中间提交的交易集是否有任何显着优势,这样您就不必为每笔交易获得新的游标?
我非常怀疑它,并且在尝试这样做时,你可能会引入额外的人为错误。最好决定一项公约并坚持下去。
获取新游标是否有很多开销,或者这不是什么大问题?
开销可以忽略不计,根本不会触及数据库服务器;它完全在MySQLdb的实现中。如果您真的很想知道创建新光标时发生了什么,可以look at BaseCursor.__init__
on 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
并在块结束时提交或回滚事务时,MySQLdb将with connection
服务器上的显式事务。这些是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
答案 1 :(得分:28)
最好使用'with'关键字重写它。 'With'将自动关闭光标(它很重要,因为它是非托管资源)。好处是它也会在异常的情况下关闭光标。
from contextlib import closing
import MySQLdb
''' At the beginning you open a DB connection. Particular moment when
you open connection depends from your approach:
- it can be inside the same function where you work with cursors
- in the class constructor
- etc
'''
db = MySQLdb.connect("host", "user", "pass", "database")
with closing(db.cursor()) as cur:
cur.execute("somestuff")
results = cur.fetchall()
# do stuff with results
cur.execute("insert operation")
# call commit if you do INSERT, UPDATE or DELETE operations
db.commit()
cur.execute("someotherstuff")
results2 = cur.fetchone()
# do stuff with results2
# at some point when you decided that you do not need
# the open connection anymore you close it
db.close()
答案 2 :(得分:5)
我认为您最好尝试使用一个游标执行所有执行,并在代码末尾关闭它。它更容易使用,它也可能具有效率优势(不要引用我的那些)。
conn = MySQLdb.connect("host","user","pass","database")
cursor = conn.cursor()
cursor.execute("somestuff")
results = cursor.fetchall()
..do stuff with results
cursor.execute("someotherstuff")
results2 = cursor.fetchall()
..do stuff with results2
cursor.close()
关键是你可以将游标执行的结果存储在另一个变量中,从而释放光标以进行第二次执行。只有在使用fetchone()时才会遇到问题,并且需要在迭代第一个查询的所有结果之前执行第二个游标执行。
否则,我会说,只要你完成从中获取所有数据就关闭你的游标。这样,您就不必担心以后会在代码中绑定松散的结尾。
答案 3 :(得分:5)
注意:这个答案适用于PyMySQL,它是MySQLdb的替代品,实际上是自MySQLdb停止维护以来最新版本的MySQLdb。我相信这里的所有内容也是遗留MySQLdb的真实,但尚未检查。
首先,一些事实:
with
语法在执行__enter__
块的主体之前调用上下文管理器的with
方法,之后调用其__exit__
方法。__enter__
方法,除了创建和返回游标之外什么都不做,还有一个__exit__
方法提交或回滚(取决于是否抛出异常)。它没有关闭连接。__enter__
方法,它不执行任何操作,并且__exit__
方法可以“关闭”游标(这意味着将游标对其父连接的引用置零并丢弃任何存储的数据在光标上。)__del__
方法关闭它们把这些东西放在一起,我们看到像这样的天真代码理论上有问题:
# Problematic code, at least in theory!
import pymysql
with pymysql.connect() as cursor:
cursor.execute('SELECT 1')
# ... happily carry on and do something unrelated
问题是没有任何东西关闭连接。实际上,如果您将上面的代码粘贴到Python shell中然后在MySQL shell上运行SHOW FULL PROCESSLIST
,您将能够看到您创建的空闲连接。由于MySQL的默认连接数为151,而不是 large ,如果你有许多进程保持这些连接处于打开状态,理论上你可能会遇到问题。
然而,在CPython中,有一个保存优雅,可以确保像上面上面的例子这样的代码不会让你留下大量的开放连接。节省的优点是,只要cursor
超出范围(例如,创建它的函数完成,或cursor
获得另一个值),其引用计数就会达到零,这会导致要删除它,将连接的引用计数丢弃为零,导致调用连接__del__
方法强制关闭连接。如果您已经将上面的代码粘贴到Python shell中,那么现在可以通过运行cursor = 'arbitrary value'
来模拟这个代码。一旦这样做,您打开的连接将从SHOW PROCESSLIST
输出消失。
然而,依赖于此是不优雅的,理论上可能在CPython以外的Python实现中失败。理论上,Cleaner将明确地.close()
连接(释放数据库上的连接而不等待Python破坏对象)。这个更健壮的代码如下所示:
import contextlib
import pymysql
with contextlib.closing(pymysql.connect()) as conn:
with conn as cursor:
cursor.execute('SELECT 1')
这很丑陋,但不依赖Python破坏你的对象来释放你(有限数量的)数据库连接。
请注意,关闭游标,如果您已经明确地关闭了连接,则完全没有意义。
最后,回答这里的次要问题:
获取新游标是否有很多开销,或者这不是什么大问题?
不,实例化游标根本不会触及MySQL basically does nothing。
查找不需要中间提交的事务集是否有任何显着优势,这样您就不必为每个事务获取新游标?
这是情境性的,很难给出一般答案。正如https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html所说的那样,“如果应用程序每秒提交数千次会遇到性能问题,并且如果它每2-3小时提交就会遇到不同的性能问题”。您为每次提交支付了性能开销,但是通过将事务保持打开更长时间,您增加了其他连接不得不花时间等待锁定的机会,增加了死锁的风险,并可能增加其他连接执行的某些查找的成本
1 MySQL 有一个它调用cursor的构造,但它们只存在于存储过程中;它们与PyMySQL游标完全不同,在这里不相关。
答案 4 :(得分:-4)
我建议像php和mysql一样。在打印第一个数据之前,在代码的开头启动i。因此,如果出现连接错误,您可以显示50x
(不记得内部错误)错误消息。并保持整个会话开放,当你知道你不再需要它时关闭它。