22

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

SQLite案例的数字似乎是正常的。

SQLite 站点中的常见问题解答解释为好像它是旋转磁盘的基本限制。

实际上,SQLite 在普通台式计算机上每秒可以轻松执行 50,000 或更多的 INSERT 语句。但它每秒只会进行几十次交易。交易速度受磁盘驱动器转速的限制。一个事务通常需要磁盘盘片完全旋转两次,这在 7200RPM 磁盘驱动器上将您限制为每秒大约 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
4

6 回答 6

20

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

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

于 2013-10-02T12:04:43.510 回答
12

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

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

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

于 2013-10-02T16:43:54.907 回答
5

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

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

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

你的效率怎么办?将几个 - 几十个 - 数百个更新/插入加入到一个事务中。对于一个简单的表,您将获得非常好的性能。

于 2013-10-02T12:36:59.023 回答
2

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

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

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

因此,数据库每秒可以执行 1,000 次甚至更多的写入。10 年前,我已经看到应用程序在台式计算机中每秒执行 1,500 次事务。

于 2014-05-07T20:57:16.590 回答
1

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

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

于 2013-10-02T11:52:49.710 回答
0

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

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

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

于 2022-01-18T15:08:48.370 回答