知识卡片

新键生成的四种策略

结构图卡

内容

创建新对象要分配一个新键,看似简单,实则暗藏不少麻烦,主要有四条路。自动生成域:数据库自身在插入时把某个字段自动递增成新值,最省事,但麻烦在于插入前很难提前知道这个自动生成的键值是多少——如果要插入一份带多条子项的订单,子项需要用到订单的新键作为外键,而这个值往往要等插入真正发生才能拿到,因此涉及关联对象插入的表通常不适合这种方式。数据库计数器:如Oracle的序列,通过独立的Select语句向数据库要下一个序列值,这个查询在独立事务里自动完成,不会锁住其他正在对同一张表做插入的事务,还能一次多要几个键——效果很好,但不是所有数据库都支持,也没有统一标准。GUID:在本机生成、保证跨越所有时空唯一的长串标识(利用网卡地址、纳秒级时间戳、芯片ID等拼出来),完全不用担心冲突,缺点是生成的字符串很长,人工输入/阅读不便,还可能拖累索引性能。自造键值:最朴素的办法是用SQL的max函数扫描全表拿最大键再加一,会给整张表加读锁,插入少还行,一旦和更新混着跑就很麻烦;更好的自造方案是专门的键表——一张记录”名字/下一个可用值”的表,取键时读出、递增、写回,还可以一次多领几个值减少数据库调用;关键窍门是把对键表的访问放进一个独立的短事务里,这样只锁很短的时间,即便订单插入本身回滚了,从键表领到但没用上的键号也无妨(数字资源本就充裕),还顺带解决了”内存对象一创建就想立刻拿到ID”的需求。

结构图

flowchart TB
  A["新键生成策略"]
  A --> B["自动生成域<br/>最省事,但插入前拿不到值<br/>不适合有关联对象要插入的表"]
  A --> C["数据库计数器(如Oracle序列)<br/>独立事务不阻塞其他插入<br/>无统一标准"]
  A --> D["GUID<br/>跨时空唯一,无冲突<br/>但字符串长,拖累索引/输入"]
  A --> E["自造键表<br/>读-增-写独立短事务<br/>可一次领多个减少调用"]

参考来源

- 位置:《企业应用架构模式》第二部分"模式"之"第12章 对象-关系结构模式"之"12.1.1 工作机制"(源文件:_epub-src/OEBPS/Text/000104.html) - 结论依据:原文说明"最通用的自动生成方法是声明一个自动生成域……这种策略的问题在于难以确定为键生成的是什么值……数据库计数器……序列查询是在一个独立的事务中自动进行的,这样,访问序列时不会锁住那些想要对同一个表执行插入操作的事务……GUID……生成的结果串会比较大,这可能会成为一个大问题……一个更好的办法是使用独立的键表……使用键表的方法就是读出数据行,获得数字,进行增加操作,得到新数字并把这个数字写回到数据行中",直接支撑本卡结构图。 - 原始内容:通过把对键表的访问放入独立的事务中,我们只为这个很短的事务锁住数据行……使用一个独立的事务也允许一创建内存对象就立刻得到ID,这往往是在提交业务事务之前需要的。