【问题标题】:cx_Oracle and Exception Handling - Good practices?cx_Oracle 和异常处理 - 好的做法?
【发布时间】:2011-11-19 22:07:22
【问题描述】:

我正在尝试使用 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() 调用。但是,我听说像这样对流控制使用异常处理是不好的做法。想法?

【问题讨论】:

  • 我怀疑您是否需要处理单独的错误代码才能打印它们,异常应该有来自驱动程序的消息。您可以使用嵌套异常,例如如果您制作故障转移代码,例如尝试查询 -> 失败 -> 重新连接 -> 重试。您的代码流对我来说看起来不错。一个好主意是创建一个函数并将清理代码放在 finally 块中(关闭光标数据库等)

标签: python oracle cx-oracle


【解决方案1】:

但是,如果它无法连接,那么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 和 Programmers 上提出类似问题的几个问题。在每个示例中,您都会看到“但在 Python 中”的字样。

这并不是说你应该过分,但 Python 通常使用 EAFP 的口头禅,“请求宽恕比请求许可更容易。” How do I check if a variable exists? 中投票的前三名示例很好关于如何使用流量控制的示例。

嵌套异常是个好主意吗?或者有没有更好的处理方式 有这样的依赖/级联异常?

嵌套异常并没有什么问题,只要您理智地执行此操作即可。考虑你的代码。您可以删除所有异常并将整个事物包装在 try:... except 块中。如果引发了异常,那么您就会知道它是什么,但是要准确找出问题所在就有点困难了。

如果您想在cursor.execute 失败时向自己发送电子邮件,会发生什么情况?您应该在cursor.execute 周围有一个例外,以便执行这项任务。然后,您重新引发异常,使其被您的外部try:... 捕获。不重新引发将导致您的代码继续运行,就好像什么都没发生一样,并且您在外部 try:... 中放置的用于处理异常的任何逻辑都将被忽略。

最终所有的异常都继承自BaseException

另外,还有一些我想要的部分(例如连接失败) 脚本只是终止 - 因此注释掉 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?
Arguments for or against using try catch as logical operators

【讨论】:

  • 我正在使用您建议 Ben 的代码 sn-p 但我遇到了以下错误 ORA-24550: signal received: Unhandled exception: Code=c0000005 Flags=0 它无法捕获错误主机或 SID 输入的异常和我的 python Django 服务器崩溃
  • 我假设this 是你的问题@sarath。这是一个 Oracle 问题,与 Python 无关,除非您说cx_Oracle.DatabaseError 没有捕获异常,否则我看不出您的 Django 服务器是如何相关的。您能否为您的问题添加更多详细信息?
  • 所以语句 cx_Oracle.connect 没有被我在错误主机和 SID 条目上的异常块捕获。如何捕获我在 try 块中使用 cx.Oracle.connect 的异常,异常块的后面是除了 cx_Oracle.DatabaseError as e: error, = e.args if error.code == 1017: print('Please检查您的凭据。') else: print('Database connection error: %s' % (e,)) raise except cx_Oracle.Error as e: error=e.args print('Error.') raise
  • 我无法在 cmets @sarath 中回答问题,你需要问一个(如果你继续删除它们,你会被禁止提问,所以不要那样做)。只需使用所有信息编辑您之前的问题。您实际上并没有发布您正在使用的确切代码或您从 Python 获得的确切错误消息。不要捕获异常,让它引发,看看它是什么。
  • .connect() 放入try: except 块并忽略所有错误@Superdooperhero; try: cx_Oracle.connect(...) except cx_Oracle.DatabaseError: pass。我强烈建议不要这样做。你永远不会发现有什么问题。
【解决方案2】:

另一种可能是优雅的解决方案是对数据库调用函数使用装饰器。装饰器能够修复错误并再次尝试数据库调用。对于过时的连接,补救措施是重新连接并重新发出呼叫。 这是对我有用的装饰器:

####### 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(...):
    ....

Github 上的更多细节:https://github.com/vvaradarajan/DecoratorForDBReconnect/wiki

注意:如果您使用连接池,内部连接池技术检查连接并在陈旧时刷新它,也可以解决问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-12
    • 1970-01-01
    • 1970-01-01
    • 2011-01-29
    相关资源
    最近更新 更多