Mongodb数据建模
文档结构:内嵌(Embedded data)和引用(Reference)
数据关系 | 选用结构 | 特点 |
---|---|---|
一对一 | 嵌套 | 内容固定,小数据量,选用嵌套模式查询快捷方便 |
一对多 | 嵌套 | 小数据量,选用嵌套模式可以一次查询完所有数据 |
一对多 | 引用 | 大数据量,超过16m文件大小,选用引用模式避免过大 |
多对多 | 引用? | 拆分成两个一对多 |
树形 | 父链接 | 根据查询需要,查子树要多个查询 |
树形 | 子链接 | 经常用于图存储 |
树形 | 祖先队列 | 能快速的找到子树 |
树形 | 物化路径 | 祖先模式略慢于物化路径模式 |
树形 | 集合模型 | 和前两者不同,适合静态树,不能修改 |
官方示例:https://docs.mongodb.com/manual/applications/data-models/
索引
数据关系 | 特点 |
---|---|
_id索引 | 绝大多数集合默认建立的索引;在该集合中是唯一的。 |
单键索引 | 值为一个单一的值,例如字符串,数字或者日期 |
多键索引 | 值具有多个记录,例如数组 |
复合索引 | 查询条件不止一个 |
过期索引 | 在索引过期后,会自动删除。适合存储一些在一段时间之后会失效的数据比如用户的登录信息、存储的日志 |
唯一索引 | 唯一索引只允许一条索引字段为空的记录存在,之后就不允许插入 |
稀疏索引 | 当 字段在文档中不存在,或为空值,则不进入索引,及不存成null,能提高性能 |
全文索引 | 索引类型:2d索引用来存储和查找平面上的点,2dsphere索引,用于存储和查找球面上的点; 查找方式:查找距离某个点一定距离内的内的点,查找包含在某区域内的点 |
地理位置索引 | 能快速的找到子树 |
哈希索引 | 祖先模式略慢于物化路径模式 |
tips:过期索引
1. 存储在过期索引字段的值必须是ISODate,不能使用时间戳
2. 过期索引不能使用复合索引
3. 删除时间不是精确的,删除过程是由后台程序需每60s跑一次,存在误差
tips:全文索引
1. 一个集合只能有一个全文索引
2. 每次查询,只能指定一个
text查询3.
text查询不能出现在nor查询中
4. 查询中如果包含了$text,hint不再起作用
5. 对中文全文索引支持不友好
tips:稀疏索引的适用场景
一种是希望在并非出现在集合所有文档内的字段上增加唯一性索引时。举例来说,你明确希望在每个产品的sku字段上增加唯一性索引。但是出于某些原因,假设产品在还未分配sku时就加入系统了。如果sku字段上有唯一性索引,而你希望插入多个没有sku的产品,那么第一次插入会成功,但后续插入都会失败,因为索引里已经存在一个sku为null的项了。这种情况下密集型索引并不适合,你所需要的是稀疏索引。
另一种适用稀疏索引的情况:集合中大量文档都不包含被索引键。例如,假设允许对电子商务网站进行匿名评论。这种情况下,半数评论都可能缺少user_id字段,如果那个字段上有索引,那么该索引中一半的项都会是null。出于两个原因,这种情况的效率会很差。第一,这会增加索引的大小。第二,在添加和删除带null值user_id字段的文档时也要求更新索引。如果很少(或不会)对匿名评论进行查询,那么可以选择在user_id上构建一个稀疏索引。
操作命令:
> db.test_u.getIndexes() //_id索引
> db.test_u.findOne()
> db.test_u.ensureIndex({y:1}) //单键索引
> db.test_u.find({y:99})
> db.test_u.insert({x:[1,2,3,4,5]})//多键索引
> db.test_u.ensureIndex({x:1,y:1})//复合索引
> db.test_u.find({x:100,y:99})
> db.test_time.insert({time:new Date()})//过期索引
> db.test_time.ensureIndex({time:1},{expireAfterSeconds:10})
> db.articles.ensureIndex({key:"text"})//全文索引
> db.articles.ensureIndex({key_1:"text",key_2:"text"})
> db.articles.ensureIndex({"$**":"text"})
> db.articles.insert({"article":"aa bb cc dd ee"})
> db.articles.insert({"article":"aa bb ff gg"})
> db.articles.insert({"article":"aa bb cc hh ii"})
> db.articles.find({$text:{$search:"aa bb"}})
> db.articles.find({$text:{$search:"aa bb -cc"}})
> db.articles.find({$text:{$search:"\"aa\" \"bb\" \"cc\""}})
> db.articles.insert({"article":"aa bb"})//全文索引相似度
> db.articles.find({$text:{$search:"aa bb"}},{score:{$meta:"textScore"}})
> db.articles.find({$text:{$search:"aa bb"}},{score:{$meta:"textScore"}}).sort({score:{$meta:"textScore"}})
> db.test_u.ensureIndex({x:1,y:1,z:1},{name:"I_comp"})//命名索引名字
> db.test_u.dropIndex({x:1,y:1,z:1},{name:"I_comp"})//删除索引
> db.test_u.ensureIndex({x:1,y:1,z:1},{name:"I_multi"})
> db.test_u.dropIndex("I_multi")
> db.test_u.ensureIndex({x:1,y:1,z:1},{name:"I_multi",unique:true})//唯一索引
> db.test_u.ensureIndex({x:1,y:1,z:1},{name:"I_multi",sparse:true})//稀疏索引
索引创建分析
好处:加快查询速度
坏处:增加磁盘空间消耗,降低写入性能
如何评判索引构建情况
1.mongostat工具
[mongodb_study]#./bin/mongostat -h 127.0.0.1:12345
>for(i=0;i<100000;i++)db.test_1.insert({x:i})
2.profile集合
> db.getProfilingStatus()
> db.getProfilingLevel()
> db.setProfilingLevel(2)//2表示记录所有,0表示不记录
> show tables
> db.system.profile.find()//查找profile集合
> db.system.profile.find().sort({$natural:-1}).limit(1)
3.mongo日志
配置verbose参数:v到vvvvv
4.explain分析
> db.system.profile.find().sort({$natural:-1}).limit(5).explain()
写操作的原子性和数据一致性
1.mongo数据库连接
要考虑mongo数据一致性和保证写操作的原子性,首先要了解mongo的内部机制。服务器为每个数据库连接维护一个请求队列。最新的请求会放在队尾。
如果打开两个shell,连接到相同数据库,这时就存在两个连接,对应不同的队列。如果在一个shell里做写操作,另一个shell做查询,新插入数据可能不再查询结果中。想动手实现很难,但在一个频繁执行插入和查询的服务器上很可能会发生。
使用ruby、python、java驱动程序的尤其要注意该问题,三者都有自己的连接池。其实mongo已经是一个现成的连接池了,而且线程安全。这个内置的连接池默认初始了10个连接,每一个操作(增删改查等)都会获取一个连接,执行操作后释放连接。因此不要自己再创建连接池了。可以通过驱动程序的配置:
- connectionsPerHost:每个主机的连接数
- threadsAllowedToBlockForConnectionMultiplier:线程队列数,它以上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出“Out
of semaphores to get db”错误。- maxWaitTime:最大等待连接的线程阻塞时间
- connectTimeout:连接超时的毫秒。0是默认和无限
- socketTimeout:socket超时。0是默认和无限
- autoConnectRetry:这个控制是否在一个连接时,系统会自动重试
MongoOptions opt = mongo.getMongoOptions();
opt.connectionsPerHost = 10 ;//poolsize
opt.threadsAllowedToBlockForConnectionMultiplier = 10;
更多查看http://mongodb.github.io/mongo-java-driver/3.4/javadoc/
2.mongo的副本备份
副本集的数据可能不是最新的,这会导致读取到的数据是一秒钟之前甚至一分钟之前的。处理这种问题的方式有几种,最简单的一种是将所有的读取请求都发到主数据库。也可以写个脚本自动检测副本集是否落后于主数据库,如果落后,将副本集设置为维护状态。
文档增长
文档增长主要表现在往一个数组push数据和增加新列field。如果使用MMAPv1 存储引擎,每个文档会有预分配空间,当空间满了会分配新的空间。分配空间的策略是双倍增长(比如32,64,128,256,512,1MB,2MB….),但上限是16MB。
将数据放同一文档的缺点:
1、document预分配空间占满以后会重新分配新的空间,将产生额外的I/O消耗;
2、重新分配空间时,本document的索引字段全部重新索引
因此不要老往一个文档里塞数据,要考虑是否数据建模设计出了问题。
分片
将数据拆分,存在集群的不同机器上的过程。何时分片需要权衡,通常不要太早分片,因为分片不仅增加部署的操作复杂度,还要求做出设计决策,而该决策决定后很难更改。也不能在系统运行太久出现问题后再分片,因为在一个过载的系统上不停机进行分片是非常困难的。
大集合和大量小集合文件的优化
大集合应该要选择拆分成多个集合,并将他们关联来,而不是将过大数据存在单个集合中。
大量小集合文件优化方式:
1.充分利用_id字段,它是一个唯一的自带索引的12byte大小的数据。可以把一些有意义的字段存到里面,但要自己控制唯一性。
2.缩短field字段名称
3.使用嵌套模式合并小集合文件
mongodb的限制和临界值:
https://docs.mongodb.com/manual/reference/limits/#Number-of-Namespaces
引用模式的两种类别选用
Manual references和DBRefs
大部分时候用手动引用。
官方示例:https://docs.mongodb.com/manual/reference/database-references/
数据建模拾遗
1.mongo索引一般是同步更新的,但也可以建立异步索引。如果没有实时要求查询数据,建立异步索引可以提高写入速度。
2.mongo存储金钱相关数据:
https://docs.mongodb.com/manual/tutorial/model-monetary-data/
3.mongo存储时间数据:
https://docs.mongodb.com/manual/tutorial/model-time-data/
4.mongodb 3.0 改变很多,从2.6版本升级到3.0要关注的细节很多,如权限等等。3.0在数据存储引擎上更换成了wiredTiger,在数据压缩方面很有效,解决大数据量问题的情况下,磁盘不够用的问题。