热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[译]MongoDB在语法上的5大缺陷

前几天翻译了一篇MongoDB的技术文章,作者提到了几个MongoDB应用中所存在的缺陷,并且用实例来说明了几个缺陷的由来和产生情景,这里截取了三个我比较想跟大家分享的,希望对大家有帮助。有兴趣可以

前几天翻译了一篇MongoDB的技术文章,作者提到了几个MongoDB应用中所存在的缺陷,并且用实例来说明了几个缺陷的由来和产生情景,这里截取了三个 我比较想跟大家分享的,希望对大家有帮助。有兴趣可以阅读作者原文,对文章创作者予以支持。
另外就是在文章翻译完毕,发现MongoDB还有很多要说的,在这里埋个坑,有时间写几篇关于MongoDB的文章跟大家交流下。
文章作者 :Slava Kim
原文 : Top 5 syntactic weirdnesses to be aware of in MongoDB


这几天抱怨MongoDB的帖子相当受追捧。大多是关于在特定的数据集,可靠性和分片问题上表现不佳。其中一些博客文章可能是正确的,其他的只是说,最受欢迎的NoSQL的解决方案并没有满足他们的需求。
这篇文章不是其中之一,虽然大多数的文章关注操作部分,基准测试和性能特征,而我想谈谈MongoDB查询接口。没错——编程接口,特别是关于Node.js的,但这个在不同语言平台和Mongo-shell上都差不多。
免责声明:我努力不去恨MongoDB。事实上我每个工作日都在使用MongoDB,它已经成为我全职工作的一部分。我也参与Minimongo的开发,使用内存缓存用纯Javascript克隆MongoDB的API。我没有任何理由嘲笑Mongo只是警告大家这些意想不到的问题。他们大多数由David Glasser发现。本文假定您熟悉MongoDB的API。

1. 哈希对象中key的顺序

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

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

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

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

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

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

竟然可以!
陷阱: 在MongoDB中key的顺序非常重要,{ a: 1, b: 2 } 和 { b: 2, a: 1 }是不匹配的。
为什么: MongoDB使用叫做BSON的二进制数据格式。在BSON中key的顺序非常重要。注意,JSON对象是一个无序的键/值对集合。
那么在Javascript里是怎样的呢?ECMA-262可没有规定(JS属性顺序)这件事。在某些浏览器下(通常是旧的)对属性的顺序不会太在意,这意味着它们可以是任何顺序(只要存在就行)。值得庆幸的是大多数现代浏览器的Javascript引擎在维护JS属性的顺序(有时甚至在数组中也维护) ,因此实际上我们可以使用node.js来控制它。
更多内容请参阅John Resig's blog.
其实我们更加期望得到的答案是:要么给出规范形式(键按字典顺序排序) ,要么就让你自己的代码保持一致的。
当然,这里有其它的解决方法。使用另一种查询方法(selector),即指定那些特定的属性项(key-path),而不是比较对象的文本信息:

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

这种特殊情况下这样的查询方式是有效地,但请注意,这个查询语句的含义是不同的。
陷阱: 每当你想建立一个拥有多键值索引的数据的时候这种行为是很危险的。

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

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

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是两个相同的值。

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

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

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

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

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

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

我们可以看到,mongo/node-native-driver 显式的将undefined转换null但实际上左边隐式的那个才是我们真正想要的(我们期望的真实结果)。
当我们使用mongo-shell显式的插入undefined的时候,有趣的事情发生了:

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

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

> db.things.find({ b: 4 });
<{ "_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.

3.数组的特殊待遇

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

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

因此每当有一个数组对象,选择器都会“分发”给每一个元素,这就像“如果其中一个元素匹配,那么整个文档(document)都会被匹配”。
值得注意的是,它并不适用于嵌套数组:

> db.x.insert({ _id: "bbb", b: [ [{x: 0}, {x: -1}], {x: 1} ] })
> db.x.find({ 'b.x': 1 })
<{ "_id" : "bbb", "b" : [ [ { "x" : 0 }, { "x" : -1 } ], { "x" : 1 } ] }
> db.x.find({ 'b.x': 0 })
> db.x.find({ 'b.x': -1 })

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

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

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

> db.z.insert({a: [[{x: "00"}, {x: "01"}], [{x: "10"}, {x: "11"}]], _id: "zzz"})
> db.z.find({'a.x': '00'})
> db.z.find({'a.x': '01'})
> db.z.find({'a.x': '10'})
> db.z.find({'a.x': '11'})
> db.z.find({'a.0.0.x': '00'})
<{ "_id" : "zzz", "a" : [ [ { "x" : "00" }, { "x" : "01" } ], [ { "x" : "10" }, { "x" : "11" } ] ] }
> db.z.find({'a.0.0.x': '01'})
> db.z.find({'a.0.x': '00'})
<{ "_id" : "zzz", "a" : [ [ { "x" : "00" }, { "x" : "01" } ], [ { "x" : "10" }, { "x" : "11" } ] ] }
> db.z.find({'a.0.x': '01'})
<{ "_id" : "zzz", "a" : [ [ { "x" : "00" }, { "x" : "01" } ], [ { "x" : "10" }, { "x" : "11" } ] ] }
> db.z.find({'a.0.x': '10'})
> db.z.find({'a.0.x': '11'})
> db.z.find({'a.1.x': '00'})
> db.z.find({'a.1.x': '01'})
> db.z.find({'a.1.x': '10'})
<{ "_id" : "zzz", "a" : [ [ { "x" : "00" }, { "x" : "01" } ], [ { "x" : "10" }, { "x" : "11" } ] ] }
> db.z.find({'a.1.x': '11'})
<{ "_id" : "zzz", "a" : [ [ { "x" : "00" }, { "x" : "01" } ], [ { "x" : "10" }, { "x" : "11" } ] ] }

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

> db.p.insert({a: [0], _id: "xxx"})
> db.p.find({'a': 0})
<{ "_id" : "xxx", "a" : [ 0 ] }
> db.q.insert({a: [[0]], _id: "yyy"})
> db.q.find({a: 0})
> db.q.find({'a.0': 0})
> db.q.find({'a.0.0': 0})
<{ "_id" : "yyy", "a" : [ [ 0 ] ] }

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

由此,在我开始使用Javascript编程的时候这些陷阱给我提了个醒。这里有一些我们平时不容易察觉的情况,其中一些诸如:跨浏览器导致结果不一致,还有一些你几乎怎么都用不到的特性却突然要拿来使用,因此你要格外小心。有些是众所周知的Javascript领域中的问题,但在MongoDB中也没有处理的那么好。
几乎所有怪异的特性我们都在模拟实现MongoDB的过程中发现,然后整理并列举在这里,这个模拟MongoDB的项目叫做Minimongo, 主要由David Glasser贡献.



文/Terry_Wang(简书作者)
原文链接:http://www.jianshu.com/p/7c530fae9540
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

推荐阅读
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • Python实现变声器功能(萝莉音御姐音)的方法及步骤
    本文介绍了使用Python实现变声器功能(萝莉音御姐音)的方法及步骤。首先登录百度AL开发平台,选择语音合成,创建应用并填写应用信息,获取Appid、API Key和Secret Key。然后安装pythonsdk,可以通过pip install baidu-aip或python setup.py install进行安装。最后,书写代码实现变声器功能,使用AipSpeech库进行语音合成,可以设置音量等参数。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • 本文介绍了OkHttp3的基本使用和特性,包括支持HTTP/2、连接池、GZIP压缩、缓存等功能。同时还提到了OkHttp3的适用平台和源码阅读计划。文章还介绍了OkHttp3的请求/响应API的设计和使用方式,包括阻塞式的同步请求和带回调的异步请求。 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • 解决Sharepoint 2013运行状况分析出现的“一个或多个服务器未响应”问题的方法
    本文介绍了解决Sharepoint 2013运行状况分析中出现的“一个或多个服务器未响应”问题的方法。对于有高要求的客户来说,系统检测问题的存在是不可接受的。文章详细描述了解决该问题的步骤,包括删除服务器、处理分布式缓存留下的记录以及使用代码等方法。同时还提供了相关关键词和错误提示信息,以帮助读者更好地理解和解决该问题。 ... [详细]
  • Python脚本编写创建输出数据库并添加模型和场数据的方法
    本文介绍了使用Python脚本编写创建输出数据库并添加模型数据和场数据的方法。首先导入相应模块,然后创建输出数据库并添加材料属性、截面、部件实例、分析步和帧、节点和单元等对象。接着向输出数据库中添加场数据和历程数据,本例中只添加了节点位移。最后保存数据库文件并关闭文件。文章还提供了部分代码和Abaqus操作步骤。另外,作者还建立了关于Abaqus的学习交流群,欢迎加入并提问。 ... [详细]
author-avatar
顺大顺麻麻1009_388
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有