MDriven ECO_ID 重复
Posted
技术标签:
【中文标题】MDriven ECO_ID 重复【英文标题】:MDriven ECO_ID duplicates 【发布时间】:2022-01-10 09:48:49 【问题描述】:MDriven 为多个对象生成相同的 ECO_ID 似乎存在问题。在大多数情况下,它似乎与意外的进程关闭和/或服务器关闭一起发生,但它也确实发生在正常活动期间。
我们的系统由一个 ASP.NET 应用程序和一个 WinForms 应用程序组成。 ASP.NET 应用程序在 IIS 中设置为使用单个工作进程。我们混合了 WebForms 和 MVC,包括 ApiControllers。我们使用的是相当旧的 ECO 包版本:7.0.0.10021。我们在 VS 2017,目标框架是 4.7.1。
我们已将其配置为使用 64 位整数作为对象 id:s。数据库是火鸟。 SQL 配置设置为使用 ReadCommitted 事务隔离。
据我所知,我们已经将EcoSpaceStrategyHandler
配置为EcoSpaceStrategyHandler.SessionStateMode.Never
,这应该意味着EcoSpaces 根本没有被重用,对吧? (为什么在这种情况下我什至会使用EcoSpaceStrategyHandler
,而不是使用new
关键字正常创建EcoSpace?)
我们创建了用于所有控制器的MasterController : Controller
和MasterApiController : ApiController
类。它们有一个 EcoSpace
属性,可以简单地执行此操作:
if (ecoSpace == null)
if (ecoSpaceStrategyHandler == null)
ecoSpaceStrategyHandler = new EcoSpaceStrategyHandler(
EcoSpaceStrategyHandler.SessionStateMode.Never,
typeof(DiamondsEcoSpace),
null,
false
);
ecoSpace = (DiamondsEcoSpace)ecoSpaceStrategyHandler.GetEcoSpace();
return ecoSpace;
即如果没有创建策略处理程序,则创建一个指定生态空间不进行池化和会话状态持久化的处理程序。然后,如果没有获取生态空间,则从策略处理程序获取一个。返回生态空间。这是一种可接受的方法吗?为什么会比简单地这样做更好:
if (ecoSpace = null)
ecoSpace = new DiamondsEcoSpace();
return ecoSpace;
在 aspx 中,我们有一个母版页,其中包含 EcoSpaceManager
。它已配置为使用池,但 SessionStateMode
是 Never
。将EnableViewState
设置为true
。这可以接受吗?这是否意味着 EcoSpaces 将在往返之间被汇集但停用?
我们可能会连续收到多个传入的 API 调用,因此一个 API 调用在下一个调用之前尚未完成。我假设这意味着 MasterApiController
的多个实例可以同时执行,但在单独的线程中。当然也可能有 MasterController
实例执行 MVC 请求,并且 WinForms 应用程序可能正在运行一些批处理作业或其他。
但据我了解,id 保留是在任何UpdateDatabase
调用开始时进行的,以这种方式:
update "ECO_ID" set "BOLD_ID" = "BOLD_ID" + :N;
select "BOLD_ID" from "ECO_ID";
如果返回值为K,这将保留N个新的id:s,范围从K-N到K-1。在任何地方使用ReadCommitted事务应该确保更新锁定id数据行,强制所有并发保存操作等待,然后在不受其他事务干扰的情况下获取更新结果,然后提交。此时,任何其他挂起的保存操作都可以继续进行自己的 id 保留。我看不出这会如何导致多个对象使用同一个 ID。
我应该注意到,它似乎有时会在一个单独的 UpdateDatabase 中产生 id 重复,即在保存一组新的相关对象时,其中一些最终具有相同的 id。不过我还没有真正证实这一点。
有什么想法吗?我应该寻找什么?
【问题讨论】:
【参考方案1】:问题很可能是您使用了 ReadCommitted 隔离。 这允许 2 个系统同时启动一个事务,读取当前值,增加批次,然后相互保存。
您必须使用可序列化的隔离来生成密钥;即只读取当前不在写操作中的东西。
MDriven 对隔离级别 UpdateIsolationLevel 和 FetchIsolationLevel 使用 2 个设置。
将您的 UpdateIsolationLevel 设置为可序列化
【讨论】:
好的,我会尝试让它运行一段时间,看看它是否有所作为。但是,如果密钥获取程序首先进行更新然后进行选择,正如我在 Firebird 提供程序的 SQL 日志中看到的那样,那么在第一个事务提交之前没有其他事务可以进行更新。那应该防止重复。从您的回复看来,密钥获取器有时/总是以选择然后更新开始。真的是这样吗? 重要的是选择和更新是否在同一个可序列化事务中。我认为所有数据库都不支持进行相对更新(更新 X 设置 y=y+10) - 即使它是数据库中的实现也可能是“选择 + 更新”,然后隔离级别的问题仍然适用. 明白。在那种情况下,我实际上希望该框架强制执行可序列化的密钥获取,并在一个专门的短期事务中执行此操作,该事务什么都不做。如果我将整个系统设置为可序列化,我担心我会受到严重的性能影响,并且可能还会出现死锁,考虑到读者会在该隔离级别阻止写入者,并且据我所知,这些锁适用于整个表,而不仅仅是特定的行(但我可能对那个细节有误解)。在一些批处理运行中,我将数万个对象保存在单个UpdateDatabase.
..
我应该补充一点,除非我在这里完全不合时宜,否则 MDriven 实际上会进行相对更新,然后在 UpdateDabase
期间进行选择,据我在 Firebird ADO 的 SQL 日志中所见提供者。我知道在 ReadCommitted 事务下这应该在 Firebird 中工作。如果它作为选择然后更新完成,它将不起作用,因为在这种情况下,并发事务可能会在第一个事务的选择和更新操作之间读取相同的值。这是一个“不,不”...我可以将自己的 ID 保留机制挂接到 MDriven 中吗?
无论如何你都会遇到同样的问题,有人会在你提交之前读取最后提交的值并对其进行操作——在这种情况下,序列化是唯一的选择。如果需要,您可以创建自己的密钥定义以上是关于MDriven ECO_ID 重复的主要内容,如果未能解决你的问题,请参考以下文章