为什么PostgreSQL比MongoDB还快?

PostgreSQL9.4带来了全新的NoSQL特性,并且根据EnterpriseDB的测试,其加载,插入和查询的性能都已经几倍于MongoDB了。
虽然我是PG的铁杆粉丝,但是关系数据库背负了ACID的重型装甲,在性能上居然能打败轻装上阵的NoSQL数据库总觉得有点离谱。
所以我在自己的环境里验证了一下EnterpriseDB的测试结果,并且小探一下PG取胜的原因。

1. EnterpriseDB的测试结果

以下是EnterpriseDB的测试结果(数据量为5000万)
http://www.enterprisedb.com/postgres-plus-edb-blog/marc-linster/postgres-outperforms-mongodb-and-ushers-new-developer-reality
为什么PostgreSQL比MongoDB还快?_第1张图片
(还可以参考这篇译文: http://blog.jobbole.com/78215/)

2. 我的验证结果

测试观点
为了使测试结果更加单纯,我准备单纯比拼CPU消耗(尽量排除IO和网络的干扰),设定以下测试条件。
1)所有数据都要放进内存
2)C/S都跑在同一台 单机上
所以,只在单机上进行10万条小数据量的测试
注)EnterpriseDB的测试环境是32G内存的Amazon Web Services M3.2XLARGE实例,总数据量超过内存了。

测试环境
测试环境为个人PC上的VMware虚拟机
PC
 CPU:Intel Core i5-3470 3.2G(4核)
 MEM:6GB
 SSD:OCZ-VERTEX4 128GB(VMware虚拟机所在磁盘,非系统盘)
 OS:Win7

VMware虚拟机
 CPU:4核
 MEM:1GB
 OS:CentOS 6.5
 PG:PostgreSQL 9.4.0(shared_buffers = 428MB,其他是默认值)
 MG: MongoDB  3.0.2

测试步骤
测试步骤非常简单,可以参考:
https://github.com/EnterpriseDB/pg_nosql_benchmark

但是,在测试前,有些东西要改。
1)把数据量减小到10万
pg_nosql_benchmark-master/pg_nosql_benchmark:
declare -a json_rows=(10000000)
==>
declare -a json_rows=(100000)

2)修改 mongo的 一处脚本(注)
pg_nosql_benchmark-master/lib/mongo_func_lib.sh:
   collectionsize="$(echo ${output}|awk -F"," '{print $5}'|cut -d":" -f2)"
==>
   collectionsize="$(echo ${output}|awk -F"," '{print $ 6}'|cut -d":" -f2)"

) pg_nosql_benchmark原来是基于 MongoDB  2.6设计的,MongoDB 3.0的db.json_tables.stats()输出可能变了,所以这边要修改一下。

测试结果

点击(此处)折叠或打开

  1. -bash-4.1$ sh pg_nosql_benchmark
  2. PID: 2160 [RUNTIME: 04-12-15 08:15:51] pg_nosql_benchmark: MongoDB Version 3.0.2
  3. PID: 2160 [RUNTIME: 04-12-15 08:15:51] pg_nosql_benchmark: PostgreSQL Version 9.4.0
  4. PID: 2160 [RUNTIME: 04-12-15 08:15:51] pg_nosql_benchmark: creating json data.
  5. PID: 2160 [RUNTIME: 04-12-15 08:17:18] pg_nosql_benchmark: preparing postgresql INSERTs.
  6. PID: 2160 [RUNTIME: 04-12-15 08:19:02] pg_nosql_benchmark: preparing mongo insert commands.
  7. PID: 2160 [RUNTIME: 04-12-15 08:20:45] pg_nosql_benchmark: droping database benchmark if exists.
  8. PID: 2160 [RUNTIME: 04-12-15 08:20:45] pg_nosql_benchmark: creating database benchmark.
  9. PID: 2160 [RUNTIME: 04-12-15 08:20:46] pg_nosql_benchmark: dropping mongo collection json_tables
  10. PID: 2160 [RUNTIME: 04-12-15 08:20:46] pg_nosql_benchmark: creating json_tables collection in postgreSQL.
  11. PID: 2160 [RUNTIME: 04-12-15 08:20:46] pg_nosql_benchmark: loading data in postgresql using sample.json.
  12. PID: 2160 [RUNTIME: 04-12-15 08:20:57] pg_nosql_benchmark: creating index on postgreSQL collections.
  13. PID: 2160 [RUNTIME: 04-12-15 08:21:00] pg_nosql_benchmark: testing mongoimport.
  14. PID: 2160 [RUNTIME: 04-12-15 08:21:14] pg_nosql_benchmark: creating index in mongodb.
  15. PID: 2160 [RUNTIME: 04-12-15 08:21:19] pg_nosql_benchmark: testing FIRST SELECT in postgresql.
  16. PID: 2160 [RUNTIME: 04-12-15 08:21:21] pg_nosql_benchmark: testing SECOND SELECT in postgresql.
  17. PID: 2160 [RUNTIME: 04-12-15 08:21:22] pg_nosql_benchmark: testing THIRD SELECT in postgresql.
  18. PID: 2160 [RUNTIME: 04-12-15 08:21:22] pg_nosql_benchmark: testing FOURTH SELECT in postgresql.
  19. PID: 2160 [RUNTIME: 04-12-15 08:21:23] pg_nosql_benchmark: calculating PostgreSQL collection size.
  20. PID: 2160 [RUNTIME: 04-12-15 08:21:23] pg_nosql_benchmark: testing mongo FIRST SELECT.
  21. PID: 2160 [RUNTIME: 04-12-15 08:21:27] pg_nosql_benchmark: testing mongo SECOND SELECT.
  22. PID: 2160 [RUNTIME: 04-12-15 08:21:27] pg_nosql_benchmark: testing mongo THIRD SELECT.
  23. PID: 2160 [RUNTIME: 04-12-15 08:21:30] pg_nosql_benchmark: testing mongo FOURTH SELECT.
  24. PID: 2160 [RUNTIME: 04-12-15 08:21:33] pg_nosql_benchmark: calculating the size of mongo collection.
  25. PID: 2160 [RUNTIME: 04-12-15 08:21:34] pg_nosql_benchmark: dropping mongo collection json_tables
  26. PID: 2160 [RUNTIME: 04-12-15 08:21:34] pg_nosql_benchmark: testing inserts in mongo
  27. PID: 2160 [RUNTIME: 04-12-15 08:23:00] pg_nosql_benchmark: droping json object in postgresql.
  28. PID: 2160 [RUNTIME: 04-12-15 08:23:00] pg_nosql_benchmark: inserting data in postgresql using sample_pg_inserts.json.
  29. number of rows 100000
  30. PG COPY (ns) 10886811763
  31. PG INSERT (ns) 22173081221
  32. PG SELECT (ns) 1018231815
  33. PG SIZE (bytes) 148946944
  34. MONGO IMPORT (ns) 13880183843
  35. MONGO INSERT (ns) 86577229486
  36. MONGO SELECT (ns) 2669842035
  37. MONGO SIZE (bytes) 429092864
除了数据加载的性能差距不明显外,其它数据基本和 EnterpriseDB的测试结果一致(MongoDB插入时间是PG的4倍,查询时间是PG的2倍多)。

3.  PostgreSQL真的比MongoDB还快吗

下面模仿 EnterpriseDB的测试方法,单独进行每一项测试。

3.1 测试数据

修改测试脚本后再次执行,将加载和插入的数据文件保留下来。
pg_nosql_benchmark:

点击(此处)折叠或打开

  1. rm -rf ${SAMPLEJSON}*
  2. rm -rf ${PG_INSERTS}
  3. rm -rf ${MONGO_INSERTS}
==>

点击(此处)折叠或打开

  1. #rm -rf ${SAMPLEJSON}*
  2. #rm -rf ${PG_INSERTS}
  3. #rm -rf ${MONGO_INSERTS}

查看一下数据
点击( 此处 )折叠或打开
  1. -bash-4.1$ head -1 sample.json
  2. { "name" : "AC33929 Phone", "brand" : "ACME1415", "type" : "phone", "price" : 200, "warranty_years" : 1, "available" : true, "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin eget elit ut nulla tempor viverra vel eu nulla. Sed luctus porttitor urna, ac dapibus velit fringilla et. Donec iaculis, dolor a vehicula dictum, augue neque suscipit augue, nec mollis massa neque in libero. Donec sed dapibus magna. Pellentesque at condimentum dolor. In nunc nibh, dignissim in risus a, blandit tincidunt velit. Vestibulum rutrum tempus sem eget tempus. Mauris sollicitudin purus auctor dolor vestibulum, vitae pulvinar neque suscipit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Phasellus lacus turpis, vulputate at adipiscing viverra, ultricies at lectus. Pellentesque ut porta leo, vel eleifend neque. Nunc sagittis metus at ante pellentesque, ut condimentum libero semper. In hac habitasse platea dictumst. In dapibus posuere posuere. Fusce vulputate augue eget tellus molestie, vitae egestas ante malesuada. Phasellus nunc mi, faucibus at elementum pharetra, aliquet a enim. In purus est, vulputate in nibh quis, faucibus dapibus magna. In accumsan libero velit, eu accumsan sem commodo id. In fringilla tempor augue, et feugiat erat convallis et. Sed aliquet eget ipsum eu vestibulum.Curabitur blandit leo nec condimentum semper. Mauris lectus sapien, rutrum a tincidunt id, euismod ac elit. Mauris suscipit et arcu et auctor. Quisque mollis magna vel mi viverra rutrum. Nulla non pretium magna. Cras sed tortor non tellus rutrum gravida eu at odio. Aliquam cursus fermentum erat, nec ullamcorper sem gravida sit amet. Donec viverra, erat vel ornare pulvinar, est ipsum accumsan massa, eu tristique lorem ante nec tortor. Sed suscipit iaculis faucibus. Maecenas a suscipit ligula, vitae faucibus turpis.Cras sed tellus auctor, tempor leo eu, molestie leo. Suspendisse ipsum tellus, egestas et ultricies eu, tempus a arcu. Cras laoreet, est dapibus consequat varius, nisi nisi placerat leo, et dictum ante tortor vitae est. Duis eu urna ac felis ullamcorper rutrum. Quisque iaculis, enim eget sodales vehicula, magna orci dignissim eros, nec volutpat massa urna in elit. In interdum pellentesque risus, feugiat pulvinar odio eleifend sit amet. Quisque congue libero quis dolor faucibus, a mollis nisl tempus." }

查看 数据的总大小,10万条记录共253MB。
点击( 此处 )折叠或打开
  1. -bash-4.1$ ls -l *.json
  2. -rw-r--r-- 1 postgres postgres 264993924 Apr 12 08:17 sample.json
  3. -rw-r--r-- 1 postgres postgres 267493911 Apr 12 08:20 sample_mongo_inserts.json
  4. -rw-r--r-- 1 postgres postgres 269493962 Apr 12 08:19 sample_pg_inserts.json

3.2 PostgreSQL测试

1)清理旧数据

点击(此处)折叠或打开

  1. benchmark=# drop table json_tables;
  2. DROP TABLE
  3. benchmark=# create table json_tables(data jsonb);
  4. CREATE TABLE

2)加载数据

点击(此处)折叠或打开

  1. -bash-4.1$ cat sample.json |time -p psql benchmark -c "copy json_tables from stdin;"
  2. real 10.00
  3. user 0.00
  4. sys 0.35

执行测试期间的系统负载

点击(此处)折叠或打开

  1. [root@hanode1 ~]# top
  2. top - 09:12:31 up 1 day, 12:12,  5 users,  load average: 0.15, 0.03, 0.01
  3. Tasks: 160 total,   2 running, 158 sleeping,   0 stopped,   0 zombie
    Cpu(s): 14.3%us, 13.3%sy,  0.0%ni, 70.0%id,  1.9%wa,  0.1%hi,  0.4%si,  0.0%st
    Mem:   1019320k total,   942632k used,    76688k free,     9768k buffers
    Swap:  2064376k total,    80232k used,  1984144k free,   840444k cached

     PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                  
    30622 postgres  20   0  588m  34m  32m R 93.4  3.5   0:08.00 postgres                                                                  
    30619 postgres  20   0 98.6m  568  464 S 13.3  0.1   0:00.99 cat                                                                        
    17197 postgres  20   0  587m 212m 211m S 10.3 21.3   0:05.91 postgres                                                                  
    17199 postgres  20   0  586m  14m  13m S  5.0  1.4   0:03.26 postgres                                                                  
    30621 postgres  20   0  167m 2496 1984 S  3.3  0.2   0:00.30 psql                                                                      
      22 root      20   0     0    0    0 S  0.3  0.0   1:13.42 events/3                                                                  
      35 root      20   0     0    0    0 S  0.3  0.0   0:01.84 kblock
注意红色的部分,postgres进程的CPU利用率接近了极限。

3) 建索引
点击( 此处 )折叠或打开
  1. -bash-4.1$ time -p psql benchmark -c "create index json_tables_idx on json_tables using gin(data);"
  2. CREATE INDEX
  3. real 2.71
  4. user 0.00
  5. sys 0.00

4)查看存储空间大小

点击(此处)折叠或打开

  1. benchmark=# select pg_relation_size('json_tables');
  2.  pg_relation_size
  3. ------------------
  4.         148946944
  5. (1 row)

  6. benchmark=# select pg_relation_size('json_tables_idx');
  7.  pg_relation_size
  8. ------------------
  9.          51134464
  10. (1 row)
PostgreSQL的jsonb存储优化还是不错的,表占了142MB,比原始数据( 253MB)要小。

5)数据查询
点击( 此处 )折叠或打开
  1. -bash-4.1$ time psql -qAt benchmark -c "SELECT data FROM json_tables WHERE (data->>'brand') = 'ACME';" >/dev/null

  2. real    0m0.784s
  3. user    0m0.044s
  4. sys    0m0.032s

看看执行计划

点击(此处)折叠或打开

  1. benchmark=# explain (analyze,buffers) SELECT data FROM json_tables WHERE (data->>'brand') = 'ACME';
  2.                                                     QUERY PLAN
  3. -------------------------------------------------------------------------------------------------------------------
  4.  Seq Scan on json_tables (cost=0.00..19682.01 rows=500 width=1262) (actual time=0.192..486.710 rows=9091 loops=1)
  5.    Filter: ((data ->> 'brand'::text) = 'ACME'::text)
  6.    Rows Removed by Filter: 90910
  7.    Buffers: shared hit=2304 read=15878
  8.  Planning time: 0.645 ms
  9.  Execution time: 487.951 ms
  10. (6 rows)

居然是顺序扫描!GIN索引是吃干饭的吗?
仔细一看,原来 EnterpriseDB的测试SQL里把操作符写错了,无法使用GIN索引(想不到PG大牛出没的 EnterpriseDB也会犯这么低级的错误)。下面改下SQL让GIN上场。

点击(此处)折叠或打开

  1. benchmark=# explain (analyze,buffers) SELECT data FROM json_tables WHERE data @> '{"brand":"ACME"}';
  2.                                                           QUERY PLAN
  3. ------------------------------------------------------------------------------------------------------------------------------
  4.  Bitmap Heap Scan on json_tables (cost=28.78..407.78 rows=100 width=1262) (actual time=3.612..47.001 rows=9091 loops=1)
  5.    Recheck Cond: (data @> '{"brand": "ACME"}'::jsonb)
  6.    Heap Blocks: exact=9091
  7.    Buffers: shared hit=9110
  8.    -> Bitmap Index Scan on json_tables_idx (cost=0.00..28.75 rows=100 width=0) (actual time=2.162..2.162 rows=9091 loops=1)
  9.          Index Cond: (data @> '{"brand": "ACME"}'::jsonb)
  10.          Buffers: shared hit=19
  11.  Planning time: 0.175 ms
  12.  Execution time: 47.690 ms
  13. (9 rows)
效果好了不少啊!

换到外面用psql测。
点击( 此处 )折叠或打开
  1. -bash-4.1$ time psql -qAt benchmark -c "SELECT data FROM json_tables WHERE data @> '{\"brand\":\"ACME\"}'" >/dev/null

  2. real    0m0.326s
  3. user    0m0.047s
  4. sys    0m0.031s

比以前有数据时快了1倍,但是没有explain (analyze,buffers) 快。原因也很简单,输出结果太多了, 9091条数据在客户端和服务端都要消耗不少资源,而explain (analyze,buffers) 只是简单的在服务端把结果丢弃

为了排除 处理大量结果数据的干扰,换一个0匹配的查询再试试。
点击( 此处 )折叠或打开
  1. -bash-4.1$ time psql -qAt benchmark -c "SELECT data FROM json_tables WHERE data @> '{\"brand\":\"ACME111\"}'" >/dev/null

  2. real    0m0.015s
  3. user    0m0.005s
  4. sys    0m0.002s
15ms,简直神速啊!

再试试 0匹配的全表扫描。

点击(此处)折叠或打开

  1. -bash-4.1$ time psql -qAt benchmark -c "set enable_bitmapscan=false;SELECT data FROM json_tables WHERE data @> '{\"brand\":\"ACME111\"}'"

  2. real    0m0.594s
  3. user    0m0.002s
  4. sys    0m0.006s
全表扫描明显慢多了。

6)数据插入
先清数据

点击(此处)折叠或打开

  1. benchmark=# truncate json_tables;
  2. TRUNCATE TABLE

插入数据

点击(此处)折叠或打开

  1. -bash-4.1$ time -p psql -qAt benchmark --single-transaction -f sample_pg_inserts.json
  2. real 20.77
  3. user 1.23
  4. sys 2.80

插入期间的系统负载

点击(此处)折叠或打开

  1. [root@hanode1 ~]# top
    top - 09:58:31 up 1 day, 12:58,  5 users,  load average: 0.23, 0.05, 0.02
    Tasks: 161 total,   3 running, 158 sleeping,   0 stopped,   0 zombie
    Cpu(s): 17.0%us,  8.0%sy,  0.0%ni, 71.7%id,  3.0%wa,  0.0%hi,  0.3%si,  0.0%st
    Mem:   1019320k total,   936824k used,    82496k free,    11420k buffers
    Swap:  2064376k total,    80128k used,  1984248k free,   833644k cached

     PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                  
    2891 postgres  20   0  587m 110m 109m R 74.5 11.1   0:07.45 postgres                                                                  
    2890 postgres  20   0  167m 2600 2044 R 19.6  0.3   0:01.87 psql                                                                      
    17197 postgres  20   0  587m 212m 211m S  3.3 21.3   0:06.55 postgres                                                                  
    17199 postgres  20   0  586m  14m  13m S  3.0  1.4   0:03.54 postgres                                                                  
    17198 postgres  20   0  586m 2648 2512 S  0.3  0.3   0:01.09 postgres                                                                  
    25933 root      20   0 15036 1088  804 R  0.3  0.1   3:01.30 top                                                                        
       1 root      20   0 19356  816  648 S  0.0  0.1   0:01.18 init      
postgres进程的CPU利用率达到74.5%。
postgres进程的 CPU利用率为什么还要空这么多?原因也很简单,客户端和服务端是串行处理的,在空闲的时间里,它在等psql。
那么把postgres的psql进程的CPU利用率加起来就是94.1%,确实可以判断达CPU性能瓶颈了。


等一等!其实这个测试中PG作弊了。
既然是和MongoDB PK普通的插入操作,应该是一个SQL一个事务才对。修改后再测。

点击(此处)折叠或打开

  1. -bash-4.1$ time -p psql -qAt benchmark -f sample_pg_inserts.json
  2. real 43.64
  3. user 0.80
  4. sys 2.89

查看系统负载。

点击(此处)折叠或打开

  1. [root@hanode1 ~]# top
    top - 11:33:20 up 1 day, 14:33,  5 users,  load average: 0.22, 0.05, 0.02
    Tasks: 157 total,   2 running, 155 sleeping,   0 stopped,   0 zombie
    Cpu(s):  2.1%us, 14.1%sy,  0.0%ni, 71.7%id, 10.2%wa,  0.1%hi,  1.8%si,  0.0%st
    Mem:   1019320k total,   947744k used,    71576k free,     8044k buffers
    Swap:  2064376k total,    67764k used,  1996612k free,   846260k cached

     PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND      
    12577 postgres  20   0  587m  89m  88m D 52.9  9.0   0:08.94 postgres      
    12576 postgres  20   0  167m 2596 2040 S  8.6  0.3   0:01.35 psql        
    12135 postgres  20   0  586m  48m  47m S  3.0  4.9   0:00.11 postgres      
      34 root      20   0     0    0    0 S  1.0  0.0   0:02.83 kblockd/0    
      36 root      20   0     0    0    0 S  0.3  0.0   0:02.63 kblockd/2    
      59 root      20   0     0    0    0 R  0.3  0.0   0:11.01 kswapd0      
这次 执行时间也长了1倍,postgres+psql进程的CPU也没用满,才61.5%。CPU的空闲那段时间应该是在等WAL被 刷到磁盘
(由于这是单并发测试,不可避免会出现CPU空闲;但是在多并发的环境里,只要存储的吞吐和IOPS足够高,是完全可以把CPU撑满的。)

3.3 MongoDB

1)清理旧数据
点击( 此处 )折叠或打开
  1. > db.json_tables.drop()
  2. true

2)加载数据

点击(此处)折叠或打开

  1. -bash-4.1$ time -p mongoimport --type json --collection json_tables --db benchmark sample.json >/dev/null 2>/dev/null
  2. real 10.22
  3. user 7.44
  4. sys 5.15

加载期间的系统负载

点击(此处)折叠或打开

  1. [root@hanode1 ~]# top
    top - 10:15:50 up 1 day, 13:15,  5 users,  load average: 0.16, 0.03, 0.01
    Tasks: 152 total,   1 running, 151 sleeping,   0 stopped,   0 zombie
    Cpu(s): 17.4%us, 18.7%sy,  0.0%ni, 57.8%id,  5.3%wa,  0.2%hi,  0.5%si,  0.0%st
    Mem:   1019320k total,   895156k used,   124164k free,     1532k buffers
    Swap:  2064376k total,    65560k used,  1998816k free,   428380k cached

     PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                  
    4673 postgres  20   0  694m 222m 3436 S 89.8 22.4   0:05.53 mongoimport                                                                
    4015 postgres  20   0 2771m 290m 142m S 49.5 29.2   0:04.29 mongod                                                                    
值得注意的是系统的瓶颈在mongoimport 进程,而不是mongod进程 mongod还有一半的余力
(数据加载时,执行的单条语句,大量数据,所以 mongoimport 和mongod进程不是串行而是并行执行的关系

 3)建索引

点击(此处)折叠或打开

  1. -bash-4.1$ echo "db.json_tables.ensureIndex( { \"name\": 1})" |time -p mongo benchmark >/dev/null
  2. real 1.35
  3. user 0.03
  4. sys 0.02
  5. -bash-4.1$ echo "db.json_tables.ensureIndex( { \"type\": 1})" |time -p mongo benchmark >/dev/null
  6. real 0.58
  7. user 0.07
  8. sys 0.01
  9. -bash-4.1$ echo "db.json_tables.ensureIndex( { \"brand\": 1})" |time -p mongo benchmark >/dev/null
  10. real 0.50
  11. user 0.03
  12. sys 0.01
MongoDB建的是btree索引,json中的每个常用的查询属性都要建1个,万一漏了就只能全集合扫描了(在一个无模式的数据模型里却需要建有模式的索引,不是有点讽刺吗?) 而PostgreSQL的GIN索引确是对整个json建的索引。这样一比较,PostgreSQL的GIN索引简直太牛了(不光 MongoDB,其它数据库也难有能和PG的扩展索引(GIN,GiST,SP-GiST)匹敌的东西吧?)。

4)查看存储空间大小

点击(此处)折叠或打开

  1. > db.json_tables.stats()
  2. {
  3.     "ns" : "benchmark.json_tables",
  4.     "count" : 100001,
  5.     "size" : 419640560,
  6.     "avgObjSize" : 4196,
  7.     "numExtents" : 16,
  8.     "storageSize" : 429092864,
  9.     "lastExtentSize" : 114012160,
  10.     "paddingFactor" : 1,
  11.     "paddingFactorNote" : "paddingFactor is unused and unmaintained in 3.0. It remains hard coded to 1.0 for compatibility only.",
  12.     "userFlags" : 1,
  13.     "capped" : false,
  14.     "nindexes" : 4,
  15.     "totalIndexSize" : 12452048,
  16.     "indexSizes" : {
  17.         "_id_" : 3262224,
  18.         "name_1" : 3899952,
  19.         "type_1" : 3442096,
  20.         "brand_1" : 1847776
  21.     },
  22.     "ok" : 1
  23. }
MongoDB的json存储 比原始数据( 253MB )稍微大一点,409MB。但是这个409MB是包含预先分配了但还没有使用的空间的,不考虑这部分空间则占了400MB(MongoDB每次扩张的空间量会越来越大,所以有时候单看storageSize不是很精确)。

5)数据查询
点击( 此处 )折叠或打开
  1. -bash-4.1$ echo "DBQuery.shellBatchSize = 10000000000;db.json_tables.find({ brand: 'ACME'})"|time -p mongo benchmark >/dev/null
  2. real 3.62
  3. user 3.53
  4. sys 0.09
查询时间确实长了,比PG慢太多。

但是,不要急,看看资源占用。

点击(此处)折叠或打开

  1. [root@hanode1 ~]# top
    top - 10:55:46 up 1 day, 13:55,  5 users,  load average: 0.13, 0.04, 0.01
    Tasks: 152 total,   2 running, 150 sleeping,   0 stopped,   0 zombie
    Cpu(s): 24.7%us,  0.4%sy,  0.0%ni, 74.8%id,  0.0%wa,  0.0%hi,  0.1%si,  0.0%st
    Mem:   1019320k total,   935684k used,    83636k free,     1464k buffers
    Swap:  2064376k total,    66976k used,  1997400k free,   615840k cached

     PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                  
    8773 postgres  20   0  752m  67m 9052 R 100.4  6.8   0:03.20 mongo                                                                    
    4015 postgres  20   0 2803m 830m 663m S  0.7 83.4   0:26.46 mongod                                                                    
    1424 root      -2   0 85108 1448 1256 S  0.3  0.1   0:19.60 lrmd                                                                      
    1425 hacluste  -2   0 89600 1496 1208 S  0.3  0.1   0:25.34 attrd                                                                      
    25911 root      20   0 98.0m 1104  972 S  0.3  0.1   0:40.12 sshd                                                                      
       1 root      20   0 19356  812  660 S  0.0  0.1   0:01.19 init          
看到没,时间都耗在客户端的mongo进程上。因为客户端在处理大量输出结果时消耗了太多的资源。
其实,执行psql时如果不加“-A”选项也会有类似的问题。

点击(此处)折叠或打开

  1. -bash-4.1$ time psql -qt -p 5433 benchmark -c "SELECT data FROM json_tables WHERE (data->>'brand') = 'ACME';" >/dev/null

  2. real    0m2.124s
  3. user    0m1.401s
  4. sys    0m0.066s

这说明什么?说明之前的查询测试结果根本就是误导嘛!(这就好比两个绝顶高手决斗的时候,比拼的却是谁的小弟递枪的速度快。)
为了排除大量数据处理的误导,下面执行一下0匹配的查询。

点击(此处)折叠或打开

  1. -bash-4.1$ echo "DBQuery.shellBatchSize = 10000000000;db.json_tables.find({ brand: 'ACME111'})"|time -p mongo benchmark >/dev/null
  2. real 0.06
  3. user 0.03
  4. sys 0.01
现在快多了。但是比起PostgreSQL执行相同查询的0.015s还是慢了很多。

再试试0匹配的全表扫描。

点击(此处)折叠或打开

  1. -bash-4.1$ echo "db.json_tables.dropIndexes()"|mongo benchmark
  2. MongoDB shell version: 3.0.2
  3. connecting to: benchmark
  4. {
  5.     "nIndexesWas" : 4,
  6.     "msg" : "non-_id indexes dropped for collection",
  7.     "ok" : 1
  8. }
  9. -bash-4.1$ echo "DBQuery.shellBatchSize = 10000000000;db.json_tables.find({ brand: 'ACME111'})"|time -p mongo benchmark >/dev/null
  10. real 0.24
  11. user 0.03
  12. sys 0.02
MongoDB的全表扫描比PostgreSQL快, PostgreSQL在相同条件下的执行时间是0.594s

6)数据插入
先清数据

点击(此处)折叠或打开

  1. -bash-4.1$ echo "db.json_tables.drop()"|mongo benchmark
  2. MongoDB shell version: 3.0.2
  3. connecting to: benchmark
  4. true
  5. bye

插入数据

点击(此处)折叠或打开

  1. -bash-4.1$ time mongo benchmark --quiet sample_mongo_inserts.json >/dev/null

  2. real    1m27.591s
  3. user    0m29.663s
  4. sys    0m48.646s
MongoDB的插入确实挺慢的。
而且如果查看输出的话,还会发现一堆错误,这是由于MongoDB的控制台不允许插入大于4k的文档。

点击(此处)折叠或打开

  1. 2015-04-12T11:19:08.680+0800 E QUERY SyntaxError: Unexpected token ILLEGAL
  2. 2015-04-12T11:19:08.681+0800 E QUERY SyntaxError: Unexpected identifier
  3. WriteResult({ "nInserted" : 1 })
  4. WriteResult({ "nInserted" : 1 })
  5. WriteResult({ "nInserted" : 1 })

下面看看插入时的系统负载

点击(此处)折叠或打开

  1. [root@hanode1 ~]# top
    top - 11:20:26 up 1 day, 14:20,  5 users,  load average: 0.00, 0.00, 0.00
    Tasks: 151 total,   2 running, 149 sleeping,   0 stopped,   0 zombie
    Cpu(s):  3.3%us,  1.9%sy,  0.0%ni, 71.1%id,  4.9%wa,  0.0%hi, 18.7%si,  0.0%st
    Mem:   1019320k total,   948452k used,    70868k free,     4076k buffers
    Swap:  2064376k total,    68296k used,  1996080k free,   632796k cached

     PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
    11269 postgres  20   0  753m  66m 9224 R 89.6  6.7   0:15.23 mongo  
    4015 postgres  20   0 2803m 534m 371m S 22.6 53.7   0:38.06 mongod
根据 系统负载,性能瓶颈还是客户端mongo进程。如果要把mongod的负载压满, MongoDB的性能还可以提升4倍。

4. 总结

测试数据总结如下(并根据服务端进程CPU的消耗量进行性能对比):
PostgreSQL MongoDB PK结果
测试数据 务端进程
CPU利用率

CPU占用
时间
测试数据 服务
CPU利用率

CPU占用
时间
Data Load(s) 10 93.40% 9.34 10.22 49.50% 5.06 MongoDB胜出(*1)
Insert(s) 20.77 74.50% 15.47 - - - 不具可比性
每SQL事Insert(s) 43.64 52.90% 23.09 87.591 22.60% 19.80 MongoDB
全表描Select(s) 0.784 - - - - - 不具可比性
Select(s) 0.326 - - 3.62 0.70% 0.03 不具可比性
0匹配Select(s) 0.015 - - 0.06 - - PostgreSQL
0匹配全表描Select(s) 0.594 - - 0.24 - - 不具可比性
Size(MB) 142 - - 409 - - PostgreSQL
索引Size(MB) 49 - - 9(*2) - - 不具可比性
*1)考虑到数据加载经常是单线程操作,不能完全无视mongoimport的瓶颈,这一局应该算是平手。
*2)3个索引的总Size(MB)

这个结果和 EnterpriseDB的测试结果有很大出入。
1)数据加载
 从服务端资源消耗的角度看,是 MongoDB的性能是 PostgreSQL的2倍。但是如果数据加载不能很好的并发展开,让mongoimport成为了瓶颈,那应该算打平。
 另外, EnterpriseDB的 数据加载的测试结果和我的结果差异比较大,可能是因为EnterpriseDB的测试中,数据量超过了系统内存量,IO对测试结果的影响开始显现。

2)数据插入
  服务端 资源消耗 的角度看,两者其实相差不大。EnterpriseDB的测试结果被mongo客户端的性能瓶颈绑架了。

3)数据查询
 对无匹配数据(或少量匹配数据)的索引查询, PostgreSQL的性能是MongoDB的4倍(这一点也有点令人不解,同样是走索引的单点查询,为什么差距就这么大呢?)。
 虽然EnterpriseDB的测试结果也表明PostgreSQL的性能MongoDB 4倍左右,但EnterpriseDB的测试方法是有问题的。

4)数据大小
  MongoDB的数据大小大约是 PostgreSQL的3倍,这和 EnterpriseDB的测试结果是一致的。

PostgreSQL在NoSQL方面的表现确实抢眼。 PostgreSQL不仅是SQL+NoSQL+ACID的完美组合,性能还比 MongoDB技高一筹(分布式集群上MongoDB更有优势)

6. 参考

http://www.enterprisedb.com/postgres-plus-edb-blog/marc-linster/edb-makes-open-call-postgres-nosql-performance-benchmarks
http://blog.163.com/digoal@126/blog/static/16387704020151443228780/
http://blog.163.com/digoal@126/blog/static/16387704020151435825593/
http://blog.163.com/digoal@126/blog/static/1638770402015142858224/
http://www.cnblogs.com/lovecindywang/archive/2011/03/02/1969324.html













你可能感兴趣的:(为什么PostgreSQL比MongoDB还快?)