我正在尝试使用cx_Oracle连接到Oracle实例并执行一些DDL语句:
db = None
try:
db = cx_Oracle.connect('username', 'password', 'hostname:port/SERVICENAME')
#print(db.version)
except cx_Oracle.DatabaseError as e:
error, = e.args
if error.code == 1017:
print('Please check your credentials.')
# sys.exit()?
else:
print('Database connection error: %s'.format(e))
cursor = db.cursor()
try:
cursor.execute(ddl_statements)
except cx_Oracle.DatabaseError as e:
error, = e.args
if error.code == 955:
print('Table already exists')
if error.code == 1031:
print("Insufficient privileges - are you sure you're using the owner account?")
print(error.code)
print(error.message)
print(error.context)
cursor.close()
db.commit()
db.close()
但是,我不太确定这里的异常处理的最佳设计是什么。
首先,我在try块中创建db
对象,以捕获任何连接错误。
但是,如果无法连接,则db
将不再存在 - 这就是我在上面设置db = None
的原因。但是,这是一种好的做法吗?
理想情况下,我需要捕获连接错误,然后运行DDL语句时出错,等等。
嵌套异常是个好主意吗?或者有更好的方法来处理像这样的依赖/级联异常吗?
此外,还有一些部分(例如连接失败),我希望脚本只是终止 - 因此注释掉sys.exit()
调用。但是,我听说使用异常处理进行流量控制是不好的做法。想法?
答案 0 :(得分:29)
但是,如果无法连接,则
db
将不再存在 - 这就是我设置db = None
的原因。但是,这是一种好的做法吗?
不,设置db = None
不是最佳做法。有两种可能性,无论是连接到数据库都可以使用,也可以不使用。
连接数据库不起作用:
由于引发的异常已被抓住而未重新提出,因此您需要继续直至cursor = db.Cursor()
。
db == None
,因此会引发类似于TypeError: 'NoneType' object has no attribute 'Cursor'
的异常。由于已经捕获了数据库连接失败时生成的异常,因此伪装失败的原因。
就个人而言,我总是提出一个连接异常,除非你很快再试一次。你如何抓住它取决于你;如果错误仍然存在,我会发电子邮件说“去检查数据库”。
连接数据库确实有效:
变量db
已在try:... except
块中指定。如果connect
方法有效,则db
将替换为连接对象。
无论哪种方式,都不会使用db
的初始值。
但是,我听说过使用异常处理进行流量控制 这样做是不好的做法。
与其他语言不同,Python 使用异常处理进行流控制。在我的回答结束时,我已经链接到有关Stack Overflow和程序员的几个问题,他们提出了类似的问题。在每个例子中,你都会看到“但在Python中”的字样。
这并不是说你应该过火,但Python通常会使用口头禅EAFP,“请求宽恕比允许更容易。” {{3}中排名前三的选举案例你是如何使用流量控制的好例子。
嵌套异常是个好主意吗?还是有更好的交易方式 有这样的依赖/级联异常吗?
嵌套异常没有任何问题,只要你做得很好。考虑你的代码。您可以删除所有异常并将整个事件包装在try:... except
块中。如果出现异常,那么你就会知道它是什么,但要确切地追踪出错的地方有点困难。
如果您想在cursor.execute
失败时自己发电子邮件,会发生什么?为了执行这一任务,您应该在cursor.execute
附近有一个例外。然后,您重新引发异常,以便它在您的外部try:...
中被捕获。不重新提升将导致您的代码继续执行,就好像什么都没有发生,并且您在外部try:...
中处理异常的任何逻辑都将被忽略。
最终所有例外都继承自How do I check if a variable exists?。
此外,我还有一些部分(例如连接失败) 刚刚终止的脚本 - 因此注释掉sys.exit()调用。
我添加了一个简单的类以及如何调用它,这大致就是我要做的事情。如果这将在后台运行,那么错误的打印是不值得的 - 人们不会坐在那里手动寻找错误。他们应该以您的标准方式登录,并通知相应的人员。因为这个原因,我删除了打印并替换了提醒记录。
当connect
方法失败并引发异常时,我将类拆分为多个函数,在尝试断开连接后,将不会运行execute
调用并且脚本将完成。
import cx_Oracle
class Oracle(object):
def connect(self, username, password, hostname, port, servicename):
""" Connect to the database. """
try:
self.db = cx_Oracle.connect(username, password
, hostname + ':' + port + '/' + servicename)
except cx_Oracle.DatabaseError as e:
# Log error as appropriate
raise
# If the database connection succeeded create the cursor
# we-re going to use.
self.cursor = self.db.cursor()
def disconnect(self):
"""
Disconnect from the database. If this fails, for instance
if the connection instance doesn't exist, ignore the exception.
"""
try:
self.cursor.close()
self.db.close()
except cx_Oracle.DatabaseError:
pass
def execute(self, sql, bindvars=None, commit=False):
"""
Execute whatever SQL statements are passed to the method;
commit if specified. Do not specify fetchall() in here as
the SQL statement may not be a select.
bindvars is a dictionary of variables you pass to execute.
"""
try:
self.cursor.execute(sql, bindvars)
except cx_Oracle.DatabaseError as e:
# Log error as appropriate
raise
# Only commit if it-s necessary.
if commit:
self.db.commit()
然后叫它:
if __name__ == "__main__":
oracle = Oracle.connect('username', 'password', 'hostname'
, 'port', 'servicename')
try:
# No commit as you don-t need to commit DDL.
oracle.execute('ddl_statements')
# Ensure that we always disconnect from the database to avoid
# ORA-00018: Maximum number of sessions exceeded.
finally:
oracle.disconnect()
进一步阅读:
cx_Oracle
documentation
Why not use exceptions as regular flow of control?
Is python exception handling more efficient than PHP and/or other languages?
答案 1 :(得分:0)
另一种可能优雅的解决方案是使用装饰器来实现数据库调用功能。装饰器能够修复错误并再次尝试数据库调用。对于过时连接,修复是重新连接并重新发出呼叫。 这是为我工作的装饰器:
####### Decorator named dbReconnect ########
#Retry decorator
#Retries a database function twice when the 1st fails on a stale connection
def dbReconnect():
def real_decorator(function):
def wrapper(*args, **kwargs):
try:
return function(*args, **kwargs)
except Exception as inst:
print ("DB error({0}):".format(inst))
print ("Reconnecting")
#...Code for reconnection is to be placed here..
......
#..end of code for reconnection
return function(*args, **kwargs)
return wrapper
return real_decorator
###### Decorate the DB Call like this: #####
@dbReconnect()
def DB_FcnCall(...):
....
注意:如果使用连接池,则检查连接并在过期时刷新连接的内部连接池技术也将解决问题。