在事务中运行SELECT + UPDATE与单独更新UPDATE的结果不同?

时间:2017-10-03 13:27:28

标签: java postgresql jdbc concurrency transactions

我试图用JDBC实现一些业务逻辑,但我不理解我得到的结果。

我有 n 个线程,每个线程执行以下伪代码:

//before threads execution
m <- exec("select x from t where id = 1")

// inside thread
conn.setAutocommit(false);
//do some stuff on the db
v <- exec("select x from t where id = 1")
exec("update t set x = {v}+1 where id=1")
conn.commit();

// after threads execution
exec("select x from t where id = 1") //result should be m+n, but it isn't

当我运行此代码时, n 表的 x 列不会像我期望的那样被 n 增加。当然,所有线程之间都存在并发问题。

如果我用以下代码替换该代码,那么它可以正常工作:

//before threads execution
m <- exec("select x from t where id = 1")

// inside thread
conn.setAutocommit(false);
//do some stuff on the db
exec("update t set x = x+1 where id=1")
conn.commit();

// after thread execution
exec("select x from t where id = 1") //result is be m+n

但是由于第一个代码在交易中,不应该等同于第二个版本吗?

我还尝试将隔离级别限制为SERIALIZABLE,但我始终观察到相同的行为。

编辑:我用每个线程的不同连接重写了我的代码,结果是一样的。

1 个答案:

答案 0 :(得分:2)

  

但是因为第一个代码在事务中

没有。事务不是神奇的,可以让你忽略并发。他们就数据何时可见,关于锁定等做出具体的,明确定义的承诺,但它们并没有使并发性消失。请参阅PostgreSQL's documentation on concurrency and isolation

在单语句操作中甚至可能存在并发效应,因此将事物简化为单个(复杂)语句不会使并发性消失。

在这种情况下,SERIALIZABLE隔离将帮助您:

CREATE TABLE sdemo
(
    id integer primary key,
    counter integer
);

INSERT INTO sdemo VALUES (1, 1);

然后

SESSION1                      SESSION2
BEGIN TRANSACTION
ISOLATION LEVEL SERIALIZABLE;
                              BEGIN TRANSACTION 
                              ISOLATION LEVEL SERIALIZABLE;
SELECT counter FROM sdemo
WHERE id = 1;
-- result is '1'
                              SELECT counter FROM sdemo
                              WHERE id = 1;
                              -- result is '1'                        

UPDATE sdemo
SET counter = 2
WHERE id = 1;
-- Succeeds

                              UPDATE sdemo
                              SET counter = 2
                              WHERE id = 1;
                              -- hangs waiting on row lock
                              -- held by session 1


COMMIT;
-- Succeeds
                              -- UPDATE finishes (succeeds)

                              COMMIT;
                              -- Aborts with
                              ERROR: could not serialize access due 
                                     to concurrent update

因为PostgreSQL检测到一个xact改变了行而另一个读取了它,然后另一个xact试图改变它。

但是,在第一个示例中,{em> 更简单select x from t where id = 1 FOR UPDATE。这需要一个行锁,这意味着在您提交或回滚之前,没有其他事务可以修改该行。