【问题标题】:Return values, throw exceptions and rolling back transactions返回值、抛出异常和回滚事务
【发布时间】:2011-09-19 12:11:19
【问题描述】:

关于“何时抛出异常或返回值”的问题被问了很多(请参阅下面的示例):

Should a retrieval method return 'null' or throw an exception when it can't produce the return value?

我完全同意 main 中的答案。

现在我的问题来自于在将其应用于更复杂的系统时向上述内容添加更多上下文。我会尽量保持简短和简单。

我们有一个示例 MVC PHP 应用程序:

模型 A:有一个函数 get_car($id),它返回一个汽车对象。

控制器 A 有一个简单的功能,比如向用户展示汽车

然而,控制器 B 有一个复杂的功能,比如获取汽车、修改它(比如通过模型 A 的设置函数之一)以及通过整个系统中的其他模型和库根据其中一些新值更新其他表 - 非常复杂啊哈哈

我们现在进入我的问题的主要部分:

为了数据完整性,我想使用 MySQL 事务。这就是我遇到“什么是最好的/什么是最佳实践”场景的地方......

如果找不到汽车或存在 SQL 错误,我们会编写 Model A 返回 FALSE。 这对控制器 A 来说很好,因为它只是想知道是否有错误和 bom 出来,所以我们只需检查返回值 an bom - 很好。

我们现在进入控制器 B。控制器 B 说在调用模型 A 函数之前进行了一些数据库更新,我们需要在错误时回滚,因此我们需要使用事务。现在这是我解决问题的地方。我是否将模型 A 保留为返回值并仅检查它,或者我是否将其更改为抛出异常并产生连锁反应,然后必须重新编写控制器 A,因为我们现在需要捕获异常......然后(不是尚未完成;o)) 我是否在模型的捕获中回滚(但我们如何知道事务是否已被使用?)或者我们是否捕获并重新抛出或允许冒泡到控制器捕获和在那里回滚?

我想说的是,如果我有很多与数据库交互的模型和控制器,我是否应该让它们抛出异常,然后将我的所有其他代码(例如控制器函数)包装在 try catch 中,将模型或库函数封装起来抛出,或者,我是否让模型“自包含”以整理和处理自己的问题,但是如果(对于这个“调用”)一个是打开的(根据我上面的示例,不是每个交易打开的时间...)?如果是这种情况,我将不得不让我的所有函数返回一些东西,然后在控制器中检查它,因为这是唯一知道是否存在开放交易的地方......

所以为了澄清我可以在控制器中使用 try catch 来捕获和回滚,这没关系,但是我如何从“进一步向下”执行此操作,例如在模型或库函数中......这可以同时调用在和事务期间或只是作为自动提交正常的 MySQL 调用?

解释的答案会很棒(因为我想了解我为什么要做某事),但如果不是为以下解决方案中最喜欢的解决方案投票(以及我可以看到的解决方案):

1) 使所有模型和库函数始终返回一个值,然后控制器可以在必要时进行 bom 或尝试捕获以回滚 - 但这意味着我必须检查每个模型和库的返回值在任何地方使用它们。

2) 让所有模型和库函数抛出异常(比如 SQL 错误),并将每个控制器(将调用模型和库函数)包装在 try catch 中,如果必要,catch 将直接返回或回滚。 ..

还请注意“bom”是将用户推送到某个地方或显示一个漂亮的错误(在有人说“让你的应用程序死掉是不好的做法......”大声笑)

我希望你明白我从这里来的地方,并对这个冗长的问题感到抱歉。

提前致谢 本

【问题讨论】:

    标签: php mysql transactions try-catch return-value


    【解决方案1】:

    [“为了数据完整性,我想使用 MySQL 事务”中隐含了一个理论问题……因为 MySQL 历来不是很 ACID - PostgreSQL 和 Oracle 都为 ACID 提供了更强大的支持。但是,回到真正的问题...]

    您的 (1) 和 (2) 都关注异常与失败返回值,但我的印象是,这不是解决异常、错误返回和打开事务的关键部分(并且某些数据库支持 SQL 异常以及)。相反,我将专注于保持事务状态与操纵模型的函数的嵌套相关联。这里有一些想法:

    • 无论如何,您可能总是会从某些库函数返回错误,因此让模型 A 返回 FALSE 并没有真正打破范式,错误返回与异常的混合也没有什么特别麻烦的地方。但是,错误返回必须正确冒泡 - 如果超出本地地址范围,则必须将其转换为异常。
    • 嵌套事务是让一个控制器启动数据库操作并仍然调用应用程序中也使用事务的其他东西的最明显方式。这允许失败的子子函数仅中止其自己的部分事务,并采用错误返回或异常方法在非 SQL 端冒泡错误,而关闭的子事务仍保持合理的匹配状态。这通常需要在数据库之外的代码中进行模拟(这本质上是 Django 所做的)。
    • 您的代码可以启动一个新的(可能很大的)事务,并跟踪它已经打开的事实,以防止代码中的子子函数尝试重新打开它。
    • 在某些数据库中,代码可以根据数据库会话状态检测事务是否已打开,从而使您可以检查数据库会话状态,而不是在代码中跟踪它。
    • 以上两种方法都允许使用保存点来模拟真正的嵌套事务。
    • 必须非常小心避免调用带有隐式提交的 SQL 调用(例如 CREATE TABLE)。在这个问题上,MySQL 可能比 PostgreSQL 更值得谨慎。

    实现一个大事务方法的一种方法是使用高级函数来启动事务,然后自身调用控制器 B 需要执行的任何操作的顶部。这使得错误冒泡或出现特殊的中止事务异常非常简单。如果没有子函数失败并且没有捕获到异常,则只有顶层函数会调用提交而不是中止。子函数不会调用提交。

    相反,您可以让您的所有函数关注在非 SQL 端(您的代码)中实现的事务深度,尽管在某些语言中这比其他语言更难设置(在 Python 中使用装饰器非常容易,例如)。如果他们完成并且事务深度为零,这将允许他们中的任何人调用提交。

    希望这对某人有所帮助:-)

    【讨论】:

      猜你喜欢
      • 2015-10-03
      • 2020-09-24
      • 2022-07-22
      • 1970-01-01
      • 1970-01-01
      • 2019-01-21
      • 2013-10-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多