`
sunxboy
  • 浏览: 2877831 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

MongoDB 在语法上的 5 大缺陷

 
阅读更多

这几天抱怨MongoDB的帖子相当受追捧。大多是关于在特定的数据集,可靠性和分片问题上表现不佳。其中一些博客文章可能是正确的,其他的只是说,最受欢迎的NoSQL的解决方案并没有满足他们的需求。

这篇文章不是其中之一,虽然大多数的文章关注操作部分,基准测试和性能特征,而我想谈谈MongoDB查询接口。没错——编程接口,特别是关于Node.js的,但这个在不同语言平台和Mongo-shell上都差不多。

免责声明:我努力不去恨MongoDB。事实上我每个工作日都在使用MongoDB,它已经成为我全职工作的一部分。我也参与Minimongo的开发,使用内存缓存用纯javascript克隆MongoDB的API。我没有任何理由嘲笑Mongo只是警告大家这些意想不到的问题。他们大多数由David Glasser发现。本文假定您熟悉MongoDB的API。

harrybetter
harrybetter
翻译于 1个月前

0人顶

 

 翻译的不错哦!

1. 哈希对象中key的顺序

比如,你要存储一个简单的文字对象::

1 > db.books.insert({ title: "Woe from Wit", meta: { author: "A. Griboyedov", year: 1823 } });

 

太棒了!现在我们有了一条书籍记录。再比如,以后我们会想找所有1823年出版的作者是 A. Griboyedov 的书。这里不太可能返回多个结果,但至少应该有《 Woe from Wit 》这本书,因为我们刚刚插入了这条记录,对不对?

1 > db.books.find({ meta: { year: 1823, author: "A. Griboyedov" } });
2 < No results returned

 

发生了什么?我们不是刚刚插入了这本书的数据吗?让我们尝试调换key的顺序:

1 > db.books.find({ meta: { author: "A. Griboyedov", year: 1823 } });
2 < { _id: ..., title: "Woe from Wit", meta: { ... } }

 

搞定了!

陷阱: 在MongoDB中key的顺序非常重要,{ a: 1, b: 2 } 和 { b: 2, a: 1 }是不匹配的。

为什么: MongoDB使用叫做BSON的二进制数据格式。在BSON中key的顺序非常重要。注意,JSON对象是一个无序的键/值对集合。

harrybetter
harrybetter
翻译于 1个月前

0人顶

 

 翻译的不错哦!

那么在JavaScript里是怎样的呢?ECMA-262可没有规定(JS属性顺序)这件事。在某些浏览器下(通常是旧的)对属性的顺序不会太在意,这意味着它们可以是任何顺序(只要存在就行)。值得庆幸的是大多数现代浏览器的JavaScript引擎在维护JS属性的顺序(有时甚至在数组中也维护) ,因此实际上我们可以使用node.js来控制它。

更多内容请参阅 John Resig's blog.

问题的答案是:要么给出规范形式(键按字典顺序排序) ,要么就使得你自己的代码中是一致的。

当然,这里有其它的解决方法。使用另一种查询方法(selector),即指定那些特定的属性项(key-path),而不是比较对象的文本信息:

1 > db.books.find({ 'meta.year': 1823, 'meta.author''A. Griboyedov' });

 

这种特殊情况下这样的查询方式是有效地,但请注意,这个查询语句的含义是不同的。

陷阱: 每当你想建立一个拥有多键值索引的数据的时候这种行为是很危险的。

1 > db.books.ensureIndex({ title: 1, 'meta.year': -1 });

 

这样的命令会使得title的优先级会比 meta.year 的优先级高。这在MongoDB中是一个很重要的分析数据的方式。更多内容请参阅MongoDB docs.

Terry_Wang
Terry_Wang
翻译于 1个月前

1人顶

 

 翻译的不错哦!

2. undefined, null and undefined

想必很多人都还记得那个undefined, null 的关系、特性很混乱的时候吧!在JavaScript的世界中undefined、null代表着两个不同的值,严格来说它们是不一样的:undefined!== NULL。当然,在非严格的情况下他们确实相等:undefined == null。有些人很小心的使用它们,而另一部分人将两者随意交替使用。说到底我们的问题是:JavaScript确实存在两个不同但很相似的值。

MongoDB的带来了它带到一个新的水平。BSON里将未定义规定为"deprecated"。 BSON spec规定undefined为“deprecated”.

然而Node.js中的node-native-driver for MongoDB却没有实现它

Node.js目前的版本(2.4.8)特性表明null和undefined是两个相同的值。

1 > db.things.insert({ a: null, b: 1 });
2 > db.things.insert({ b: 2 }); // the 'a' is undefined implicitly
3 > db.things.find({ a: null });
4 < { a: null, b: 1 }
5 < { b: 2 }

 

我不确定node driver for MongoDB中的实现情况,不过看起来像是node driver直接将undefined转换为null,但是这在mongo-shell里是被限制的(因为在MongoDB里undefined和null本来就是两个值--译者注)。

Terry_Wang
Terry_Wang
翻译于 1个月前

1人顶

 

 翻译的不错哦!

在下面的代码中我们定义了三个对象,我们将会得到相同的结果并打印两次。

1 // from node.js code with mongo/node-native-driver
2 db.things.insert({ a: null, b: 1 });
3 db.things.insert({ b: 2 });
4 db.things.insert({ a: undefined, b: 3 });
5 console.log(db.things.find({ a: null }).fetch())
6 console.log(db.things.find({ a: undefined }).fetch())

 

然而,在mongo-shell中你只能使用null来查询,注意,我们所使用的三个对象和上面的是一样的。

1 // from mongo-shell
2 > db.things.find({a: undefined});
3 < error: { "$err" "can't have undefined in a query expression""code" : 13629 }
4 > db.things.find({a: null});
5 < { "a" null"b" : 1, "_id" "wMWNPm7zrYXTNJpiA" }
6 < { "b" : 2, "_id" "RjrYvmZF5EukhpuAY" }
7 < { "a" null"b" : 3, "_id" "kethQ2khbyfFjJ7Sa" }

 

我们可以看到,mongo/node-native-driver 显式的将undefined转换null但实际上左边隐式的那个才是我们真正想要的(我们期望的真实结果)。

当我们使用mongo-shell显式的插入undefined的时候,有趣的事情发生了:

1 // from mongo-shell
2 > db.things.insert({ a: undefined, b: 4 });
3 > db.things.find({ a: null })
4 < { "a" null"b" : 1, "_id" "wMWNPm7zrYXTNJpiA" }
5 < { "b" : 2, "_id" "RjrYvmZF5EukhpuAY" }
6 < { "a" null"b" : 3, "_id" "kethQ2khbyfFjJ7Sa" }

 

我们得到相同的三个值,但并没有我们刚才在mongo-shell里插入的 b=4的对象。undefined不是和null相等吗?好吧,让我们来看看这个新的对象:

1 > db.things.find({ b: 4 });
2 < { "_id" : ObjectId("52ca134f3e47d3d91146f2b5"), "a" null"b" : 4 }

 

它仍然在那里,虽然a属性的值很像是null,但与我们的选择器却不匹配。

陷阱:有2个以上的值在MongoDB中看起来像null: null,undefined以及隐式的向mongo-shell里插入的undefined,虽然看起来像null但在实际情况下和BSON(第6版)中的undefined 相匹配。最后一个在选择器上并不和null匹配,前两者都匹配undefined和null。这也说明了没有值同样可以匹配前两者。

原始问题请参阅 GitHub issue.

Terry_Wang
Terry_Wang
翻译于 1个月前

1人顶

 

 翻译的不错哦!

3. Soft limits, hard limits and no limits

你有一个项目的输入并且允许用户指定数字项目返回。你应该把问题的结果像这样返回:

1 db.items.find({ ... }).limit(N);

N值是由 用户供给的。我们当然希望小心的将用户限制在50之内,否则网络上的任何人只需简单地提供一个非常大的N值都可以下载我们的应用服务器和数据库。

 

1 function getItems (N) {
2   if (N > 50)
3     N = 50;
4   return db.items.find({}).sort({ year: 1 }).limit(N);
5 }

看起来是个有道理运行在你的node.js app上的代码。 

陷阱:如果用户提供0作为一个项目的值,他希望MongoDB可以理解为把所有都给他。

calance
calance
翻译于 28天前

1人顶

 

 翻译的不错哦!

这在文档里写的很清楚,但很多情况下并不是那么显然:在MongoDB中零表示无限制。我猜想MongoDB的代码可能将undefined, null, 0等等所有的false值当做无限制对待。

这没关系,我们可以对0进行单独处理:

1 function getItems (N) {
2   if (N > 50 || !N) // check if N is falsy ("no limit")
3     N = 50;
4   return db.items.find({}).sort({ year: 1 }).limit(N);
5 }

 

看上去不错?但是如果用户输入一个负值怎么办?这可能么?这又意味着什么?

事实上像db.items.find().limit(-1000000000000)这类的语句可能返回非常多的项。很难找到相关的文档,但几个月前我在node.js的驱动文档中看到一篇文章描述了这种行为,它将其表述为“硬”限制和“软”限制。我不知道这是什么意思。

那么我们服务器端方法的最终版就是这样了:

1 function getItems (N) {
2   if (N < 0) N = -N;
3   if (N > 50 || !N) // check if N is falsy ("no limit")
4     N = 50;
5   return db.items.find({}).sort({ year: 1 }).limit(N);
6 }

 

总结: 限制可以是负数。它在广义上和正数是一样的但是负数限制是“软限制”。

DrZ
DrZ
翻译于 23天前

0人顶

 

 翻译的不错哦!

4.数组的特殊待遇

很多人并不知道这个特性,但数组确实是经过特殊处理的。

1 > db.c.insert({ a: [{x: 2}, {x: 3}], _id: "aaa"})
2 > db.c.find({'a.x': { $gt: 1 }})
3 < { "_id" "aaa""a" : [  {  "x" : 2 },  {  "x" : 3 } ] }
4 > db.c.find({'a.x': { $gt: 2 }})
5 < { "_id" "aaa""a" : [  {  "x" : 2 },  {  "x" : 3 } ] }
6 > db.c.find({'a.x': { $gt: 3 }})
7 < Nothing found

 

因此每当有一个数组对象,选择器都会“分发”给每一个元素,这就像“如果其中一个元素匹配,那么整个文档(document)都会被匹配”。

值得注意的是,它并不适用于嵌套数组::

1 > db.x.insert({ _id: "bbb", b: [ [{x: 0}, {x: -1}], {x: 1} ] })
2 > db.x.find({ 'b.x': 1 })
3 < { "_id" "bbb""b" : [  [  {  "x" : 0 },  {  "x" : -1 } ],  {  "x" : 1 } ] }
4 > db.x.find({ 'b.x': 0 })
5 < Nothing found
6 > db.x.find({ 'b.x': -1 })
7 < Nothing found

 

同样也适用于预测数组中字段(field)的一些特性:

1 > db.z.insert({a:[[{b:1,c:2},{b:2,c:4}],{b:3,c:5},[{b:4, c:9}]]})
2 > db.z.find({}, {'a.b': 1})
3 < { "_id" : ObjectId("52ca24073e47d3d91146f2b7"), "a" : [  [  {  "b" : 1 },  {  "b" : 2 } ],  {  "b" : 3 },  [  {  "b" : 4 } ] ] }

 

如果我们在选择器上将以上特性与使用数字键做更多的组合,那么这个特性将变得越来越难以预测:

01 > db.z.insert({a: [[{x: "00"}, {x: "01"}], [{x: "10"}, {x: "11"}]], _id: "zzz"})
02 > db.z.find({'a.x''00'})
03 < Nothing found
04 > db.z.find({'a.x''01'})
05 < Nothing found
06 > db.z.find({'a.x''10'})
07 < Nothing found
08 > db.z.find({'a.x''11'})
09 < Nothing found
10 > db.z.find({'a.0.0.x''00'})
11 < { "_id" "zzz""a" : [     [   {   "x" "00" },   {   "x" "01" } ],     [   {   "x" "10" },   {   "x" "11" } ] ] }
12 > db.z.find({'a.0.0.x''01'})
13 < Nothing found
14 > db.z.find({'a.0.x''00'})
15 < { "_id" "zzz""a" : [     [   {   "x" "00" },   {   "x" "01" } ],     [   {   "x" "10" },   {   "x" "11" } ] ] }
16 > db.z.find({'a.0.x''01'})
17 < { "_id" "zzz""a" : [     [   {   "x" "00" },   {   "x" "01" } ],     [   {   "x" "10" },   {   "x" "11" } ] ] }
18 > db.z.find({'a.0.x''10'})
19 < Nothing found
20 > db.z.find({'a.0.x''11'})
21 < Nothing found
22 > db.z.find({'a.1.x''00'})
23 < Nothing found
24 > db.z.find({'a.1.x''01'})
25 < Nothing found
26 > db.z.find({'a.1.x''10'})
27 < { "_id" "zzz""a" : [     [   {   "x" "00" },   {   "x" "01" } ],     [   {   "x" "10" },   {   "x" "11" } ] ] }
28 > db.z.find({'a.1.x''11'})
29 < { "_id" "zzz""a" : [ [ { "x" "00" }, { "x" "01" } ], [ { "x" "10" }, { "x" "11" } ] ] }

 

好的,我们再来稍作改动。这个和上一个案例的区别仅仅是内部值的改动:在上一个案例中是一个对象,在下面的案例中将会是一个数字。这足以让数组的特性发生改变:

01 > db.p.insert({a: [0], _id: "xxx"})
02 > db.p.find({'a': 0})
03 < { "_id" "xxx""a" : [  0 ] }
04 > db.q.insert({a: [[0]], _id: "yyy"})
05 > db.q.find({a: 0})
06 < Nothing found
07 > db.q.find({'a.0': 0})
08 < Nothing found
09 > db.q.find({'a.0.0': 0})
10 < { "_id" "yyy""a" : [  [  0 ] ] }

 

陷阱: 尽可能的避免数组或者嵌套数组以及其他一对多关系的数据存在于文档之中,并且在需要查询的时候,通常我们倾向于按照一对一关系去查询。然而对于使用数字键(例如{ 'a.0.x': Y }意味着字段a的第一个元素的x字段必须为Y)的混合型文档很可能会让人感觉非常别扭,当然这也取决于数据的复杂程度。

Terry_Wang
Terry_Wang
翻译于 1个月前

0人顶

 

 翻译的不错哦!

5. 地理定位操作符$near

这个操作符很简单。你拥有大量包含位置字段的文档。位置字段表示的是地理位置信息。技巧就是MongoDB可以对两种不同类型的位置信息进行索引,每种类型都有稍微不同的API和行为。

第一种类型如下:

1 db.c.find({
2   location: {
3     $near: [12.3, 32.1],
4     $maxDistance: 777
5   }});

第二种类型如下:

01 db.c.find({
02   location: {
03     $near: {
04       $geometry: {
05         type: "Point",
06         coordinates: [ 12.3, 32.1 ]
07       },
08       $maxDistance: 777
09     }
10   }});

对每个被索引类型来说,地理信息查询语法稍稍有些不同。在通常所用的地理位置信息配对中$maxDistance与$near处于同等地位,而在Geo-JSON表示的地理位置信息配对中$maxDistance就是$near的子元素。 
然而,还不止这些!有时你在结果集中会两次获得同一个位置点!为了能够理解这一点,我们需要回想一下前一个嵌套数组里存在的缺陷。看看下面代码:

1 > db.c.insert({ location: [[1, 2], [1, 0]] }); // inserting an array of two points> db.c.ensureIndex({ location: "2d" });
2 > db.c.find({ location: { $near: [0, 0], $maxDistance: 500 } });
3 < { "_id" : ObjectId("52ca30ec3e47d3d91146f2b8"), "location" : [  [  1,  2 ],  [  1,  0 ] ] }
4 < { "_id" : ObjectId("52ca30ec3e47d3d91146f2b8"), "location" : [  [  1,  2 ],  [  1,  0 ] ] }

从匹配给定选择子的数组里返回同一个位置点两次,认为是两个位置点。

几点人
几点人
翻译于 8天前

0人顶

 

 翻译的不错哦!

在我开始使用Javascript编程的时候这些陷阱给提了醒。这里有一些我们平时不容易察觉的情况,其中一些跨浏览器效果不一致,还有一些你几乎用不到的特性,因此在某些情况下你要格外小心。以上这些都是众所周知的JavaScript领域中的问题,但在MongoDB领域中也没有处理的那么好。

几乎所有怪异的特性都在模拟MongoDB的过程中发现,然后整理并列举在这里,这个模拟MongoDB的项目叫做Minimongo, 主要由 David Glasser贡献.

分享到:
评论

相关推荐

    Thinkphp使用mongodb数据库实现多条件查询方法

    Thinkphp是一个基于PHP的轻量级框架...这包括了添加或修改查询方法以适应MongoDB的查询语法,以及处理框架内部潜在的bug。这些改动可以帮助Thinkphp在结合MongoDB使用时,为开发者提供更加强大和灵活的数据库查询能力。

    消失的MongChef 版本

    5. 脚本支持:MongoChef允许用户编写和执行JavaScript脚本,直接在界面上对数据库进行更复杂的操作。 6. 安全性:用户可以管理数据库的认证和授权,设置用户权限,确保数据库安全。 7. 多平台兼容:MongoChef支持...

    scrum-app:Scrum 应用程序、node.js、mongoDB

    此外,可能会有诸如Mongoose之类的库,用于在MongoDB上进行数据操作,提供ORM(对象关系映射)功能,简化数据库操作。前端部分可能使用了React或Vue等库,构建用户界面,这些库能够创建动态、交互性强的组件。 开发...

    面试万能小全库.zip

    5. 浏览器兼容性:了解不同浏览器对某些特性的差异,如CSS3新特性、JavaScript ES6+语法等。 6. jQuery与框架:理解jQuery简化DOM操作的优势,以及Vue、React、Angular等主流框架的基本概念和应用场景。 7. 响应式...

    实验四.rar

    RAR格式支持分卷压缩,这意味着大文件可以分割成几个小文件,方便在有限的存储介质上分批传输。 2. **解压工具**:要访问"实验四.rar"的内容,你需要一个支持RAR格式的解压工具,如WinRAR、7-Zip或Bandizip。这些...

    软件学院方向介绍

    在当今信息化社会,软件行业的发展日新月异,为各行各业提供了强大的技术支持。为了帮助朋友们更好地理解和选择适合自己的软件就业方向,我们对以下几个主要领域进行了深入的解析:Java方向、嵌入式方向、测试方向、...

    03173软件开发工具.rar

    10. **数据库管理**:熟悉SQL语言,了解如MySQL、Oracle等数据库管理系统,以及NoSQL数据库如MongoDB,是处理数据存储和检索的关键。 通过学习《03173软件开发工具》中的课件和笔记,自考计算机本科的学生可以系统...

    葵花宝典V3.CHM

    源码部分可能涵盖了多种编程语言,例如C++、Java、Python、JavaScript等,这些语言的基础语法、进阶特性以及实战技巧都有可能被讨论。同时,它也可能包含框架和库的使用,如Spring、React或Django等,帮助读者了解...

    mystik:Mystik (MkTable) 是用于数据库系统的通用 Javascript DAO(数据访问对象),包括 NoSQL 数据库、Redis、NodeJS 支持和 REST API Web 服务处理

    它还允许在客户端和服务器端使用相同的查询语法来获取数据。 数据可以来自 JSON 格式的数据库或 API。 注意:所有工作功能都包含在单元测试中。 请帮助实现其他数据库的引擎或修复缺陷。 谢谢。 安装 $ npm ...

    软件设计师中级历年真题.zip

    NoSQL数据库也可能出现在题目中,如MongoDB或Redis。 四、操作系统 操作系统原理和应用也是考试的重点,如进程管理、内存管理、文件系统、网络编程接口(如套接字编程)等。理解操作系统如何影响软件运行性能和安全...

    bugzGalore

    在软件开发中,"bug"通常指的是程序中的错误或缺陷,可能导致程序行为不正确或者完全崩溃。 在JavaScript中,错误类型可以分为以下几类: 1. ReferenceError:当尝试引用一个不存在的变量时,会抛出此类错误。 2. ...

    聊天应用

    【聊天应用】是一种常见的软件类型,它允许用户通过文本、语音、视频等多种方式实时交流。...通过学习和实践,开发者能够掌握如何构建自己的聊天应用,并在此基础上进行扩展和定制,满足不同场景的需求。

    软件测试知识小结

    - **集成测试**:在单元测试基础上,将所有模块按照概要设计要求组装成子系统或系统,进行集成测试。 - **系统测试**:把经过测试的子系统装配成一个完整的系统来测试。 - **验收测试**:也称为交付测试,是在系统...

    害虫计划支持

    在IT行业中,"害虫计划支持"可能是指一个软件项目或者工具,其目的是为了管理和跟踪问题,特别是软件开发过程中的bug或缺陷。由于标签提到了"TypeScript",我们可以推测这是一个使用TypeScript编写的后端主项目,...

    EpamSys课程

    2. **编程语言**:EpamSys可能会涵盖多种编程语言,如Java、Python、C#等,教授语法、面向对象编程、异常处理和性能优化等。 3. **Web开发框架**:介绍前端技术如HTML、CSS、JavaScript,以及React、Vue或Angular等...

    SEP2_GROUP:与SEP2项目相关的互助库

    NoSQL数据库如MongoDB也可能在某些场景下被采用。 10. **Docker和Kubernetes**:为了实现容器化部署和集群管理,项目可能使用Docker封装应用,而Kubernetes则用于容器编排和集群管理。 11. **文档编写**:清晰的...

    软件工程——思维导图

    软件工程师在开发过程中需要考虑到跨平台兼容性,确保软件在不同的系统平台上能够正常运行。 软件工程领域还涉及到了多种标准和规范,它们是软件开发过程中的重要依据,保证了软件开发的秩序和产品的质量。例如,...

    BugTracker:BugTracker应用程序

    BugTracker是一款用于软件开发过程中管理错误和缺陷的工具,它使得团队成员能够有效地跟踪、记录和解决代码中的问题。作为一个基于JavaScript的应用程序,BugTracker充分利用了Web技术的灵活性和交互性,提供了一个...

Global site tag (gtag.js) - Google Analytics