我试图用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,但我始终观察到相同的行为。
编辑:我用每个线程的不同连接重写了我的代码,结果是一样的。
答案 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
。这需要一个行锁,这意味着在您提交或回滚之前,没有其他事务可以修改该行。