何时使用MySQLdb关闭游标

| 我正在构建WSGI Web应用程序,并且我有一个MySQL数据库。我正在使用MySQLdb,它提供了用于执行语句和获取结果的游标。获取和关闭游标的标准做法是什么?特别是,我的光标应持续多长时间?我应该为每个交易获取一个新的游标吗? 我相信您需要在提交连接之前关闭游标。查找不需要中间提交的事务集有什么显着的优势,这样您就不必为每个事务获取新的游标了吗?获取新的游标是否有很多开销,还是不重要?     
已邀请:
既然不知道标准做法是什么,因为这通常不清楚并且很主观,您可以尝试向模块本身寻求指导。通常,建议另一个用户使用
with
关键字是一个好主意,但是在这种特定情况下,它可能无法提供预期的功能。 从模块的1.2.5版本开始,
MySQLdb.Connection
通过以下代码(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
的问答,或者您可以阅读了解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,),)\"
现在的问题是,退出“ 0”块后连接和游标的状态是什么?上面显示的
__exit__
方法仅调用
self.rollback()
self.commit()
,而这些方法都没有继续调用
close()
方法。游标本身未定义
__exit__
方法-并没有关系,因为
with
仅管理连接。因此,退出“ 0”块后,连接和游标都保持打开状态。通过在上面的示例中添加以下代码,可以很容易地确认这一点:
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不支持游标;但是,游标很容易模拟。\”实际上,Indeed21ѭ类直接继承了从ѭ22开始,并且对游标的提交/回退没有任何限制。 Oracle开发人员曾这样说:   cnx.commit()在cur.close()之前听起来对我来说最合逻辑。可能是你   可以遵循以下规则:\“如果不再需要关闭光标。\”   因此,在关闭游标之前,先执行commit()。最后,   连接器/ Python,并没有多大区别,但其他   数据库可能。 我希望这与您在该主题上达到“标准做法”一样近。   查找不需要中间提交的事务集有什么显着的优势,这样您就不必为每个事务获取新的游标了吗? 我对此非常怀疑,在尝试这样做时,您可能会引入其他人为错误。最好决定约定并坚持执行。   获取新的游标是否有很多开销,还是不重要? 开销可以忽略不计,完全不涉及数据库服务器。它完全在MySQLdb的实现中。如果您真的想知道创建新游标时发生了什么,可以在github上查看
BaseCursor.__init__
。 回到前面讨论
with
时,也许现在您可以理解为什么
MySQLdb.Connection
__enter__
__exit__
方法在每个
with
块中为您提供一个全新的游标对象,而不必理会它或最后将其关闭的块。它相当轻巧,纯粹是为了您的方便而存在。 如果对微管理光标对象确实很重要,则可以使用contextlib.closing来弥补光标对象没有定义
__exit__
方法的事实。为此,还可以使用它在退出“ 0”块时强制连接对象自行关闭。这应该输出\“ 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__
方法。它只会在
with
块的末尾调用参数对象的
close
方法。 (要查看实际情况,只需使用包含简单
print
语句的
__enter__
__exit__
close
方法定义类
Foo
,然后将执行
with Foo(): pass
时发生的情况与执行
with closing(Foo()): pass
时发生的情况进行比较。)这​​有两个重要的含义: 首先,如果启用了自动提交模式,当您使用“ 45”并在块末尾提交或回滚该事务时,MySQLdb将在服务器上将“ 44”作为显式事务。这些是MySQLdb的默认行为,旨在保护您免受MySQL的立即提交任何DML语句的默认行为的影响。 MySQLdb假定使用上下文管理器时需要事务,并使用显式的“ 44”绕过服务器上的自动提交设置。如果您习惯使用
with connection
,您可能会认为自动提交实际上只是被绕过了而被禁用了。如果在代码中添加“ 48”并失去事务完整性,您可能会感到不快。您将无法回滚更改,您可能会开始看到并发错误,并且可能并不清楚为什么。 其次,与将新光标对象绑定到50的51相比,“ 49”将连接对象绑定到“ 50”。在后一种情况下,您将无法直接访问连接对象!取而代之的是,您将必须使用游标的
connection
属性,该属性提供对原始连接的代理访问。关闭光标时,其“ 53”属性将设置为“ 55”。这将导致废弃的连接一直存在,直到发生以下情况之一: 删除所有对光标的引用 光标超出范围 连接超时 通过服务器管理工​​具手动关闭连接 您可以通过监视打开的连接(在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
    
最好使用\'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()
    
注意:此答案适用于PyMySQL,它是MySQLdb的直接替代品,并且实际上是自停止维护MySQLdb以来的最新版本的MySQLdb。我相信这里的所有情况对于旧版MySQLdb都是正确的,但尚未检查。 首先,一些事实: Python的
with
语法在执行
with
块的主体之前调用上下文管理器的
__enter__
方法,然后再执行其
__exit__
方法。 连接有一个
__enter__
方法,除了创建和返回游标外什么都不做;还有一个
__exit__
方法,它可以提交或回滚(取决于是否引发异常)。它不会关闭连接。 PyMySQL中的游标纯粹是用Python实现的抽象。 MySQL本身没有等效的概念。1 游标具有不执行任何操作的
__enter__
方法和“关闭”游标的
__exit__
方法(这仅意味着使游标对其父连接的引用无效,并丢弃游标上存储的所有数据)。 游标保留对产生它们的连接的引用,但是连接不保留对它们创建的游标的引用。 连接采用a67ѭ方法将其关闭 根据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
问题是没有任何事情关闭连接。确实,如果将上面的代码粘贴到Python Shell中,然后在MySQL Shell中运行
SHOW FULL PROCESSLIST
,您将能够看到您创建的空闲连接。由于MySQL的默认连接数为151,这并不是很大,因此,如果您有许多使这些连接保持打开状态的进程,那么从理论上讲您可能会遇到问题。 但是,在CPython中,有一个节省的宽限期,可确保像我上面的示例一样的代码不会导致您遗漏打开的连接。节省的余地是,一旦
cursor
超出范围(例如,创建它的函数完成,或者
cursor
获得分配给它的另一个值),其引用计数将变为零,这将导致其引用被删除,从而断开连接\的引用计数为零,导致调用连接的“ 67”方法,该方法强制关闭连接。如果已经将上面的代码粘贴到Python shell中,那么现在可以通过运行
cursor = \'arbitrary value\'
进行模拟;一旦这样做,打开的连接就会从from56ѭ输出中消失。 但是,仅依靠它是不明智的,并且在理论上可能会在CPython以外的Python实现中失败。从理论上讲,更清洁的方法是显式地对连接进行“ 75”连接(以释放数据库上的连接,而无需等待Python销毁对象)。这个更健壮的代码如下所示:
import contextlib
import pymysql
with contextlib.closing(pymysql.connect()) as conn:
    with conn as cursor:
        cursor.execute(\'SELECT 1\')
这很丑陋,但不依赖Python破坏对象来释放数据库连接(数量有限)。 请注意,如果您已经像这样显式关闭连接,则关闭游标完全没有意义。 最后,在这里回答次要问题:   获取新的游标是否有很多开销,还是不重要? 不,实例化一个游标根本不会影响MySQL,并且基本上什么也不做。   查找不需要中间提交的事务集有什么显着的优势,这样您就不必为每个事务获取新的游标了吗? 这是情境,很难给出普遍的答案。如https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html所述,“如果应用程序每秒提交数千次,则它可能会遇到性能问题,并且会出现不同的性能问题。如果仅每2-3小时提交一次”。您需要为每次提交支付性能开销,但是通过延长事务处理时间,会增加其他连接不得不花时间等待锁的机会,增加死锁的风险,并可能增加其他连接执行某些查找的成本。 1 MySQL确实具有调用游标的构造,但它们仅存在于存储过程中;它们与PyMySQL游标完全不同,因此与此处无关。     
我认为您最好尝试对所有执行使用一个游标,然后在代码末尾将其关闭。它使用起来更容易,并且也可能带来效率收益(请不要在那方面引用我)。
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()时,您才会遇到这种问题,并且在遍历第一个查询的所有结果之前需要执行第二次游标执行。 否则,我要说的是,一旦完成将所有数据移出游标,请立即关闭游标。这样,您不必担心稍后在代码中捆绑松散的一端。     
我建议这样做像php和mysql。在打印第一个数据之前,在代码的开头启动i。因此,如果出现连接错误,则可以显示a78ѭ(不记得是什么内部错误)错误消息。并在整个会话中保持打开状态,并在您不再需要它时将其关闭。     

要回复问题请先登录注册