5

我正在开发一个连接到 oracle 的网络应用程序。我们在 oracle 中有一个表,其中有一列“已激活”。任何时候只有一行可以将此列设置为 1。为了强制执行这一点,我们一直在 Java 中使用 SERIALIZED 隔离级别,但是我们遇到了“无法序列化事务”错误,并且无法找出原因。

我们想知道 READ COMMITTED 的隔离级别是否可以完成这项工作。所以我的问题是:

如果我们有一个涉及以下 SQL 的事务:

SELECT *
FROM MODEL;

UPDATE MODEL
SET ACTIVATED = 0;

UPDATE MODEL
SET ACTIVATED = 1
WHERE RISK_MODEL_ID = ?;

COMMIT;

鉴于可能同时执行多个这些事务,是否有可能多个 MODEL 行将激活标志设置为 1 ?

任何帮助,将不胜感激。

4

3 回答 3

3

您的解决方案应该有效:您的第一次更新将锁定整个表。如果另一个事务没有完成,更新将等待。您的第二次更新将保证只有一行的值为 1,因为您正在锁定表(但它不会阻止 INSERT 语句)。

您还应该确保存在的行RISK_MODEL_ID(或者在事务结束时您将有零行,其值为“1”)。

为防止并发 INSERT 语句,您将锁定表(在 EXCLUSIVE 模式下)。

于 2009-09-22T22:28:37.360 回答
3

您可以考虑使用唯一的、基于函数的索引来让 Oracle 处理只有一行且激活标志设置为 1 的约束。

CREATE UNIQUE INDEX MODEL_IX ON MODEL ( DECODE(ACTIVATED, 1, 1, NULL));

这将停止将标志设置为 1 的不止一行,但并不意味着总是有一行将标志设置为 1。

于 2009-09-22T22:33:52.340 回答
2

如果您想要确保一次只能运行一个事务,那么您可以使用该FOR UPDATE语法。由于您有一个需要锁定的单行,因此这是一种非常有效的方法。

declare
    cursor c is 
        select activated
        from model
        where activated = 1
        for update of activated;
    r c%rowtype;
begin
    open c;
    --  this statement will fail if another transaction is running
    fetch c in r;
    ....
    update model
    set activated = 0
    where current of c;

    update model
    set activated = 1
    where risk_model_id = ?;

    close c;

    commit;
end;
/

commit释放锁 。

默认行为是等到该行被释放。否则我们可以指定NOWAIT,在这种情况下,任何其他尝试更新当前活动行的会话将立即失败,或者我们可以添加一个WAIT带有轮询时间的选项。 NOWAIT是选择绝对避免挂起风险的选项,它也让我们有机会通知用户其他人正在更新表格,他们可能想知道。

这种方法比更新表中的所有行更具可扩展性。使用 WW 显示的基于函数的索引来强制执行只有一行可以具有 ACTIVATED=1 的规则。

于 2009-09-23T05:46:32.230 回答