zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

Mongodb后台创建索引的问题

MongoDB后台索引 创建 问题
2023-09-27 14:27:54 时间
Mongodb创建索引分为前台和后台,前台创建索引将阻塞所有对该Collection的读写操作,后台创建索引不阻塞对该Collection的读写操作,那么问题来了,后台创建索引的同时,Collection正在被读写,如何保证索引创建的准确性呢?

后台创建索引,遍历Collection表的所有数据之前,会先把数据库的锁从MODE_X变成MODE_IX, (关于数据库多级锁的概念,https://en.wikipedia.org/wiki/Multiple_granularity_locking), 从而允许其他客户端对数据库读写操作.一面做全表扫面,一面做数据的更新,如果保证索引和数据能对的上呢?

事实上Mongodb是不提供事务保证的,只对单个文档的操作提供了原子操作保证,所以如果执行一个update多个值的操作,不能保证要更新的多个值的原子性.假如现在更新操作要更改两个值(key1,value1),(key2,value2),你在更新(key1,value1)时候,有可能另一个人更改了(key2,value2),等你再去更新(key2,value2)的时候,由于你获取的是原来的版本,所以更新时候会产生WCE(写冲突异常),你需要重新获取(key2,value2)的新版本,在新版本基础上再执行更新操作.事实上,Mongodb在对每个值做操作之前都会重新获取一次这个值的最新版本.就WiredTiger存储引擎而言,每次获取一个新版本,相当于创建了一个新的快照,在这个快照基础上做一次事务.快照隔离级别对应ANSI SQL定义的可重复读隔离级别.了解了这些我们再说后台创建索引的问题.

我们将创建索引和对数据的读写操作的竞争分为几种场景来描述:

a 对key已经创建了索引,对key做修改操作
b 对key还没创建索引,对key做修改操作
c 对key做创建索引的同时,对key做修改操作

从上面的三个场景来看,a,b是不会有竞争条件出现的,两个操作是一前一后执行.

a 的情况key已经做了索引的创建,对key的修改操作会先删除key的旧值索引,再对key的新值做索引.
b 的情况key还没有创建索引,对key的修改操作先删除老的索引,因为还没有索引,所以删除返回WT_NOTFOUND,不做处理.再对key的新值做索引.等后台创建索引操作为这个key创建索引的时候拿的是key的新版本创建索引,因为索引已经创建所以会产生写入冲突,跳过为这key创建索引,继续为下一个key创建索引

对于问题c是我们要讨论的竞争条件,这个问题曾经引发Mongodb索引创建的bug,目前已经修改,我们来描述这个bug的产生条件

During an index build, the collection is locked in mode IX. Consider a background index build occurring with concurrent updates. Then, the following race scenario could occur:

Both the updater and the index builder read the same document. To perform the update, the updater unindexes the documents old value. Because the document has yet to be indexed, the WiredTiger returns WT_NOTFOUND. No WCE is generated. The update proceeds to index the new value. The indexer in the background, unaware of any WCEs, indexes the value it has read. The result is that the index has one too many keys.
We can fix this by having the update path trigger a write conflict exception even in the "no-op" case when unindexing returns WT_NOTFOUND.

(An alternative is to simply lock the collection in X mode during an index build, but this will probably have a large impact on performance.)

以上描述的场景,最终产生的结果是,更新操作为document的新版本创建一个索引,后台创建索引操作会为document老版本创建一个索引.显然这是错误的,错误产生的原因是更新操作为新值创建了索引,后台创建索引操作为老值创建了索引,两个操作读取的数据集合相同,但是更新的数据集合没有交集,所以两个快照同时操作不会产生写冲突.更新操作修改的数据集合是(NewValue),后台创建索引修改的数据集合是(OldValue).

但是明明是操作的同一个key,这个冲突应该产生才对,为了解决这个问题,Mongodb的开发者在步骤2)上删除老索引返回WT_NOTFOUND的时候,故意插入老版本数据的索引,再删除它,这样更新操作修改的数据集合是(OldValue,NewValue),后台创建索引修改的数据集合是(OldValue),因为对修改了OldValue就使更新操作和后台创建索引操作产生写冲突,后台创建索引操作会跳过为这key创建索引,继续为下一个key创建索引

这个bug,详见
https://jira.mongodb.org/browse/SERVER-23807


MongoDB系列-在复制集(replication)以及分片(Shard)中创建索引 在使用MongoDB时,在创建索引会涉及到在复制集(replication)以及分片(Shard)中创建,为了最大限度地减少构建索引的影响,在副本和分片中创建索引,使用滚动索引构建过程。
技能学习:学习使用Node.js + Vue.js,开发前端全栈网站-4.使用axios,并创建接口上传数据到mongodb数据库 根据我的业务范围,设想网站内容分为三类,设计、开发、小程序,设计中有二级分类平面设计、UI设计,平面设计又有三级分类包括VI设计、名片设计、LOGO设计…之后网站发布的不管是文章还是合作订单,全部归属到这些类别中,这样就需要用到数据的关联。