我有简单的表格:
create table tx_test
(
i integer,
constraint i_unique unique (i)
);
此外,我还有以事务方式(jdbc-insert-i-tx
)对此表执行插入的函数。 timeout-before
,timeout-after
,label
参数仅用于帮助重现问题并简化调试。
(defn jdbc-insert-i [con i]
(jdbc/db-do-prepared-return-keys
con
;; db-do-prepared-return-keys can itself do updates within tx,
;; disable this behaviour sice we are handling txs by ourselves
false
(format "insert into tx_test values(%s)" i)
[]))
(defn jdbc-insert-i-tx [db-spec timeout-before timeout-after label i]
(jdbc/with-db-transaction [t-con db-spec :isolation :serializable]
(and timeout-before
(do
(println (format "--> %s: waiting before: %s" label timeout-before))
(do-timeout timeout-before)))
(let [result (do
(println (format "--> %s: doing update" label))
(jdbc-insert-i t-con i))]
(and
timeout-after
(do
(println (format "--> %s: waiting after: %s" label timeout-after))
(do-timeout timeout-after)))
(println (format "--> %s about to leave tx" label))
result)))
超时是使用manifold
的延迟实现的,但这与此问题无关:
(defn do-timeout [ms]
@(d/timeout! (d/deferred) ms nil))
在单独交易中我同时插入两个相同值后。我希望在任何事务提交之前执行这些更新。因此,我正在设置超时,因此第一个事务在执行更新之前不会等待,但在执行提交之前等待1秒,而第二个事务在执行更新之前等待半秒,但在提交之前不等待。 / p>
(let [result-1 (d/future (jdbc-insert-i-tx db-spec nil 1000 :first 1))
result-2 (d/future (jdbc-insert-i-tx db-spec 500 nil :second 1))]
(println @result-1) ;; => {:i 1} ;; this transaction finished successfully
(println @result-2) ;; => no luck, exception
)
执行上面的代码后,我得到以下调试输出:
--> :first: doing update
--> :second: waiting before: 500
--> :first: waiting after: 1000
--> :second: doing update
--> :first about to leave tx
显然第二笔交易没有完成。这是因为例外:
PSQLException ERROR: duplicate key value violates unique constraint "i_unique"
Detail: Key (i)=(1) already exists. org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse (QueryExecutorImpl.java:2284)
然而,异常与序列化错误(实际上我所期待的)无关,但是通知了约束违规。它也是在执行jdbc/db-do-prepared-return-keys
期间发生的,而不是在Connection.commit
的调用中发生的。所以,似乎第二次交易可以某种方式“看到”第一次交易所做的更新。这对我来说完全出乎意料,因为隔离级别设置为最高级别::serializable
。
这种行为是否正确?或者在某处错了?
如果这有帮助,我正在使用以下库:
[com.mchange/c3p0 "0.9.5.2"]
[org.postgresql/postgresql "9.4.1208"]
[org.clojure/java.jdbc "0.3.7"]
答案 0 :(得分:3)
您遇到与此问题INSERT and transaction serialization in PostreSQL
基本相同的情况这样的行为是正确的,因为Postgresql阻止执行带有索引字段的行的后续并发突变,直到第一个突变完全结束(成功或有错误),所以当你的第二个JDBC插入时#34;触摸" DB第一个事务已经完成。有关此行为的一些信息可以在here找到。