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

标签 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 的 deferreds 实现的,但这与这个问题无关:

(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 时发生。所以,看起来,第二个事务可以以某种方式“看到”第一个事务所做的更新。这对我来说完全出乎意料,因为隔离级别设置为最高级别::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 插入“接触”数据库时第一笔交易已经完成。可以找到有关此行为的一些信息 here .

关于postgresql - 为什么一个 Clojure JDBC 可序列化事务 "sees"(?) 由另一个事务进行更改?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39466866/

相关文章:

php - 如何在数据库中存储可用的项目数量?

postgresql - 如何在可选参数上查询 postgres?

java - Tomcat 8.0.15 Oracle 11 数据库和 jdbc javax.naming.NamingException

clojure 生成向量与映射文字表达式

sql - 如何使用 psql 命令行将参数传递给 .sql 文件?

php - 在哪里存储云应用程序中的翻译?

即使在 Intellij 上使用 mySql Maven 依赖项后,java.lang.ClassNotFoundException : com. mysql.jdbc.Driver

java - 有条件更新交易中的值

visual-studio - 如何将项目文件类型与 Visual Studio 扩展 (VSIX) 中的项目类型相关联

function - 如何从Clojure中的符号获取函数名称?