我的Java项目使用纯JDBC与OracleDB交互(12节)。事务隔离级别是读提交的。
我有一个高度非规范化的表,它将实体存储在一组行中。我不能改变这个。不幸的是,这张桌子必须保持这样,原因与我无关。
+------+------+---------+
| date | hash | ....... |
+------+------+---------+
| date | xyz | ....... |
| date | xyz | ....... |
| date | xyz | ....... |
我有两个列标识一个实体-日期和散列。由于每个实体都存储为多行,因此这些列不是真正唯一的,也不是主键,而是仅索引列。尽管如此,我还是想实施一种“独特性”,即在那时,只有一个实体存在,不管它是由多少行组成的。
这样的实体一天可以更新两次,结果是不同的值,但行数也不同。
为了实现这一切,每次更新实体时,我都会在单个事务中执行两个或多个查询:
delete from "table" where "date" = ? and "hash" = ?
insert into "table" values (?, ?, .....)
insert into "table" ....
... -- as many inserts as needed to store whole entity
对于应用程序的单个实例来说,这很好。不幸的是,我有两个实例同时工作,试图在几乎相同的时间存储完全相同的数据(它们只是主备份实例,但备份也在持续-这一点我也没有影响)。
如果这是规范化表,解决方案是使用merge语句,但在这里不起作用。
我目前的解决方案:
到目前为止,我试图做的是再添加一列,即实例持久化的id,然后使用select作为数据源执行insert语句,并放置条件以选择此日期/哈希和app id必须没有数据,否则select不提供要插入的数据。
我原以为可以,但显然不行。我仍然看到复制品。我认为这是因为两个事务首先执行删除,但仍然看不到其他事务提交的数据,因此只能自己执行插入。然后“提交”就是执行和繁荣。两个事务都插入其数据。
我考虑的其他方法:
我想乐观锁定也不会起作用,因为在最终版本检查时,两个事务仍然可以认为版本没有更改,而它们实际上是由两个事务同时更改的,并且将以这种方式提交。
我知道我可以将事务隔离切换到可序列化,但这也不是完美的(首先,oracle驱动程序不会序列化查询,但会采用乐观的方法,在并发修改时出错,我不喜欢这样,它是一个“异常编程”范式,一个反模式,第二个缺点当然是表现。
有没有其他办法解决这个问题?
最佳答案
我读到你的要求是:
数据库结构无法更改
两个应用程序必须同时更新完全相同的数据
乐观锁定已关闭,因为它可能会导致错误或性能降低
悲观锁定与乐观锁定的原因相同
似乎最重要的不是你在改变什么数据,而是你在阅读什么数据。您需要一种方法来确定您的系统的用户应该为哪些数据提供服务(我不知道这些应用程序只是维护数据还是同时使用数据)。
我假设您当前的数据服务查询是这样的:
select * from table where date = :1 and hash = :2
如果您将其更改为以下内容,那么您将始终选择最新的数据,如果有重复的时间,您将选择第一个应用程序(基本上是随机的-更改为您想要的任何顺序)
select *
from ( select t.*
, rank() over (partition by hash
order by date desc, app_id desc) as rnk
from table t
)
where rnk = 1
你可以把这个放在视野里吗?
然后,基本上可以在一个表中运行两个单独的表。您可以使用merge等,并可以将delete/insert语句更改为:
merge into table o
using (select :1, :2 ... ) n
on ( o.date = n.date
and o.hash = n.hash
and o.app_id = n.app_id
)
when matched then
update
set ...
when not matched then
insert (...
commit;
delete from table
where date < :1
and hash = :2
commit;
在这里,您使用的是来自merge语句的相同日期和散列。如果删除失败,您并不介意-因为您已经更改了select查询,所以不会选择错误的数据。
就我个人而言,我承认你的一个要求必须改变。
如果有添加其他应用程序的计划,我将接受性能降低,并使用排队机制对该表连续执行更新。
如果没有添加其他应用程序的计划,现在就采用简单的方法,开始使用锁定策略(不是很好),并只处理一些已知的错误。