何时使用MySQLdb关闭游标

jmi*_*loy 76 python mysql mysql-python

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

我相信你需要在提交连接之前关闭光标.查找不需要中间提交的事务集是否有任何显着优势,这样您就不必为每个事务获取新游标?获得新游标是否有很多开销,或者这不是什么大不了的事?

Air*_*Air 72

不要问什么是标准做法,因为这通常不清楚和主观,你可能会尝试寻找模块本身的指导.一般来说,使用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()
Run Code Online (Sandbox Code Playgroud)

现在有几个已经存在的Q&A with,或者你可以阅读了解Python的"with"语句,但实质上发生的是__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,),)"
Run Code Online (Sandbox Code Playgroud)

现在的问题是,退出with块后连接和光标的状态是什么?__exit__上面显示的方法只调用self.rollback()or 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'
Run Code Online (Sandbox Code Playgroud)

您应该看到打印到stdout的输出"光标已打开;连接已打开".

我相信你需要在提交连接之前关闭光标.

为什么?的MySQL的C API,这是基础MySQLdb,没有实现任何光标对象,作为模块文档中暗示:"MySQL不支持游标;然而,游标容易仿真".实际上,MySQLdb.cursors.BaseCursorobject在提交/回滚方面直接继承并且不对游标施加这样的限制.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'
Run Code Online (Sandbox Code Playgroud)

注意,with closing(arg_obj)不会调用参数对象__enter____exit__方法; 它只会closewith块的末尾调用参数对象的方法.(要看到这个动作,简单地定义一个类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
Run Code Online (Sandbox Code Playgroud)

  • 你的帖子是最详尽的,但即使重读了几次,我发现自己仍然对关闭游标感到困惑.从关于这个问题的众多帖子来看,这似乎是一个常见的混淆点.我的看法是,游标似乎不需要.close()来调用 - 永远.那么为什么甚至有一个.close()方法呢? (13认同)
  • 简短的回答是,`cursor.close()`是的一部分[Python的DB API](https://www.python.org/dev/peps/pep-0249/#cursor-methods),将其不被写入特别是考虑到MySQL. (6认同)

Rom*_*nov 29

最好使用'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()
Run Code Online (Sandbox Code Playgroud)


Mar*_*ery 6

注意:此答案适用于PyMySQL,它是MySQLdb的直接替代品,并且实际上是自停止维护MySQLdb以来的最新版本的MySQLdb。我相信这里的一切都遗留MySQLdb的真实,但还没有检查。

首先,一些事实:

  • Python的with语法__enter__在执行with块主体之前调用上下文管理器的方法,然后在执行其__exit__方法。
  • 连接有一个__enter__除了创建和返回游标之外什么都不做的方法,以及一个__exit__提交或回滚的方法(取决于是否抛出异常)。它不会关闭连接。
  • PyMySQL中的游标纯粹是用Python实现的抽象。MySQL本身没有等效的概念。1个
  • 游标有一个__enter__不执行任何操作的__exit__方法和一个“关闭”游标的方法(这仅意味着使游标对其父连接的引用为空,并丢弃所有存储在游标上的数据)。
  • 游标保留对产生它们的连接的引用,但是连接不保留对它们创建的游标的引用。
  • 连接具有__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
Run Code Online (Sandbox Code Playgroud)

问题是没有任何事情关闭连接。确实,如果将上面的代码粘贴到Python shell中,然后SHOW FULL PROCESSLIST在MySQL shell上运行,您将能够看到您创建的空闲连接。由于MySQL的默认连接数为151,这不是很大,因此,如果您有许多使这些连接保持打开状态的进程,那么从理论上讲您可能会遇到问题。

然而,在CPython的,是有救命之恩,以确保像我上面的例子代码可能不会引起你的周围留下打开的连接的负载。节省cursor的余地是,一旦超出范围(例如,创建它的函数完成,或cursor获得分配给它的另一个值),其引用计数就为零,这将导致该引用计数被删除,从而删除连接的引用计数设置为零,将导致__del__强制关闭连接的连接方法被调用。如果您已经将上面的代码粘贴到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')
Run Code Online (Sandbox Code Playgroud)

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

请注意,如果已经像这样显式关闭了连接,则关闭游标完全没有意义。

最后,在这里回答次要问题:

获取新的游标是否有很多开销,还是不重要?

不,实例化一个游标根本不会影响MySQL,并且基本上什么也没做

查找不需要中间提交的事务集是否有显着的优势,这样您就不必为每个事务获取新的游标?

这是情境,很难给出普遍的答案。正如https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html所述“如果应用程序每秒提交数千次,则它可能会遇到性能问题,而如果每秒提交数千次,则可能会遇到性能问题。它仅每2-3小时提交一次”。您需要为每次提交支付性能开销,但是通过延长事务处理的开放时间,会增加其他连接不得不花时间等待锁的机会,增加死锁的风险,并可能增加其他连接执行的某些查找的成本。


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


nct*_*t25 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()
Run Code Online (Sandbox Code Playgroud)

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

否则,我会说你只要在完成所有数据后就关闭你的游标.这样您就不必担心以后在代码中绑定松散的端点了.