在通用SQL中同时检索(选择)或创建(插入)新行而不会发生冲突

Gly*_*yph 7 mysql sql oracle postgresql

我有一个系统,它有一个复杂的主键用于连接外部系统,还有一个快速,小的不透明主键供内部使用.例如:外部键可能是复合值 - 类似于(给定名称(varchar),系列名称(varchar),邮政编码(char)),内部键是整数("客户ID").

当我收到带有外部密钥的传入请求时,我需要查找内部密钥 - 这里是棘手的部分 - 如果我还没有一个用于给定外部ID的内部密钥,则分配一个新的内部密钥.

显然,如果我一次只有一个客户端与数据库通信,这很好. SELECT customer_id FROM customers WHERE given_name = 'foo' AND ...,INSERT INTO customers VALUES (...)如果我没有找到一个值.但是,如果有可能同时从外部系统进入许多请求,并且许多可能同时到达前所未闻中的客户,则存在竞争条件,其中多个客户端可能尝试INSERT新行.

如果我正在修改现有的行,那将很容易; 只是SELECT FOR UPDATE第一次,以获得相应的行级锁,做一个之前UPDATE.但是在这种情况下,我没有可以锁定的行,因为该行还不存在!

到目前为止,我已经提出了几个解决方案,但每个解决方案都有一些非常重要的问题:

  1. 捕获错误INSERT,从顶部重新尝试整个事务.如果交易涉及十几个客户,这是一个问题,特别是如果传入的数据可能每次都以不同的顺序讨论相同的客户.可能会陷入相互递归的死锁循环,每次冲突都发生在不同的客户身上.您可以通过重试尝试之间的指数等待时间来缓解这种情况,但这是处理冲突的缓慢而昂贵的方法.此外,这使得应用程序代码变得非常复杂,因为所有内容都需要重新启动.
  2. 使用保存点.在SELECT捕获错误之前启动保存点INSERT,然后再次回滚到保存点SELECT.保存点不是完全可移植的,它们的语义和功能在数据库之间略有不同; 我注意到的最大的区别是,有时它们似乎是窝,有时它们没有,所以如果我能避免它们会很好.这只是一个模糊的印象 - 它是不准确的吗?保存点是标准化的,还是至少实际上是一致的?此外,保存点使得在同一事务上并行执行操作变得困难,因为您可能无法确切知道您将回滚多少工作,尽管我意识到我可能只需要接受它.
  3. 获取一些全局锁,比如使用LOCK语句(oracle mysql postgres)的表级锁.这显然会减慢这些操作并导致很多锁争用,所以我宁愿避免它.
  4. 获取更精细,但数据库特定的锁.我只熟悉Postgres这样做的方式,这在其他数据库中肯定不受支持(函数甚至以" pg_" 开头),所以这又是一个可移植性问题.此外,postgres的这种方式需要我将键转换为一对整数,它可能不适合.是否有更好的方法来获取假设对象的锁?

在我看来,这必须是数据库的常见并发问题,但我没有设法找到它的大量资源; 可能只是因为我不知道规范的措辞.是否可以在任何标记数据库中使用一些简单的额外语法来执行此操作?

Har*_*arv 0

WRT 生成不透明主键,有许多选项,例如,使用 guid 或(至少对于 Oracle)序列表。WRT 确保外部键是唯一的,对列应用唯一约束。如果由于键存在而导致插入失败,请重新尝试提取。您可以使用 where not exit 或 where not in 的插入。使用存储过程可以减少往返次数并提高性能。