【问题标题】:How does PostgreSQL perform writes so much faster than SQLite?PostgreSQL 如何比 SQLite 更快地执行写入?
【发布时间】:2013-10-08 17:50:42
【问题描述】:

我做了一个简单的整数更新性能测试。 SQLite 每秒只更新 15 次,而 PostgreSQL 每秒更新 1500 次。

SQLite 大小写的数字似乎是normal

SQLite 站点中的 FAQ 解释为好像它是旋转磁盘的基本限制。

实际上,SQLite 可以轻松地执行 50,000 或更多的 INSERT 语句 在普通台式计算机上排名第二。但它只会做几十 每秒事务数。交易速度受限于 磁盘驱动器的转速。交易通常需要 磁盘盘片的两个完整旋转,在 7200RPM 磁盘上 drive 将您限制为每秒大约 60 个事务。交易 速度受磁盘驱动器速度的限制,因为(默认情况下)SQLite 实际上一直等到数据真正安全地存储在磁盘上 在交易完成之前浮出水面。这样,如果你突然 断电或如果您的操作系统崩溃,您的数据仍然是安全的。为了 详细信息,请阅读 SQLite 中的原子提交。

默认情况下,每个 INSERT 语句都是它自己的事务。但是如果你 用 BEGIN...COMMIT 包围多个 INSERT 语句,然后所有 插入被分组到一个事务中。需要的时间 提交事务在所有封闭的插入中摊销 语句,因此每个插入语句的时间大大减少。

另一个选项是运行 PRAGMA synchronous=OFF。该命令将 导致 SQLite 不等待数据到达磁盘表面,这将 使写操作看起来要快得多。但如果你失去力量 在事务处理过程中,您的数据库文件可能会损坏。

这个描述是真的吗?那么,PostgreSQL 怎么能比 SQLite 执行得这么快呢? (我在 PostgreSQL 中将 fsyncsynchronous_commit 选项都设置为 on

更新:

这是用 Clojure 编写的完整测试代码:

(defproject foo "0.1.0-SNAPSHOT"
  :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.clojure/java.jdbc "0.3.0-SNAPSHOT"]
                 [com.mchange/c3p0 "0.9.2.1"]
                 [org.xerial/sqlite-jdbc "3.7.2"]
                 [postgresql "9.1-901.jdbc4"]])
(ns foo.core
  (:require [clojure.java.jdbc :as jdbc]
            [clojure.java.jdbc.ddl :as ddl])
  (:import  [com.mchange.v2.c3p0 ComboPooledDataSource]))

(def sqlite
  (let [spec {:classname "org.sqlite.JDBC"
              :subprotocol "sqlite"
              :subname "test.db"}]
    {:datasource (doto (ComboPooledDataSource.)
                   (.setDriverClass (:classname spec))
                   (.setJdbcUrl (str "jdbc:" (:subprotocol spec) ":" (:subname spec)))
                   (.setMaxIdleTimeExcessConnections (* 30 60))
                   (.setMaxIdleTime (* 3 60 60)))}))

(def postgres
  (let [spec {:classname "org.postgresql.Driver"
              :subprotocol "postgresql"
              :subname "//localhost:5432/testdb"
              :user "postgres"
              :password "uiop"}]
    {:datasource (doto (ComboPooledDataSource.)
                   (.setDriverClass (:classname spec))
                   (.setJdbcUrl (str "jdbc:" (:subprotocol spec) ":" (:subname spec)))
                   (.setUser (:user spec))
                   (.setPassword (:password spec))
                   (.setMaxIdleTimeExcessConnections (* 30 60))
                   (.setMaxIdleTime (* 3 60 60)))}))

(doseq [x [sqlite postgres]]
  (jdbc/db-do-commands x
    (ddl/create-table :foo [:id :int "PRIMARY KEY"] [:bar :int])))

(doseq [x [sqlite postgres]]
  (jdbc/insert! x :foo {:id 1 :bar 1}))

(defmacro bench
  [expr n]
  `(dotimes [_# 3]
     (let [start# (. System (nanoTime))]
       (dotimes [_# ~n]
         ~expr)
       (let [end#               (. System (nanoTime))
             elapsed#           (/ (double (- end# start#)) 1000000.0)
             operation-per-sec# (long (/ (double ~n) (/ (double (- end# start#)) 1000000000)))]
       (prn (str "Elapsed time: " elapsed# " ms (" (format "%,d" operation-per-sec#) " ops)"))))))

(bench (jdbc/query sqlite ["select * from foo"]) 20000)
(bench (jdbc/execute! sqlite ["update foo set bar=bar+1 where id=?" 1]) 100)

(bench (jdbc/query postgres ["select * from foo"]) 20000)
(bench (jdbc/execute! postgres ["update foo set bar=bar+1 where id=?" 1]) 5000)

输出是:

; Running "select * from foo" 20000 times in SQLite

"Elapsed time: 1802.426963 ms (11,096 ops)"
"Elapsed time: 1731.118831 ms (11,553 ops)"
"Elapsed time: 1749.842658 ms (11,429 ops)"

; Running "update foo set bar=bar+1 where id=1" 100 times in SQLite

"Elapsed time: 6362.829057 ms (15 ops)"
"Elapsed time: 6405.25075 ms (15 ops)"
"Elapsed time: 6352.943553 ms (15 ops)"

; Running "select * from foo" 20000 times in PostgreSQL

"Elapsed time: 2898.636079 ms (6,899 ops)"
"Elapsed time: 2824.77372 ms (7,080 ops)"
"Elapsed time: 2837.622659 ms (7,048 ops)"

; Running "update foo set bar=bar+1 where id=1" 5000 times in PostgreSQL

"Elapsed time: 3213.120219 ms (1,556 ops)"
"Elapsed time: 3564.249492 ms (1,402 ops)"
"Elapsed time: 3280.128708 ms (1,524 ops)"

pg_fsync_test 结果:

C:\temp>"C:\Program Files\PostgreSQL\9.3\bin\pg_test_fsync"
5 seconds per test
O_DIRECT supported on this platform for open_datasync and open_sync.

Compare file sync methods using one 8kB write:
(in wal_sync_method preference order, except fdatasync
is Linux's default)
        open_datasync                   81199.920 ops/sec      12 usecs/op
        fdatasync                                     n/a
        fsync                              45.337 ops/sec   22057 usecs/op
        fsync_writethrough                 46.470 ops/sec   21519 usecs/op
        open_sync                                     n/a

Compare file sync methods using two 8kB writes:
(in wal_sync_method preference order, except fdatasync
is Linux's default)
        open_datasync                   41093.981 ops/sec      24 usecs/op
        fdatasync                                     n/a
        fsync                              38.569 ops/sec   25927 usecs/op
        fsync_writethrough                 36.970 ops/sec   27049 usecs/op
        open_sync                                     n/a

Compare open_sync with different write sizes:
(This is designed to compare the cost of writing 16kB
in different write open_sync sizes.)
         1 * 16kB open_sync write                     n/a
         2 *  8kB open_sync writes                    n/a
         4 *  4kB open_sync writes                    n/a
         8 *  2kB open_sync writes                    n/a
        16 *  1kB open_sync writes                    n/a

Test if fsync on non-write file descriptor is honored:
(If the times are similar, fsync() can sync data written
on a different descriptor.)
        write, fsync, close                45.564 ops/sec   21947 usecs/op
        write, close, fsync                33.373 ops/sec   29964 usecs/op

Non-Sync'ed 8kB writes:
        write                             889.800 ops/sec    1124 usecs/op

【问题讨论】:

  • 这与写入(提交)到磁盘的速度(或延迟)无关。这是关于有序写入
  • 您使用 PosgreSQL 对每个事务进行了多少更新?
  • 一个简单的整数更新性能测试:好的,所以我们不知道你实际做了什么。
  • @CL。我没有明确使用事务,所以它是每个事务的一个更新。我知道我可以在一个事务中包含多个更新以提高速度,但这不是我现在要问的。
  • @DanielVérité 这是一个有 2 个整数列的表,查询为 update foo set bar=bar+1 where id=1

标签: performance sqlite postgresql clojure


【解决方案1】:

分解为他们如何实现快照隔离。

SQLite 使用文件锁定作为隔离事务的一种方式,允许写入仅在所有读取完成后才命中。

相比之下,Postgres 使用一种称为多并发版本控制 (mvcc) 的更复杂的方法,它允许多次写入与多次读取并行发生。

http://www.sqliteconcepts.org/SI_index.html

http://www.postgresql.org/docs/current/static/mvcc-intro.html

http://wiki.postgresql.org/wiki/MVCC

【讨论】:

  • 这是一个有 2 个整数列的表,查询被update foo set bar=bar+1 where id=1 串行执行。换句话说,不涉及并发,所以我怀疑它与 MVCC 有关。
  • 它实际上可能:SQLite 将(内部)更新现有行。事实上,所有行,导致它重写整个表(以及事实上的整个 SQL 文件)。而 Postgres 将(再次在内部)将行标记为在该 txid 处已删除,并插入从该 txid 开始有效的新行。如果您使用表格的填充因子,您可能会看到 Postgres 的性能略有不同,例如将其从默认值(60 或 70,如果有记忆的话)更改为 10(稍微快一点,因为页面被拆分的频率较低)或 95(分别稍微慢一点)。
【解决方案2】:

你的怀疑是正确的。具有您指示的设置的 PostgreSQL 应该无法在每秒单独的顺序事务中对旋转介质执行接近 1500 次更新。

您的 IO 堆栈中可能存在关于它如何实现同步的谎言或错误。这意味着您的数据在意外断电或操作系统故障后面临严重损坏的风险。

看pg_test_fsync的结果,确实是这样。 open_datasync 是 Windows 下的默认设置,速度似乎不切实际,因此肯定是不安全的。当我在 Windows7 机器上运行 pg_test_fsync 时,我看到了同样的情况。

【讨论】:

  • 我认为fsync 是默认值。改成fsync后tps下降到50~60。
  • 我也把SQLite中的journal_mode改成了WAL,现在看到的tps数差不多,所以在这个简单的情况下两个数据库的性能应该差不多.
【解决方案3】:

丹尼斯的回答包含您需要的所有链接。我将寻求一个不太详细但可能更容易理解的答案。

Sqlite 没有使用任何复杂的事务管理器,也没有隐藏高级的多任务逻辑。它按照您告诉它执行的顺序执行。换句话说:它完全按照您的指示去做。如果您尝试从两个进程中使用相同的数据库 - 您会遇到问题。

另一方面,PostgreSQL 是一个非常复杂的数据库:它有效地支持多个并发读取和写入。把它想象成一个异步系统——你只安排要完成的工作,你实际上并没有控制它的细节——Postgres 为你做。

如何提高您的效率? 将数个 - 数十个 - 数百个更新/插入合并到一个事务中。对于一个简单的表,您将获得非常好的性能。

【讨论】:

    【解决方案4】:

    实际上,旋转磁盘上的任何写入都在 10 毫秒的数量级(典型数字为 8 毫秒)。

    这意味着每秒写入超过 100 次,如果您在磁盘中写入相同的位置,这对于数据库来说是一种非常奇怪的情况。请参阅 ACM 中的“您对磁盘一无所知”,通常一个磁盘可以在一次轮换中安排 10 次读取或写入。

    http://queue.acm.org/detail.cfm?id=864058

    因此,数据库每秒可以执行 1,000 次甚至更多的写入。 10 年前,我在台式电脑上看到应用每秒执行 1,500 次事务。

    【讨论】:

      【解决方案5】:

      假设您使用的是普通硬盘(即没有 ssd),您可以预期每秒最多写入 50-100 次。每秒 15 次写入似乎略低,但并非不可能。

      因此,如果 Postgres 每秒进行 1500 次更新,它们要么被写入某个缓冲区/缓存,要么被折叠成单个更新。在不了解更多实际测试的情况下,很难说出真正的原因,但是如果您要打开一个事务,更新一行 1500 次并在此之后提交,那么 Postgres 应该足够聪明,只能执行一个“真实”写入磁盘。

      【讨论】:

      • 相信其实是和事务隔离相关的问题有关。
      • @Denis:非常正确,主要的是,只有在提交之后才能保证何时必须将所有内容写入磁盘。同时它取决于事务隔离级别。未提交的读取可能会给您提供已写入事务中的不正确结果。
      【解决方案6】:

      现代服务器和存储实现了 RAID 技术,在内存中写入缓存和分层。

      为了增加交易数量,有一些硬件可能性

      接收写入的多个驱动器 拥有千兆内存缓存的存储,提前确认交易 具有大内存缓存 (2GB) 并为性能做好准备的磁盘控制器 具有 SSD 磁盘的第 1 层磁盘(稍后将数据移动到第 2/3 层)

      【讨论】:

        猜你喜欢
        • 2018-06-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-30
        • 1970-01-01
        • 2014-01-10
        相关资源
        最近更新 更多