知识卡片
数据库会话状态的核心张力:会话数据不能过早污染已提交数据
内容
数据库会话状态每次请求都要从数据库读出需要的数据、处理后再存回数据库,其核心难题在于——会话过程中产生的中间数据是局部的、不该在整体提交之前就影响系统的其他部分。比如正在编辑一份订单,把它的中间状态存进数据库时,必须让它和其他”已确定”的订单区分开,否则它就会被误算进”可供租借的图书数量”“每日收入统计”这类查询结果里,污染那些本该只反映已提交事实的数据。区分”会话中的临时数据”和”真正的记录数据”主要有两种方案。标记字段:在每一行数据上加一个字段表明它是不是会话数据——最简单是一个isPending布尔值,更好的做法是存一个会话ID(这样能方便地按会话ID一次取出某个会话的全部数据),配套要求所有查询语句都得加上”会话ID为空”的过滤条件才能只看到真实记录(或者建一个视图来屏蔽这层判断,但视图本身也有性能代价);这个方案影响面很大,因为凡是接触这份记录数据库的代码都得理解这个会话ID字段的含义。临时表:给每张真实表各配一张结构完全相同的临时表(字段名保持一致,临时表额外加一个会话标识号字段),会话中的数据先写进临时表,等真正提交时再转移到正式表——好处是不会像标记字段那样影响到所有查询代码,代价是数据库映射代码里要加入”选哪张表”的判断逻辑,而且真实记录数据通常带有的完整性约束和审查规则在临时表里天然缺失(这是双刃剑:可以按需选择性地在临时表上补规则,也可能因为疏忽而漏掉本该有的校验)。无论用哪种方案,都还要处理会话取消或超时后的清理(找到并删除对应会话的所有数据,配合定时巡检未活动会话),以及回滚的复杂性——最简单的做法是干脆不允许会话中途取消,只在每次请求结束时增量提交部分修改,这既省事又符合用户的直觉预期。
结构图:
flowchart TB
A["数据库会话状态的核心矛盾"]
A --> B["会话中间数据不能污染已提交数据"]
B --> C["方案一:标记字段(如会话ID)<br/>影响所有查询语句<br/>需过滤/建视图"]
B --> D["方案二:临时表<br/>结构与正式表一致<br/>提交时转移,天然缺完整性约束"]
A --> E["回滚复杂度<br/>最简单解法:不允许会话中途取消"]
参考来源
- 位置:《企业应用架构模式》第二部分"模式"之"第17章 会话状态模式"之"17.3.1 运行机制"(源文件:_epub-src/OEBPS/Text/000208.html)
- 结论依据:原文说明"关键问题之一是会话状态是会话的局部数据,通常不能在整体提交之前影响系统的其他部分……在每个数据行中加上一个说明是否是会话数据的字段是一个办法……第二种方法是用一些单独的临时表……记录数据通常有完整性规则,而临时表中的数据没有……一个办法是不允许会话中途取消。对记录数据的修改在每次请求结束时部分地更新记录数据。这个方法很简单,也符合用户的观点",直接支撑本卡结构图。
- 原始内容:因此,当正在处理一份订单信息,而要将其中间状态存到数据库时,要把它和其他的订单分别看待。这是因为不希望在查询的结果中出现那些还没有确定的订单信息,如可供租借的图书信息和每天的收入信息。