在多用户访问环境中即时“锁定”记录

Posted

技术标签:

【中文标题】在多用户访问环境中即时“锁定”记录【英文标题】:Instantly "locking" a record in multi-user Access environment 【发布时间】:2017-06-29 14:02:19 【问题描述】:

因此,Access 中的记录锁定非常糟糕。我不能使用内置的记录锁定,因为它锁定记录的“页面”而不是单个记录(我尝试更改使用记录级锁定的设置,但它仍然锁定页面而不是仅仅锁定一条记录),但即使我可以让它工作,它也不能解决我的问题,因为在用户开始对表单进行更改之前,记录不会锁定。

问题是,当两个人打开相同的记录时,他们可以开始进行更改并同时保存(从而覆盖之前的更改)。更糟糕的是,表单上有链接到其他表(以 ID 为键)的列表框,如果它们都打开相同的记录,它们对这些表所做的更改会被随后的任何更改覆盖。

长话短说,我需要确保两个人不可能同时打开同一条记录(无论他们是否对其进行了任何编辑) .

为此,我在表中添加了一个字段,指示记录是否已被用户锁定。当他们打开一个表单时,它会在字段中设置他们的姓名,其他尝试打开该记录的用户会收到一条通知,表明它已被锁定。问题是,这个“锁”不是瞬时的。其他用户需要几秒钟才能“检测到”该记录已被锁定,因此如果两个人几乎同时尝试打开同一条记录,这将允许他们都打开它。我已将事务应用到设置锁的 UPDATE 语句,但它仍然留下一个短窗口,其中锁不会“占用”并且两个人可以打开相同的记录。

那么,有没有一种方法可以使 UPDATE 即时(以便所有其他用户立即看到它的结果),或者更好的是一种在 Access 多用户环境中锁定记录的强大而全面的方法?

【问题讨论】:

【参考方案1】:

不清楚为什么您只收到“页面”锁定。

如果你在 file->options 中打开行锁定,那么你还需要设置特定的表单来锁定当前记录。因此,仅打开记录锁定对您没有帮助。该设置仅为新表单设置默认值 - 它不是系统范围的设置。

如果您正确地为表单打开锁定,那么如果两个用户正在查看同一记录并且一个用户开始编辑该记录,那么所有其他用户都不能编辑该记录。任何其他尝试编辑记录的用户都将在记录选择器栏中看到一个“锁定”图标(假设为给定表单打开了所选记录)。如果他们尝试在给定表单上的任何可编辑控件中键入内容,他们也会收到“哔”声。

当他们尝试编辑时,他们会在选择器栏上看到一个可见的“锁定”图标,如下所示:

一些事情:

如果两个用户能够编辑一条记录,那么您没有为该给定表单打开锁定。必须逐个表格设置此功能。更改文件->选项->客户端设置中的设置仅为您创建的新表单设置默认值!因此该设置仅适用于新表单的默认设置 - 它不会更改现有表单。

所以设置记录锁定只是一个表单设置。

因此,您始终必须将要锁定的每个表单设置为当前编辑的记录。您可以在表单设计中设置它,在属性表的数据选项卡中,如下所示:

还要记住,记录级别锁定的设置(不同的设置和功能)是访问客户端设置,不会随给定的应用程序一起移动。

因此,既然您声明两个用户可以编辑同一记录,那么 CLEARY 您永远不会为该给定表单打开记录锁定。系统范围的“默认”记录锁定仅设置上述表单默认值(因此您拥有的现有表单不会更改)。

接下来: [x] 使用记录级锁定打开数据库的设置是 Access 客户端设置,不与应用程序一起保存。所以这是一种访问范围的设置,不是应用程序设置,也不是随应用程序移动的设置。

因此您必须在每个客户端工作站上进行设置,或者您必须在启动代码中进行设置。

如果您无法通过更改每个工作站来更改此设置(或者您使用访问运行时),那么您可以在启动代码中使用此 VBA 来设置此功能:

  Application.SetOption "Use Row Level Locking", True

请注意,该设置在退出应用程序之前不会生效,但这实际上是一个“非”问题,因为这意味着当您第一次运行此代码时,一些用户很可能处于页面锁定模式,而其他用户则处于行锁定模式模式。大多数情况下,这不会引起什么问题。

但是,当任何用户下次启动应用程序时,他们将处于行锁定模式。

我过去也编写过自定义锁定代码。并且可以概述如何使这项工作顺利进行,但是从您到目前为止发布的内容来看,您从未打开或设置锁定,也没有为您现在拥有的任何表单正常工作。

【讨论】:

是的,我知道这一切。正如我在 OP 中所说,我最初使用的是 Access 的内置记录锁定,但这不起作用。它不仅会锁定记录,还会锁定其中的一页。我做了您在帖子中列出的所有内容:我将应用程序级别的“默认记录锁定”设置设置为“已编辑记录”并选中“使用记录级锁定打开数据库”。我还将所有表单设置为具有“已编辑记录”的表单级“记录锁定”属性。这仍然锁定了一个页面,而不是一个记录。在放弃该方法之前,我对其进行了广泛的测试。 这并不重要,因为 Access 的记录锁定在他们开始更改记录之前不会启动,这是不好的。他们可以打开记录并浏览它(无需进行更改),然后其他人可以打开它,现在两个人打开它,一旦发生这种情况,我已经搞砸了。有几种方法可以修改与该记录相关的值,一旦打开该记录的最后一个人保存它,这些值就会被覆盖。所以即使我能解决页面锁定问题,也无济于事。 我需要一种即时的方法来锁定记录,以便连接到该数据库的每个其他用户都知道其他人已经打开了它。他们是否进行了任何更改都没关系。一旦它打开,我需要它锁定。 我不确定如何更清楚。 Access 的内置记录锁定在他们开始编辑记录之前不会“启动”,而不是在他们打开表单时,因此它不是即时的。该记录锁定还锁定了一个页面,而不仅仅是一条记录,我已经对此进行了广泛的测试。正如我在 OP 中提到的,我目前拥有的是一个字段,第一个打开表单的用户使用他们的用户名填充。这让其他所有人都知道谁“锁定”了它。之后尝试打开它的任何人都可以以只读方式查看它。 我必须解决的问题是,让其他用户知道谁锁定了它的字段的数量不是即时的。其他机器上的用户需要几秒钟才能“看到”该字段中的用户名,在此期间,其他用户可以打开表单,就好像它没有被锁定一样。我曾考虑为锁定创建一个单独的表,希望这将解决我的“滞后”问题,即该字段没有立即更新,但如果表上的单个字段在其他用户看到更新之前发生延迟,则表更新可能也是如此。无论如何,我可能会测试。【参考方案2】:

好的,我终于弄清楚了导致此问题的所有问题并制定了解决方案。

这个问题是多方面的,所以我将分别讨论这些问题:

第一个问题:我的自定义锁不是即时的。即使我正在使用事务,在锁定后的几秒钟内,用户仍然可以同时访问相同的记录。我正在使用 CurrentDb.Execute 更新记录和 Workspaces(0).BeginTrans 进行交易。出于某种原因(尽管微软从这里保证相反:https://msdn.microsoft.com/en-us/library/office/ff197654.aspx),问题是使用 Workspaces 对象时事务无法正常工作。当我切换到 DBEngine.BeginTrans 时,锁定是即时的,解决了我的直接问题。

具有讽刺意味的是,我几乎总是使用 DBEngine 进行交易,但这次无缘无故地使用了 Workspaces,所以这显然是一个糟糕的举动。

第二个问题:我必须首先使用自定义锁定的原因是因为记录级锁定没有按预期工作(尽管配置正确)。它仍在使用页面级锁定。这是由于我从这里使用的性能技巧:https://msdn.microsoft.com/en-us/library/dd942824%28v=office.12%29.aspx?f=255&MSPPError=-2147217396

其中的诀窍是打开与包含链接表的数据库的连接,从而加快链接表的操作。问题是 OpenDatabase 方法与记录级锁定不兼容,因此它使用页面级锁定打开数据库,并且由于第一个打开数据库的用户确定了它的锁定级别(如此处所述:https://msdn.microsoft.com/en-us/library/aa189633(v=office.10).aspx),所有随后的连接被强制到页面级。

第三个问题: 我的问题是我的表单不只是简单的绑定到单个表格的表单。它们打开单个记录(不允许用户导航)并提供多个功能,允许用户进行修改,这些修改会影响与他们正在编辑的记录相关的其他表中的其他记录(通过组合框和弹出表单以及什么不是)。结果,我不能允许两个人同时打开同一个记录,因为这给用户留下了太多机会来查看彼此的更改。因此,即使我删除了 OpenDatabase 性能技巧,我仍然必须在他们打开表单后立即强制表单变脏,以便记录立即锁定并且没有其他人可以打开它。我不知道这是否会像我的自定义锁定一样即时,并且还没有测试过这方面。

无论如何,我需要在用户打开记​​录时立即锁定它,现在我决定继续使用我的自定义锁定(带有事务修复)。如果发现其他问题导致这不太理想,我可以尝试删除 OpenDatabase 技巧并切换到 Access 的内置锁定,并在打开每条记录时强制立即锁定它。

【讨论】:

请注意,通过行锁定“打开数据库”不会改变,无论您使用什么代码以及如何打开数据库(包括您的持久连接)。配置后,设置不会以任何方式更改锁定功能。设置简单强制通过记录集或 SQL 更新进行 ANY 和 ALL 编辑,以将一条记录扩展到整个页面大小(因此您伪造行锁定)。设置不会改变锁定的工作方式,但会强制所有编辑将任何记录扩展到整个页面大小——即使不使用锁定也是如此。因此,此设置是一个巨大的臃肿来源,但会导致所有用户编辑收到他们自己的页面。 我真的不确定你在说什么。 这篇文章是为了更正您关于页面锁定的声明。 ACE 引擎将如何使用数据库页面的记录“大小”编辑方式并不重要,也不会改变。您声明使用持久连接打开数据库,该连接禁用或导致行锁定不起作用 - 事实并非如此。所以我只是在这里更正了你在公共场合的一些错误、不正确和误导性的陈述。因此,您在此处的声明中有错误,这会误导公众。因此,即使您根本不使用任何锁定 - 选中行锁定框也会将所有编辑扩展到一个页面大小。 然而,在应用程序加载时使用 OpenDatabase 打开持久连接确实会强制对此后打开的所有表单进行页面锁定,即使您打开了记录锁定也是如此。你可以自己测试一下。 我很困惑你当时试图纠正我的答案的哪一部分,因为听起来你现在在说同样的事情。如果您创建一个数据库对象并使用 OpenDatabase 实例化它(这是我链接到的页面中的性能技巧所说的),它将对同一数据库中链接表的所有后续连接施加页面级锁定。跨度> 【参考方案3】:

您可以使用这里描述的方法:

Handle concurrent update conflicts in Access silently

处理您的锁定字段。

【讨论】:

不幸的是,这不起作用,因为它依赖于 EditMode 属性。这不会因为其他人在另一台机器上编辑它而阻止其他用户打开表单(甚至进行编辑)。最初,当我尝试实现“锁定”时,我会通过调用 rs.Edit 来运行测试,如果这引发了错误,那么我知道其他人正在编辑记录。问题是在他们对其中一个字段进行更改之前,记录不会被锁定以进行编辑,而不是在他们第一次打开表单时。 即使在第一次打开记录的机器上手动调用 rs.Edit 也不会真正将其置于编辑模式(他们必须对字段进行物理更改)。因此,如果他们只是浏览一下记录而不进行更改,其他人可以在没有 rs.Edit 在另一台机器上引发异常的情况下打开它。一旦两个人打开了相同的记录,我就注定要失败(表单上有许多功能允许他们修改与他们正在查看的记录相关的其他表中的记录)。 另外,这让我回到了无法使用 Access 的内置记录锁定的原始问题,因为它锁定了记录的整个“页面”,而不仅仅是单个记录本身(意味着几个其他未被编辑的记录也将无法访问)。 如果您设置行锁定,访问不应锁定页面。正如我在下面的回复中指出的那样,如果表单上的锁定设置设置正确,那么其他用户将无法开始编辑该记录。 是的,我只是想到了将记录标记为正在编辑的想法。 @AlbertD.Kallal 的广泛解释应该为您提供答案。【参考方案4】:

由于 Access 无法轻松锁定记录,我想知道您是否要添加一个带有锁定记录条目的表,即使它是“胶带、汤罐和衣架”,是否会解决问题解决方案:您创建一个“Locked_Record”表,其中包含 2 个字段 a) 正在更新的记录 ID 和 b) 更新该记录的人员的用户名。该表将准确控制谁拥有,因此可以编辑什么记录。您的表单将有一个搜索字段,当输入搜索词并按下“Enter”时,表单将通过在数据中查找记录并在 Locked_Record 表中查找来搜索记录。如果在 Locked_Record 表中找到,则您的用户会收到一条错误消息,指出“记录已在使用中”并显示谁拥有该记录。如果在数据中未找到,则显示相应的消息。如果在数据中找到但在 Locked_Record 表中没有找到,则将创建 Locked_Record 条目,然后用户将获得显示在表单中的数据。此时没有其他人可以编辑该记录。当用户完成更新时,要么用户需要按下“完成更新”按钮,要么必须关闭表单。无论哪种方式,Locked_Record 条目都将被删除,以便其他人可以使用该记录。如果记录所有者没有关闭表格或没有按下按钮,那么这是一个培训问题。此方法可能是多个实体的用户,例如客户、员工、部门等。您只需确保您的应用程序和数据库已设置,因此使用的任何可能锁定其他表的子表单只会影响该记录中的条目其他表。

【讨论】:

【参考方案5】:

我知道这有点旧,但这里的信息启发我使用以下内容。基本上,me.txtApplication 是绑定表单上的一个文本框。表单与表格绑定,并设置在属性部分锁定已编辑的记录。除了触发编辑锁定并立即撤消更改之外,此代码不会做任何事情。如果另一个用户尝试加载相同的记录,它将尝试执行相同的编辑,触发错误,然后移动到下一条记录或开始新记录,而用户并不聪明。

'Lock current record with edit-level lock by editing and removing the edit from a 
field.
'If record is already locked, move to next record.

On Error Resume Next
Me.txtApplication = Me.txtApplication & "-%$^$^$$@#$"
Me.txtApplication = Replace(Me.txtApplication, "-%$^$^$$@#$", "")

If Err.Number = -2147352567 Then
    If Me.CurrentRecord < Me.Recordset.RecordCount Then
        DoCmd.GoToRecord , , acNext
    Else
        MsgBox "No available records.", vbOKOnly, "No Records"
        DoCmd.GoToRecord , , acNewRec
        '[If the condition is not true, then we are on the last record, so don't go 
to the next one]
    End If
End If

End Sub

【讨论】:

以上是关于在多用户访问环境中即时“锁定”记录的主要内容,如果未能解决你的问题,请参考以下文章

如何锁定访问子表单中的记录

MySQL伪事务和性能

安全地使用 auto_ptr 交换对象而不锁定在多线程环境中?

SQLServer之锁简介

Neo4j锁定策略是否阻止用户访问在同一时刻重新定位的节点?

C# 在多线程环境中,进行安全遍历操作