WebObjects/EOF/使用 EOF/乐观锁
请注意,本节介绍了 EOF 在数据库层使用的锁定策略,而不是 EOEditingContext 锁定,后者在堆栈中处于更高的位置。有关 EOEditingContext 锁定的信息,请参见上下文和数据库锁定。
数据库 API 可以使用两种主要的锁定方式:悲观锁定和乐观锁定。悲观锁定通过在第一个参与者开始进行更改时显式锁定对该行的访问,从而主动阻止两个参与者更新同一行(有效地锁定其他人,直到更改完成)。乐观锁定允许参与者在没有显式锁定的情况下对资源进行更改,假设成功,只有在更改确实导致冲突时才会失败。EOF 在锁定数据时采用乐观方法。
举个例子,来说明 EOF 中的乐观锁定是如何工作的,假设你有一个 Person EO,PersonID 为 123,名字为 "Bob",姓氏为 "Roberts"。如果你将名字更改为 "Robert" 并在你的编辑上下文中执行 saveChanges(),你会看到以下 SQL 发送到你的数据库
update Person set FirstName = 'Robert' where PersonID = 123 and FirstName = 'Bob' and LastName = 'Roberts'
乐观锁定的关键在于更新语句的 where 子句。如果你是唯一一个更改此 EO 的人,则此更新命令将返回更新了 1 行,这是 EOF 预期的成功结果。但是,假设另一个用户在同一时间进行编辑并将 Bob 的姓氏更改为 'Wilson'。重新运行上面的查询,你会发现没有行被更新,因为限定符中的 LastName = 'Roberts' 子句不会匹配(数据库中的姓氏现在是 'Wilson')。这种情况在 EOF 中的结果是从 saveChanges() 抛出乐观锁定异常。
当你运行 EOModeler 时,你可能已经注意到属性视图中有一个带有锁定图标的列。此列定义了你的实体的哪些属性将用于乐观锁定。所有被选中属性都将出现在你对对象进行更新的 'where' 子句中。请注意,所有锁定列都会在更新时被检查,而不仅仅是修改属性的列。
人们在使用 EOF 时经常遇到一个问题,即使用 "不精确" 的类型作为锁定列。我所指的不精确类型是指那些在 EO 的整个生命周期中可能发生精度变化的类型,即使它没有被用户修改。最臭名昭著的数据类型是日期和浮点类型。Java 对浮点类型的精度通常与数据库的精度不匹配。结果是,当 EOF 从数据库中取出浮点数,将其放入 Float 中,并将其放置在快照中时,仅仅是将其加载到 Float 中这一行为就会改变它的值。这通常会导致锁定方面的非常奇怪的问题,即使没有并发更新,你也最终会遇到乐观锁定失败。这种情况下的建议是,如果你的数据库出现问题,请取消选中具有日期和浮点类型的属性的锁定列。这样做明显的缺点是,这些字段将不再有资格用于检测乐观锁定失败(即,该列的 "最后写入获胜",并且你的程序不会收到关于更新冲突的通知)。
mmalcolm 在 O'Reilly 2002 年 Mac OS X 大会上做了一个名为 "EOF 中缺乏冲突,或 '嘿,妈妈,有人覆盖了我的数据!'" 的演讲,并且幻灯片可以在http://conferences.oreillynet.com/presentations/macosx02/crawford_eoconflicts.pdf 上找到。
关于乐观锁定导致的数据库服务器负载增加(因为数据库必须在更新之前验证所有锁定列的值)存在一些讨论。
2006 年 1 月,在 WebObjects 5.3 上进行了一次基准测试,场景如下:
这在 2006 年 1 月的最新版本 FrontBase 上运行,使用的是 Intel iMac 上的 WO 5.3。
创建了两个相同的实体,它们具有 3 个整型、3 个布尔型、3 个字符串和 3 个日期作为属性,但其中一个锁定所有属性,另一个不锁定任何属性。在数据库中插入了 10,000 个实例,然后对每个实体的同一个属性进行更新,更新后的值相同。
对于锁定所有列的实体,完成运行需要 25753 毫秒。对于没有锁定任何列的实体,完成运行需要 7111 毫秒。在这个例子中,使用这个数据库和这个硬件,不锁定任何列的速度快了 3.6 倍。
将场景修改为锁定一个日期列,锁定列的实体完成运行需要 25574 毫秒,而未锁定列的实体完成运行需要 14125 毫秒,速度相差 1.81 倍。
这实际上取决于你的应用程序类型。如果你要进行 100,000 次更新,你可能关心多花了 190 秒(或者其他时间差)来执行更新。但是,如果你只是定期更新 "正常" 数据集,那么多花几毫秒可能无关紧要。显然,权衡是数据完整性。
数据库基准测试永远不简单,并且通常不适用于不同供应商的不同数据库,因此请将这些结果谨慎看待。在进行大规模功能牺牲之前,请始终对你的应用程序进行性能分析。
这里是最容易触发它的方法。
- 将一个对象拉到 WO 中的编辑页面。
- 现在通过后门使用命令行或其他独立于 WebObjects 的工具直接编辑数据库记录中的一些属性(这些属性在 EOModel 中有锁定图标),这些属性代表对象。
- 现在提交你的对象编辑页面。
- 现在会抛出一个乐观锁定异常,因为在你编辑对象之前从数据库中读取的原始快照与数据库中现在的值不同。
假设我们有一个表,它有一个主键 'oid' 和一个乐观锁定的属性,名为 'field1'。现在,我们获取主键为 273 的对象,它的数据库值为 "original"。
现在假设你在 WO 中的表单中将 field1 更改为 "new",并且在提交之前,假设你直接将数据库中的 field1 值更改为 "changed",那么当你提交时,EOF 生成的 SQL 类似于:UPDATE table T1 set field1 = "new" where oid = 273 and field1 = "original";
.... 此更新将失败,因为没有记录具有 oid = 273 和 field1 = "original"。存在一个具有 oid = 273 和 field1 = "changed" 的记录,但是 WO 不知道这一点,因为其他应用程序已经更改了它。
如果你在更改时记录 SQL,你会看到乐观锁定会影响所有生成的 UPDATE 语句的 WHERE 部分。