瞬间完成
MongoDB的插入、删除和更新都是瞬间完成的,这是因为它们都不需要等待数据库响应。客户端将文档发送给服务器后就立刻干别的了,客户端永远不会收到”好的,知道了”或者”有问题,能重新传送一遍吗”这类响应。
这个特点的优点很明显,速度快,这些操作都会非常快地执行,它只会受客户端发送的速度和网络速度的制约。通常会工作得很好,但有时也会出岔子:服务器崩溃了,网线被老鼠咬断了,数据中心被洪水淹了。在没有服务器的情况下,客户端还是会发送写操作到服务器的,完全不理会到底有没有服务器。这对有些是可以接受的,由于硬件故障丢失几秒钟的日志记录、用户点击或者分析数据没什么大不了的,但是对于另一些应用(如付费系统),这样就不好玩了
.1假设要完成一个电商系统,某人订购了某物,应用程序应该花点时间确保订单顺利。这就是为什么要给这些操作弄个”安全”版本,执行时检查到了错误还可以重来。
MongoDB的开发者选择了不安全的版本作为默认选择,这是由于他们与关系型数据库打资产的经验所导致的。很多构建在关系型数据库之上的应用程序都根本不关心返回的代码,也不会检查返回码,但又得苦苦等待这个返回码,这会造成性能的极大下降。MongoDB让用户来选择。这样,像一些收集日志记录或者实时数据分析的程序,就不用等待它们根本不在乎的返回码了。
安全的版本在执行完了操作后立即运行getLastError命令,来检查是否执行成功。驱动程序会等待数据库响应,然后适当处理错误,一般会抛出一个可被捕获的异常。这样,开发者就能用自己的语言以比较自然的方式捕获并处理数据库错误了。要是操作成功,getLastError会给出额外的信息作为响应(例如,对于更新或删除操作,会给出受到影响的文档数量)。
“安全”的代价就是性能。即使忽略客户端处理异常的开销(不同语言的开销不一样,但一般都不是轻量级的),等待数据库响应本身的时间比只发送消息的时间多一个数量级。所以,应用程序需要权衡数据的重要性(以及丢失后的后果)及速度需求。
.2捕获”常规”错误
安全操作不仅能对付前面那种世界末日的场景,也是一种调试数据库”奇怪”行为的好方法。即使安全操作最后会在生产环境中移除,但是在开发过程中还是应该大量地使用。这样可以避免很多常见的数据库使用错误,最常见的就是键重复的错误。
键重复错误经常发生在试图插入一个其”_id”值已被占用的文档。MongoDB中不允许在一个集合里面有多个”_id”值一样的文档。如果做的是安全插入,发生了键重复错误,安全检查会发现这个服务器错误,并抛出异常。在不安全模式下,数据库没有响应,所以可能根本就不知道插入失败了。
注意:shell总会检查错误,而驱动程序中检查是可选项。
请求和连接
数据库会为每一个MongoDB数据库连接创建一个队列,存放这个连接的请求。当客户端发送一个请求,会被放到队列的末尾。只有队列中的请求都执行完毕,后续的请求才会执行。所以从单个连接就可以了解整个数据库,并且它总是能读到自己写的东西。
注意:每个连接都有独立的队列,要是打开两个shell,就有两个数据库连接。在一个shell中执行插入,之后在另一个shell中进行查询不一定能得到插入的文档。然而,在同一个shell中,插入后再进行查询是一定能查到的。手动复现这个行为并不容易,但是在繁忙的服务器上,交错的插入/查找就显得稀松平常了。当开发者用一个线程插入数据,用另一个线程检查是否成功插入时,就会经常遇到这种问题,有那么一两秒钟时间,好像根本就没有插入数据,但随后数据又突然冒出来。
使用Ruby、Python和Java驱动程序要特别注意这种行为,因为这几个语言的驱动程序都使用了连接池。为了提高效率,这些驱动程序都和服务器建立了多个连接(一个连接池),并将请求分散到这些连接中去。好在它们都提供了一些机制来确保一系列的请求都遇一个连接来处理。MongoDB wiki上有不同语言连接池的详细信息。