当前位置: 首页 > news >正文

【开源库学习】libodb库学习(十一)

12 乐观并发

  • 只要我们在单个数据库事务中执行与特定应用程序事务对应的所有数据库操作,ODB事务模型(第3.5节,“事务”)就保证了一致性。也就是说,如果我们在数据库事务中加载一个对象并在同一事务中更新它,那么我们可以保证我们在数据库中更新的对象状态与我们加载的状态完全相同。换句话说,在这些加载和更新操作之间,另一个进程或线程不可能修改数据库中的对象状态。

  • 在本章中,我们使用术语应用程序事务来指代应用程序需要对持久对象执行的一组操作,以实现某些特定于应用程序的功能。术语数据库事务是指在ODB begin()commit()调用之间执行的一组数据库操作。到目前为止,我们基本上将应用程序事务和数据库事务视为同一件事。

  • 虽然此模型易于理解和使用,但它可能不适合具有长应用程序事务的应用程序。这种情况的典型示例是在加载对象和更新对象之间需要用户输入的应用程序事务。这样的操作可能需要任意长的时间才能完成,在单个数据库事务中执行它将消耗数据库资源,并阻止其他进程/线程长时间更新对象。

  • 这个问题的解决方案是将长期存在的应用程序事务分解为几个短期存在的数据库事务。在我们的示例中,这意味着在一个数据库事务中加载对象,等待用户输入,然后在另一个数据库交易中更新对象。例如:

unsigned long id = ...;
person p;{transaction t (db.begin ());db.load (id, p);t.commit ();
}cerr << "enter age for " << p.first () << " " << p.last () << endl;
unsigned short age;
cin >> age;
p.age (age);{transaction t (db.begin ());db.update (p);t.commit ();
}
  • 如果我们只有一个进程/线程可以更新对象,这种方法效果很好。然而,如果我们有多个进程/线程修改同一个对象,那么这种方法就不再保证一致性。考虑一下在上面的例子中,如果在我们等待用户输入的同时,另一个进程更新了该人的姓氏,会发生什么。由于我们在发生此更改之前加载了对象,因此我们版本的人员数据仍将使用旧名称。一旦我们收到用户的输入,我们就会继续更新对象,用新的(正确)覆盖旧的年龄,用旧的(不正确)覆盖新的名称。

  • 虽然无法在由多个数据库事务组成的应用程序事务中恢复一致性保证,但ODB提供了一种称为乐观并发的机制,允许应用程序检测并可能从这种不一致中恢复。

  • 本质上,乐观并发模型检测数据库中当前对象状态与加载到应用程序内存时的状态之间的不匹配。这种不匹配意味着对象被另一个进程或线程更改。有几种方法可以实现这种状态不匹配检测。目前,ODB使用对象版本控制,而未来可能会支持其他方法,如时间戳。

  • 为了用乐观并发模型声明一个持久类,我们使用optimistic pragma(第14.1.5节,“乐观”)。我们还使用version pragma(第14.4.16节,“version”)来指定哪个数据成员将存储对象版本。例如:

#pragma db object optimistic
class person
{...#pragma db versionunsigned long version_;
};
  • 版本数据成员由ODB管理。当对象被持久化时,它被初始化为1,每次更新时递增1。ODB不使用0版本值,应用程序可以将其用作特殊值,例如,表示对象是瞬态的。请注意,为了使乐观并发正常工作,在使对象持久化或从数据库加载它之后,应用程序不应修改版本成员,直到从数据库中删除此对象的状态。为了避免对version成员的任何意外修改,我们可以将其声明为const,例如:
#pragma db object optimistic
class person
{...#pragma db versionconst unsigned long version_;
};
  • 当我们调用database::update()函数(第3.10节,“更新持久对象”)并传递一个状态过时的对象时,会抛出odb::object_changed异常。此时,应用程序有两个恢复选项:它可以中止并可能重新启动应用程序事务,也可以从数据库中重新加载新的对象状态,重新应用或合并更改,并再次调用update()。请注意,中止在多个数据库事务中执行更新的应用程序事务可能需要还原已提交到数据库的更改。因此,如果所有更新都在应用程序事务的最后一个数据库事务中执行,则此策略效果最佳。这样,只需回滚最后一个数据库事务,就可以恢复更改。

  • 以下示例显示了如何使用第二个恢复选项重新实现上述事务:

unsigned long id = ...;
person p;{transaction t (db.begin ());db.load (id, p);t.commit ();
}cerr << "enter age for " << p.first () << " " << p.last () << endl;
unsigned short age;
cin >> age;
p.age (age);{transaction t (db.begin ());try{db.update (p);}catch (const object_changed&){db.reload (p);p.age (age);db.update (p);}t.commit ();
}
  • 在上面的代码片段中需要注意的一点是,第二次update()调用不能抛出object_changed异常,因为我们正在重新加载对象的状态并在同一个数据库事务中更新它。

  • 根据应用程序采用的恢复策略,更新失败的应用程序事务可能比成功的事务贵得多。因此,乐观并发最适合低到中等争用级别的情况,在这种情况下,大多数应用程序事务在没有更新冲突的情况下完成。这也是为什么这种并发模型被称为乐观的原因。

  • 除了更新,当我们从数据库中删除对象时,ODB还会执行状态不匹配检测(第3.11节,“删除持久对象”)。要理解为什么这很重要,请考虑以下应用程序事务:

unsigned long id = ...;
person p;{transaction t (db.begin ());db.load (id, p);t.commit ();
}string answer;
cerr << "age is " << p.age () << ", delete?" << endl;
getline (cin, answer);if (answer == "yes")
{transaction t (db.begin ());db.erase (p);t.commit ();
}
  • 再次考虑一下,如果在我们等待用户输入的同时,另一个进程或线程通过更改人的年龄来更新对象,会发生什么。在这种情况下,用户根据特定年龄做出决定,而我们可能会删除(或不删除)一个年龄完全不同的对象。以下是我们如何使用乐观并发来解决这个问题:
unsigned long id = ...;
person p;{transaction t (db.begin ());db.load (id, p);t.commit ();
}string answer;
for (bool done (false); !done; )
{if (answer.empty ())cerr << "age is " << p.age () << ", delete?" << endl;elsecerr << "age changed to " << p.age () << ", still delete?" << endl;getline (cin, answer);if (answer == "yes"){transaction t (db.begin ());try{db.erase (p);done = true;}catch (const object_changed&){db.reload (p);}t.commit ();}elsedone = true;
}
  • 请注意,只有当我们通过将对象实例传递给erase()函数删除对象时,才会执行状态不匹配检测。如果我们想删除一个具有乐观并发模型的对象,而不管它的状态如何,那么我们需要使用erase()函数来删除给定id的对象,例如:
{transaction t (db.begin ());db.erase (p.id ());t.commit ();
}
  • 最后,请注意,对于具有乐观并发模型的持久类,如果数据库中没有这样的对象,则update()函数和接受对象实例作为其参数的erase()函数都不再抛出object_not_persistent异常。相反,这种情况被视为对象状态的变化,并抛出object_changed异常。

  • 有关如何使用乐观并发的完整示例代码,请参阅odb-examples中的optimistic 示例。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【ROS2】演示:为有损网络使用服务质量设置
  • pytest使用
  • 2024年网络安全焦点:新兴威胁与防御技术创新
  • SQL Server 设置端口
  • 记录使用el-form的resetFields时遇到的表单数据回显失败的问题,去除nextTick解决
  • C#初级——条件判断语句、循环语句和运算符
  • 文件系统中元数据的隐患——缓存
  • prompt面试三道题
  • mysql的主从复制和读写分离
  • Java二十三种设计模式-代理模式模式(8/23)
  • 正点原子imx6ull-mini移植文件系统
  • 视频加密软件哪个好?怎么进行视频文件加密?
  • pyenv-win | python版本管理,无需卸载当前版本
  • 上传文件传参 pc端vue的formData
  • Jenkins集成JDK、git、gitee、maven逐步实现自动拉取,自动部署,自动启动
  • 【翻译】babel对TC39装饰器草案的实现
  • 【刷算法】求1+2+3+...+n
  • angular2 简述
  • CSS 三角实现
  • es6--symbol
  • HomeBrew常规使用教程
  • Map集合、散列表、红黑树介绍
  • QQ浏览器x5内核的兼容性问题
  • - 概述 - 《设计模式(极简c++版)》
  • 基于组件的设计工作流与界面抽象
  • 将回调地狱按在地上摩擦的Promise
  • 适配mpvue平台的的微信小程序日历组件mpvue-calendar
  • 数组的操作
  • 消息队列系列二(IOT中消息队列的应用)
  • 一、python与pycharm的安装
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • linux 淘宝开源监控工具tsar
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • $GOPATH/go.mod exists but should not goland
  • (1)svelte 教程:hello world
  • (12)Hive调优——count distinct去重优化
  • (二)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (附源码)计算机毕业设计大学生兼职系统
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (原創) 如何優化ThinkPad X61開機速度? (NB) (ThinkPad) (X61) (OS) (Windows)
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • (转载)深入super,看Python如何解决钻石继承难题
  • ***利用Ms05002溢出找“肉鸡
  • .NET Core跨平台微服务学习资源
  • .net framework 4.0中如何 输出 form 的name属性。
  • .Net(C#)自定义WinForm控件之小结篇
  • .NET设计模式(11):组合模式(Composite Pattern)
  • .net下的富文本编辑器FCKeditor的配置方法
  • /var/log/cvslog 太大
  • [ C++ ] STL---仿函数与priority_queue
  • [1525]字符统计2 (哈希)SDUT
  • [AMQP Connection 127.0.0.1:5672] An unexpected connection driver error occured
  • [Android View] 可绘制形状 (Shape Xml)
  • [ASP.NET 控件实作 Day7] 设定工具箱的控件图标