为什么一个Clojure JDBC可序列化事务“看到”(?)另一个事务所做的更改?

时间:2016-09-13 09:43:12

标签: postgresql jdbc clojure transactions transaction-isolation

我有简单的表格:

create table tx_test
(
  i integer,
  constraint i_unique unique (i)
);

此外,我还有以事务方式(jdbc-insert-i-tx)对此表执行插入的函数。 timeout-beforetimeout-afterlabel参数仅用于帮助重现问题并简化调试。

(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"]

1 个答案:

答案 0 :(得分:3)

您遇到与此问题INSERT and transaction serialization in PostreSQL

基本相同的情况

这样的行为是正确的,因为Postgresql阻止执行带有索引字段的行的后续并发突变,直到第一个突变完全结束(成功或有错误),所以当你的第二个JDBC插入时#34;触摸" DB第一个事务已经完成。有关此行为的一些信息可以在here找到。