介绍
在我以前的文章中,我演示了为什么乐观锁定是应用程序级事务唯一可行的解决方案。 乐观锁定要求版本列可以表示为:
- 物理时钟(从系统时钟获取的时间戳值)
- 逻辑时钟(递增的数值)
本文将说明为什么逻辑时钟更适合于乐观锁定机制。
系统时间
系统时间由操作系统内部时钟算法提供。 可编程间隔计时器定期发送中断信号(频率为1.193182 MHz)。 CPU接收时间中断,并增加一个滴答计数器。
Unix和Window都将时间记录为自预定义的绝对时间参考(纪元)以来的滴答数。 操作系统时钟分辨率从1ms(Android)到100ns(Windows)和1ns(Unix)不等。
单调时间
要订购事件,版本必须单调前进。 虽然增加本地计数器是单调函数,但系统时间可能并不总是返回单调时间戳。
Java有两种获取当前系统时间的方法。 您可以使用:
- System#currentTimeMillis() ,它提供自Unix纪元以来经过的毫秒数。此方法不会给您单调的时间结果,因为它返回的墙上时钟时间易于向前和向后调整(如果NTP用于系统时间)同步) 。对于单调的currentTimeMillis,你可以检查彼得Lawrey的解决方案或Bitronix事务管理单调时钟 。
- System#nanoTime() ,返回自任意选择的时间参考以来经过的纳秒数
此方法尝试使用当前的操作系统单调时钟实现,但是如果找不到单调时钟,它将回到挂钟时间 。
参数1:系统时间并非总是单调递增。
数据库时间戳精度
SQL-92标准将TIMESTAMP数据类型定义为YYYY-MM-DD hh:mm:ss。 小数部分是可选的,每个数据库都实现特定的时间戳记数据类型:
关系数据库管理系统 | 时间戳分辨率 |
---|---|
甲骨文 | TIMESTAMP(9)最多可以使用9个小数位(纳秒精度)。 |
微软SQL | DATETIME2的精度为100ns。 |
的MySQL | MySQL 5.6.4增加了对TIME,DATETIME和TIMESTAMP类型(例如TIMESTAMP(6))的微秒精度支持。 以前的MySQL版本会丢弃所有时间类型的小数部分。 |
PostgreSQL的 | TIME和TIMESTAMP类型都具有微秒精度。 |
DB2 | TIMESTAMP(12)最多可以使用12个小数位(皮秒精度)。 |
关于持久性时间戳,大多数数据库服务器至少提供6个小数位。 MySQL用户一直在等待更精确的时间类型,而5.6.4版本最终增加了微秒精度。
在5.6.4之前的MySQL数据库服务器上,更新可能会在任何给定的每秒生命周期内丢失。 这是因为所有更新同一数据库行的事务都将看到相同的版本时间戳(指向当前运行秒的开始)。
参数2:5.6.4之前的MySQL版本仅支持秒精度时间戳。
处理时间不是那么容易
递增本地版本号始终更安全,因为此操作不依赖任何外部因素。 如果数据库行已包含更高版本号,则您的数据已过时。 就这么简单。
另一方面,时间是最复杂的维度之一。 如果您不相信我,请查看的夏令时处理注意事项 。
Java用了8个版本终于有了成熟的Date / Time API 。 跨应用程序层处理时间(从JavaScript到Java中间件再到数据库日期/时间类型)使情况变得更糟。
论据3:处理系统时间是一项艰巨的任务。 您必须处理leap秒 , 夏令时 , 时区和各种时间标准 。
分布式计算的经验教训
乐观锁定与事件排序有关,因此自然而然地,我们只对之前发生的关系感兴趣。
在分布式计算中,逻辑时钟优于物理时钟(系统时钟),因为网络时间同步意味着可变的延迟。
序列号版本控制类似于Lamport时间戳算法 ,每个事件仅增加一个计数器。
虽然Lamport时间戳是为多个分布式节点事件同步定义的,但数据库乐观锁定要简单得多,因为只有节点(数据库服务器)上的所有事务都同步了(来自并发客户端连接)。
论据4:分布式计算比物理时钟更喜欢逻辑时钟,因为无论如何我们只对事件排序感兴趣。
结论
起初使用物理时间似乎很方便,但事实证明这是一种幼稚的解决方案。 在分布式环境中,最不可能实现完美的系统时间同步。 总而言之,在实现乐观锁定机制时,您应该始终偏爱逻辑时钟。
翻译自: https://www.javacodegeeks.com/2014/10/logical-vs-physical-clock-optimistic-locking.html