java - 通过 Clojure 和 JDBC 将 5,000,000 行移动到另一个 Postgresql 数据库

标签 java postgresql jdbc clojure lazy-evaluation

我正在尝试将 5,000,000 行从一个 Postgre 数据库移动到另一个数据库。两个连接都在 Hikari CP 连接池中。

我浏览了很多文档和帖子。它给我留下了下面的代码。但它并不是真的可用:

(jdbc/with-db-connection [tx {:datasource source-db}]
  (jdbc/query tx
      [(jdbc/prepare-statement (jdbc/get-connection tx)
                                answer-sql
                                {:fetch-size 100000})]
                  {:result-set-fn (fn [result-set]
                                    (jdbc/insert-multi!
                                     {:datasource target-db}
                                     :migrated_answers
                                     result-set))}))

我已经尝试了很多不同的形式。 jdbc/with-db-transaction 或任何其他我能想到的都没有多大帮助。

  1. 很多教程和帖子只提到如何处理整体结果的方式。进入 RAM 的小表绝对没问题,但它看起来很快。但事实并非如此。

  2. 因此,当我正确使用 :fetch-size 并且我的 RAM 没有爆炸(hocus pocus)时,传输速度非常慢,两个连接都在“Activity ”和“空闲”之间切换在数据库端的事务状态中。我从来没有等这么久才找到任何实际传输的数据!

    当我在 Talend Open Studio(生成 Java 代码的 ETL 工具)中创建这个简单的批处理时,它会在 5 分钟内传输所有数据。那里的光标大小“也”设置为 100000。我认为 Clojure 的干净代码应该更快。

  3. 我得到的最快结果是使用下面的代码。我认为这是因为 :as-array 参数。如果我不使用 :max-rows 参数内存会爆炸,因为它没有被延迟处理,所以我不能将它用于整个传输。为什么?我不明白这里的规则。

    (jdbc/with-db-transaction [tx {:datasource source-db}]
      (jdbc/query tx
                  [(jdbc/prepare-statement (:connection tx)
                                            answer-sql
                                           {:result-type :forward-only
                                            :concurrency :read-only
                                            :fetch-size 2000
                                            :max-size 250000})]
                  {:as-arrays? true
                   :result-set-fn (fn [result-set]
                                    (let [keys (first result-set)
                                          values (rest result-set)]
                                      (jdbc/insert-multi! 
                                         {:datasource dct-db}
                                          :dim_answers
                                           keys values)))}))
    

如果我明显缺少任何帮助或信息,我将不胜感激。

最佳答案

我认为这里的关键观察是,当您的查询 懒惰地从一个数据库流式传输结果时,您的插入 只是对另一个数据库的一次巨大写入。关于内存使用,如果您在最后为单个写入操作收集所有这些结果(在内存中),我认为无论您是否懒惰地流式传输查询结果都没有太大区别。

平衡内存使用和吞吐量的一种方法是批处理写入:

(db/with-db-transaction [tx {:datasource source-db}]
  (db/query tx
    [(db/prepare-statement (:connection tx)
                           answer-sql
                           {:result-type :forward-only
                            :concurrency :read-only
                            :fetch-size 2000})]
    {:as-arrays? true
     :result-set-fn (fn [result-set]
                      (let [keys (first result-set)
                            values (rest result-set)]
                        (doseq [batch (partition-all 2000 values)]
                          (db/insert-multi! {:datasource dct-db}
                                            :dim_answers
                                            keys
                                            batch))))}))

不同之处在于,它使用 partition-all 批量插入 values(大小与 :fetch-size 相同,但我确保可以对其进行调整)。通过将 JVM 最大堆大小设置为类似 -Xmx1g 的方法,比较此方法与其他方法的性能/内存使用情况。我无法使用此堆大小完成非批处理版本。

我能够在大约 1 分钟内在笔记本电脑上的本地 PostgreSQL 数据库之间迁移 600 万个小行,并使用 java 使用 <400MB 内存。我还使用了 HikariCP。

如果您确实批量插入,如果适合您的用例,您可能需要考虑将所有插入包装在单个事务中。为简洁起见,我在这里省略了额外的交易。

If i dont use :max-size parameter memory explodes

我在最新的 clojure.java.jdbc 中找不到对此选项的任何引用(除了规范),它没有影响我的测试。我确实看到了一个 :max-rows 但你肯定不想要那个。

I think it is because the :as-array parameter.

我希望这对内存使用有益;行 vector 应该比行映射更节省空间。

关于java - 通过 Clojure 和 JDBC 将 5,000,000 行移动到另一个 Postgresql 数据库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48652788/

相关文章:

sql - 插入语句要求我插入自动增量列

SQLite/JDBC 内连接

Java执行多条SQL语句并写入excel

java - 无法连接到 MySql 数据库

java - 代码在命令提示符中运行但不在 Eclipse 中运行

java - 如何禁止在 Checkstyle 中对文件进行所有检查?

java - Apache Commons VFS 中的 Git 协议(protocol)

sql - jsonb_array_elements 获取元素位置

sql - 根据多个属性定义 SQL 约束

java - 在 Multi-Sim 设备中检测来电的目标 SimCard