在Python中,如何确保在离开代码块前关闭数据库连接?
我想尽量避免数据库连接长时间保持打开状态,因为这段代码会在一个使用频繁的服务器上运行,大家都告诉我数据库连接应该尽快关闭。
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
...
但是,我该如何确保总是正确地关闭数据库连接,而不必担心中间有return
、raise
、continue
或者其他的情况呢?我在想是否可以用类似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 个回答
假设你使用的数据库驱动程序不支持直接使用 with
这个功能,可以试试 来自 contextlib
的 closing
方法。
如果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块都会被执行。
传统的方法是使用 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
。