何时使用MySQLdbclosures游标

我正在构build一个WSGI的Web应用程序,我有一个MySQL数据库。 我正在使用MySQLdb,它提供了执行语句和获取结果的游标。 获取和closures游标的标准做法是什么? 特别是我的游标要持续多久? 我应该为每个交易获得一个新的光标吗?

我相信你需要在提交连接之前closures光标。 find不需要中间提交的事务组是否有很大的优势,这样就不必为每个事务获取新的游标? 获得新的游标是否有很多开销,还是没有什么大不了的?

不要问什么是标准实践,因为这通常是不清楚和主观的,你可以尝试寻找模块本身的指导。 一般来说,使用with关键字作为另一个用户的build议是一个好主意,但在这个特定的情况下,它可能不会给你相当多的function,你所期望的。

从模块版本1.2.5开始, MySQLdb.Connection使用以下代码( github )实现上下文pipe理器协议 :

 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() 

现在有几个关于已经存在的问题,或者你可以阅读理解Python的“with”语句 ,但是本质上会发生什么是__enter__with块的开始__exit__执行,而__exit__在离开with块时执行。 如果打算稍后引用该对象,则可以将with EXPR as VAR的可选语法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__方法 – 并且不pipe它是否做,因为with只是pipe理连接。 因此,连接和光标在退出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' 

您应该看到输出“光标打开;连接打开”打印到标准输出。

我相信你需要在提交连接之前closures光标。

为什么? 作为MySQLdb基础的MySQL C API没有实现任何游标对象,正如模块文档中所暗示的那样: “MySQL不支持游标;但是,游标很容易被模拟。 事实上, MySQLdb.cursors.BaseCursor类直接从objectinheritance而来,并不会在提交/回滚方面对游标施加任何限制。 一位Oracle开发人员这样说 :

cur.close()之前的cnx.commit()对我来说最合乎逻辑。 也许你可以遵循这个规则:“如果你不再需要光标,就closures光标。” 因此在closures游标之前提交()。 最后,对于连接器/ Python,它没有多大的区别,但可能是其他数据库。

我期望这个问题和你要达到“标准实践”一样。

find不需要中间提交的事务集有什么重要的优点,这样就不必为每个事务获取新的游标?

我非常怀疑,试图这样做,你可能会引入更多的人为错误。 最好决定一个惯例,坚持下去。

获得新的游标是否有很多开销,还是没有什么大不了的?

开销可以忽略不计,根本不接触数据库服务器; 这完全在MySQLdb的实现中。 你可以看看github上的BaseCursor.__init__ ,如果你真的好奇,想知道当你创build一个新的游标时发生了什么。

回到之前讨论with ,也许现在你可以理解为什么MySQLdb.Connection__enter____exit__方法在每个块中给你一个全新的游标对象,而且不用担心跟踪它或closures它块的结尾。 它相当轻巧,纯粹为了您的方便而存在。

如果对游标对象进行微操作是非常重要的,则可以使用contextlib.closing来弥补游标对象没有定义__exit__方法的事实。 对于这个问题,你也可以使用它来强制连接对象在退出with块时closures。 这应该输出“my_curs已closures; my_conn已closures”:

 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方法。 (要看到这一点,只需使用__enter____exit__close包含简单print语句的方法来定义Foo类,然后比较在with Foo(): pass时发生的情况with Foo(): passwith closing(Foo()): pass时发生的事情with closing(Foo()): pass 。)这有两个重要的含义:

首先,如果启用了自动提交模式,MySQLdb将在with connection使用BEGIN一个显式事务,并在块的末尾提交或回滚事务。 这些是MySQLdb的默认行为,旨在保护您免受MySQL立即提交任何和所有DML语句的默认行为。 MySQLdb假定当你使用一个上下文pipe理器时,你想要一个事务,并使用明确的BEGIN绕过服务器上的自动提交设置。 如果您习惯了使用with connection ,您可能会认为自动提交实际上仅在被绕过时被禁用。 如果在代码中添加closing并失去事务完整性,您可能会遇到不愉快的惊喜; 您将无法回滚更改,您可能会开始看到并发错误,并且可能不会立即明白为什么。

其次, with closing(MySQLdb.connect(user, pass)) as VAR连接对象绑定到VAR ,与将with MySQLdb.connect(user, pass) as VAR绑定到一个新的光标对象 。 在后一种情况下,您将无法直接访问连接对象! 相反,你将不得不使用游标的connection属性,它提供了原始连接的代理访问。 当游标closures时,其connection属性被设置为None 。 这会导致一个被遗弃的连接,直到发生以下情况之一:

  • 所有对游标的引用都被删除
  • 光标超出范围
  • 连接超时
  • 通过服务器pipe理工​​具手动closures连接

您可以通过监视打开的连接(在Workbench中或通过使用SHOW PROCESSLIST )逐个执行以下行来进行testing:

 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 

最好用'with'关键字重写它。 'With'将自动关注closures游标(这很重要,因为它是非托pipe资源)。 好处是在exception的情况下也会closures光标。

 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() 

我想你会更好地使用一个游标来执行所有的执行,并在代码结束时closures它。 工作起来比较容易,也可能有效益(不要引用那个)。

 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() 

重点是你可以将游标执行的结果存储在另一个variables中,从而释放游标以执行第二次执行。 只有在使用fetchone()时,才会以这种方式遇到问题,并且在迭代第一个查询的所有结果之前需要执行第二个游标执行。

否则,我会说只要你把所有的数据从它们中取出,就closures你的游标。 这样你就不用担心在你的代码中稍后再束手无策。

注意:这个答案适用于PyMySQL ,它是MySQLdb的一个直接替代品,并且在MySQLdb停止维护的情况下实际上是最新版本的MySQLdb。 我相信这里的一切也是遗留MySQLdb,但没有检查。

首先,一些事实:

  • with语法的Python在执行with块的主体之前调用上下文pipe理器的__enter__方法,之后调用__exit__方法。
  • 连接有一个__enter__方法,除了创build和返回一个游标,还有一个__exit__方法,不pipe是提交还是回滚(取决于是否抛出exception)。 它closures连接。
  • PyMySQL中的游标纯粹是一个用Python实现的抽象; MySQL本身没有相应的概念。 1
  • 游标有一个不做任何事情的__exit__方法和一个“closures”游标的__exit__方法(这意味着将游标对其父连接的引用归零并丢弃存储在游标上的所有数据)。
  • 游标持有对产生它们的连接的引用,但连接不包含对它们创build的游标的引用。
  • 连接有一个closures它们的__del__方法
  • 根据https://docs.python.org/3/reference/datamodel.html,CPython (默认的Python实现)使用引用计数,一旦引用次数达到零,就会自动删除一个对象。

把这些东西放在一起,我们看到这样的朴素代码在理论上是有问题的:

 # Problematic code, at least in theory! import pymysql with pymysql.connect() as cursor: cursor.execute('SELECT 1') # ... happily carry on and do something unrelated 

问题是什么都没有closures连接。 事实上,如果将上面的代码粘贴到Python shell中,然后在MySQL shell中运行SHOW FULL PROCESSLIST ,您将能够看到您创build的空闲连接。 由于MySQL的默认连接数是151 ,这个数目并不是很大 ,所以如果你有很多进程保持这些连接打开的话,理论上你可能会遇到问题。

但是,在CPython中,有一个节约的宽限,可以确保像上面的示例那样的代码可能不会导致您离开大量打开的连接。 这种保存的优点是,只要cursor超出范围(例如创build完成的函数,或者cursor获得另一个值),其引用计数就会达到零,这会导致删除连接引用计数为零,导致连接__del__方法被调用强制closures连接。 如果你已经把上面的代码粘贴到你的Python shell中,那么现在你可以通过运行cursor = 'arbitrary value'来模拟这个代码。 只要您这样做,您打开的连接将从SHOW PROCESSLIST输出中消失。

然而,依赖于这一点是不雅观的,理论上可能会在CPython以外的Python实现中失败。 清理者在理论上是显式地.close()连接(释放数据库上的连接而不用等待Python来销毁对象)。 这个更健壮的代码如下所示:

 import contextlib import pymysql with contextlib.closing(pymysql.connect()) as conn: with conn as cursor: cursor.execute('SELECT 1') 

这是丑陋的,但不依赖于Python破坏你的对象来释放(有限数量的)数据库连接。

请注意,closures游标 ,如果你已经明确地closures连接,这是完全没有意义的。

最后,在这里回答第二个问题:

获得新的游标是否有很多开销,还是没有什么大不了的?

不,实例化一个游标根本不会碰到MySQL, 基本上什么都不做 。

find不需要中间提交的事务集有什么重要的优点,这样就不必为每个事务获取新的游标?

这是情况,很难给出一个普遍的答案。 正如https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html所说的那样,; “如果应用程序每秒提交数千次,则应用程序可能会遇到性能问题,而如果应用程序它只会每2-3小时提交一次“ 。 您为每次提交付出了性能开销,但是通过将事务打开的时间更长,可以增加其他连接不得不花费时间等待locking的机会,增加死锁的风险,并可能增加由其他连接执行的某些查找的成本。


1 MySQL有一个构造它调用一个游标,但它们只存在于存储过程中; 它们与PyMySQL游标完全不同,在这里不相关。

我build议像php和mysql这样做。 在打印第一个数据之前,从代码的开头开始我。 所以,如果你得到一个连接错误,你可以显示一个50x (不记得是什么内部错误)的错误信息。 并保持打开整个会议,并closures它,当你知道你不会再需要它了。