【问题标题】:Running SELECT + UPDATE in a transaction has different results than UPDATE alone?在事务中运行 SELECT + UPDATE 与单独 UPDATE 的结果不同?
【发布时间】:2018-03-14 17:15:33
【问题描述】:

我正在尝试使用 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

当我运行此代码时,t 表的 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,但我始终观察到相同的行为。

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

【问题讨论】:

  • 您面临的问题是什么?
  • '我有 n 个线程(共享一个连接)'并且以某种随机顺序触发 connection.commit()?!这行不通。获取一个连接池,使用适当的事务划分并使用某种形式的 - 也许是乐观的 - 锁定。
  • 那是什么意思'共享同一个连接'呢?
  • 是的,这个坏主意。如果您现在有单独的连接,则隔离级别的设置可能会起作用。还是SERIALIZABLE吗?
  • 您的exec() 是做什么的?它如何替换 {v}+1 部分?但其他事务可以轻松更新您事务的selectupdate 语句之间的表。

标签: java postgresql jdbc concurrency transactions


【解决方案1】:

但是由于第一个代码在事务中

没有。事务并不是让您忽略并发性的魔法。他们就数据何时可见、锁定等做出具体的、定义明确的承诺,但它们不会让并发性消失。见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 尝试更改它。

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

【讨论】:

  • 我也尝试了 SERIALIZABLE 隔离级别,但它似乎不起作用。我仍然有相同的结果。唯一真正的区别是现在偶尔会出现两个错误由于并发更新而无法序列化访问和一些死锁。当然,这些错误是意料之中的。
  • @heapOverflow 这……令人惊讶。也许您应该发布SSCCE,因为很难确切地看到您在做什么。
猜你喜欢
  • 1970-01-01
  • 2016-10-28
  • 2011-06-28
  • 2013-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多