在Python中,如何确保在离开代码块前关闭数据库连接?

27 投票
4 回答
42688 浏览
提问于 2025-04-15 22:43

我想尽量避免数据库连接长时间保持打开状态,因为这段代码会在一个使用频繁的服务器上运行,大家都告诉我数据库连接应该尽快关闭。

def do_something_that_needs_database ():
    dbConnection = MySQLdb.connect(host=args['database_host'], user=args['database_user'], passwd=args['database_pass'], db=args['database_tabl'], cursorclass=MySQLdb.cursors.DictCursor)
    dbCursor = dbConnection.cursor()
    dbCursor.execute('SELECT COUNT(*) total FROM table')
    row = dbCursor.fetchone()
    if row['total'] == 0:
        print 'error: table have no records'
        dbCursor.execute('UPDATE table SET field="%s"', whatever_value)
        return None
    print 'table is ok'
    dbCursor.execute('UPDATE table SET field="%s"', another_value)

    # a lot more of workflow done here

    dbConnection.close()

    # even more stuff would come below

不过,我觉得当表里没有数据时,这样做可能会让数据库连接保持打开状态,尽管我对这个是怎么运作的仍然不太确定,我还是不太明白

无论如何,这样的设计可能不好,因为我可以在每个小的execute块后打开和关闭数据库连接。当然,我可以在那种情况下在return之前加一个close...

但是,我该如何确保总是正确地关闭数据库连接,而不必担心中间有returnraisecontinue或者其他的情况呢?我在想是否可以用类似try的代码块,像下面这个建议,但显然这个方法不行:

def do_something_that_needs_database ():
    dbConnection = MySQLdb.connect(host=args['database_host'], user=args['database_user'], passwd=args['database_pass'], db=args['database_tabl'], cursorclass=MySQLdb.cursors.DictCursor)
    try:
        dbCursor = dbConnection.cursor()
        dbCursor.execute('SELECT COUNT(*) total FROM table')
        row = dbCursor.fetchone()
        if row['total'] == 0:
            print 'error: table have no records'
            dbCursor.execute('UPDATE table SET field="%s"', whatever_value)
            return None
        print 'table is ok'
        dbCursor.execute('UPDATE table SET field="%s"', another_value)
        # again, that same lot of line codes done here
    except ExitingCodeBlock:
        closeDb(dbConnection)
    # still, that "even more stuff" from before would come below

我觉得没有类似于ExitingCodeBlock的东西来处理异常,虽然我知道有try else,但我希望Python已经有类似的功能...

或者也许有人可以给我建议,告诉我这种做法很糟糕,强烈建议我不要这样做。也许这根本不需要担心,让MySQLdb来处理就可以了,或者不是呢?

4 个回答

4

假设你使用的数据库驱动程序不支持直接使用 with 这个功能,可以试试 来自 contextlibclosing 方法

6

如果MySQLdb支持的话,你可以使用“with”语句。这个“with”语句就是为了这个目的而存在的。不过,它需要对象定义两个方法:__enter__和__exit__,这样才能正常工作。

举个“with”语句的例子……比如在读写文件时,你可能会这样写:

with open('filename','r') as file:
    for line in file:
        # processing....
# File automatically closed afterwards or if there was an exception thrown

如果它不支持“with”,那么你可以使用try...finally,像这样:

try:
    # Do some processing
finally:
    # Cleanup

不管try块是成功结束,还是遇到异常但被捕获,或者抛出了异常并继续传播,finally块都会被执行。

34

传统的方法是使用 try/finally 语句:

def do_something_that_needs_database ():
    dbConnection = MySQLdb.connect(host=args['database_host'], user=args['database_user'], passwd=args['database_pass'], db=args['database_tabl'], cursorclass=MySQLdb.cursors.DictCursor)
    try:
       # as much work as you want, including return, raising exceptions, _whatever_
    finally:
       closeDb(dbConnection)

从 Python 2.6 开始(在 2.5 中需要加上 from __future__ import with_statement),有了一种新的方法(不过 try/finally 依然很好用!):那就是 with 语句。

with somecontext as whatever:
   # the work goes here

上下文有一个 __enter__ 方法,在进入时执行(可以返回上面提到的 whatever),还有一个 __exit__ 方法,在退出时执行。尽管这种方式很优雅,但如果没有现成的上下文符合你的需求,构建一个上下文所需的工作量(虽然在 2.6 中通过 contextlib 有所减少)可能还是让人觉得老老实实用 try/finally 更好。

如果你使用的是 2.6 版本,并且想尝试 contextlib,可以这样做来“隐藏” try/finally...:

import contextlib

@contextlib.contextmanager
def dbconnect(**kwds):
  dbConnection = MySQLdb.connect(**kwds)
  try:
    yield dbConnection
  finally:
    closeDb(dbConnection)

可以这样使用:

def do_something_that_needs_database ():
    with dbconnect(host=args['database_host'], user=args['database_user'], 
                   passwd=args['database_pass'], db=args['database_tabl'], 
                   cursorclass=MySQLdb.cursors.DictCursor) as dbConnection:
       # as much work as you want, including return, raising exceptions, _whatever_

如果你打算使用很多次,这样做可能是值得的,毕竟可以避免每次都重复写 try/finally

撰写回答