为什么一个 Clojure JDBC 可序列化事务 "sees" (?) 由另一个事务进行更改?

Why one Clojure JDBC serializable transaction "sees" (?) changes made by another transaction?

我有简单的table:

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

我还具有以事务方式 (jdbc-insert-i-tx) 执行对此 table 的插入的功能。 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 秒,而第二个事务在进行更新之前等待半秒,但在提交之前不会等待。

(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 时发生。因此,看起来,第二笔交易可能会以某种方式 "see" 由第一笔交易进行更新。这对我来说完全出乎意料,因为隔离级别设置为最高::serializable.

这种行为是否正确?还是哪里错了?

如果这有帮助,我正在使用以下库:

[com.mchange/c3p0 "0.9.5.2"]
[org.postgresql/postgresql "9.4.1208"]
[org.clojure/java.jdbc "0.3.7"]

您遇到的情况与本题基本相同INSERT and transaction serialization in PostreSQL

这样的行为是正确的,因为 Postgresql 会阻止对带有索引字段的行的后续并发突变的执行,直到第一个突变完全完成(成功或出错),所以到你的第二个 JDBC insert "touches" DB 第一个事务已经完成。可以找到有关此行为的一些信息 here