为什么一个 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-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 秒,而第二个事务在进行更新之前等待半秒,但在提交之前不会等待。
(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。
我有简单的table:
create table tx_test
(
i integer,
constraint i_unique unique (i)
);
我还具有以事务方式 (jdbc-insert-i-tx
) 执行对此 table 的插入的功能。 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 秒,而第二个事务在进行更新之前等待半秒,但在提交之前不会等待。
(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。