最近我花了一个星期研究NoSQL,我有一个400万记录的MySQL速度比较慢,准备迁移到NoSQL上,因为主要是一些JSON格式的文档,本来序列化存在MySQL中的,因为看上了Erlang这个号称非常牛X的平台,挑中了CouchDB,但导入40万有效测试数据,在本地运行的时候,发现速度特别慢(本地机器为2011年的iMac,配备16G内存,根本不弱的),而且查询功能很弱,可能还没到成熟的时候。
CouchDB特点:
面向文档数据库,不需要范式,直接存储JSON就可以,CouchDB默认会生成 _id,_rev 两个键,_id是一条记录(文档)的唯一标识,如果不提供_id,_id会自动生成,也可以手动指定_id,比如用手机号做主键:
{'_id' : '+86186*****', name: '' }
_rev是其版本号,每更新一次 _rev就会自动发生变化,格式为
5-6a8617596d2adfea245662df0df611ao
,标识第5个版本,后面是HASH签名,可以通过_rev寻找到所有的历史版本,所以用来做需要存储版本的文档系统应该非常不错,比如多人协作修改一篇文档等应用。
CouchDB提供RESTful接口访问,只需要执行http请求就能完成增删改功能,好处是,只需要命令行cURL就可以开始工作了,不需要特别的驱动,比如:获取一篇文档:
curl -X GET http://127.0.0.1:5984/dbname/_id
就可以了,而坏处是,http协议的效率并不高,尤其是如果执行一个页面需要多个请求的时候,用MySQL可以公用一个打开的链接就可以不断执行查询,而用http?得想想怎么样让http复用一个socket链接,所以越是入门简单,后面的麻烦就越多!
CouchDB的查询功能非常弱,CouchDB如何执行查询呢?不同于MySQL,扔一条SQL过去就得了。得为每一次查询创建一个view,view的格式:
{"map": "function(doc){ emit( key, value); } "}
,这里面的function必须是一个字符串,而不能是javascript的合法语句,所以你写一个稍微复杂一点的语句,得先保证在node/console里调试好,然后把他转换成字符串,然后再构造出合法的json字符串post过去,这个过程非常的痛苦。而且这个view在第一次查询的时候速度会慢的吓死人,40万条数据,执行一次需要的时间大概要5分钟左右,400万条,我就不敢测试了,但stackoverflow上有网友说他跑了4个小时还没跑出结果来,因为这个map可不存在什么优化的地方,map就是一条条在运行,所以不管做什么查询,都要遍历所有doc,能不慢么!
另外,view其实也是像其他普通的记录一样实实在在存在数据库里的,可以通过 _utils 看到,view中是不可以传递变量的(临时view可以),比如在一个库里存在 member 数据有 name, email, city等,需要查询 city为"shenzhen"的member,那么怎么办?第一用临时view: temp_view,构造动态的查询map语句:
curl http://127.0.0.1:5984/dbname/_temp_view?include_docs=true -H 'Content-Type: application/json' -d / '{"map": "function(doc){ if(doc.city.match(/shenzhen/) ) emit(doc._id, 1 ); }" }'
最好把这个 _temp_view 存储成永久的view,否则每次查询一个新的city都会很慢的,一旦执行过一次查询,后面的访问就会比较快,但是前提是:得执行过一次查询!
但如果存储为永久view,就需要写死 shenzhen这个字符串在view中,这种传递变量的办法显然是不可取的,非常呆板,难道我要查询一个 city为 shenzhen的就得新建一个 view,那么全国那么多city,是不是每一个都要新建一个city?view这种方式查询真的很愚蠢!
除了temp_view外,到底有没有办法可以动态传递参数的?可以啊 startkey 和 endkey啊,但这两个参数光从名字就可以看出设计的是多么愚蠢了。
好这样查询,先创建一个view,保存为 member/city_query
'{"map": "function(doc){ emit([doc.city, doc._id], 1 ); }" }'
接下来就可以痛快查询啦:
curl / http://127.0.0.1:5984/dbname/_design/member/_view/city_query?startkey=[shenzhen]&endkey=[shenzhen] -H 'Content-Type: application/json'
这个语句是不能运行的,需要手动将中括号 [] 转义。看就是这么无聊。
执行的过程是这样的:startkey传递了一个数组参数,只有一个值 shenzhen,在view的map里,emit不再是一个普通的key,而是一个数组,startkey第一个值对应于emit的第一个参数中的第一位即 doc.city, 结果就会检索 city的值>=shenzhen,再指定一个endkey就OK了啊!可以想想,这个map还是会遍历所有的记录!
这样的设计的很低级,而且非常不好用,如果我要查询 正则匹配怎么办?老老实实创建一个新的view? 结果就是如果你的查询比较多样化,里面的view可能比数据还多!
最终我的结论是,不要在大数据上尝试CouchDB,不要在需要频繁查询的地方使用CouchDB,不要在需要大量汇总、分析数据的地方使用CouchDB,他只适合最多几千条数据的小博客、小文档系统,并且不需要各种花式查询的地方,他的性能不如想想中来的那么畅快,而且CouchDB内部存储就是实实在在的文件而已,没有什么优化,提升查询速度也不是CouchDB最近的目标,他们更多关注在功能上而非性能。并且CouchDB的开发初衷是Apache基金会的一厢情愿,并非工程需求,所以如果选择NoSQL,要尽可能找在工程需求中开发出来的数据库。