【mongodb】日常开发须知(基础版)
以下为个人在公司中所了解的mongodb开发基础,不具有权威性,仅供参考
库
1.库名全部小写,禁止使用任何_
以外的特殊字符,禁止使用数字打头的库名,如:123_abc
解释:库以文件夹的形式存在,使用特殊字符或其它不规范的命名方式会导致命名混乱。
2.数据库名最多为64字符。
3.在创建新的库前应尽量评估该库的体积、QPS等,提前考虑是应该新建一个库还是专门为该库创建一个新的集群。
案例:某开发在拿到dba提供的mongodb后由于mongodb的权限控制比较宽松,导致该业务的开发在创建集合的时候懒得与dba讨论,而是随意的将所有集合都创建在一个库中,最初并没有什么问题,因为业务的请求量并不大。半年后,该业务增长到了一个比较大的量级,而此时开发人员上线了一个新的项目,该项目的写入量很大,大部分都为批量更新,由于所有集合都存放在一个库中,这个新项目的批量更新带来了频繁的库级锁,由于库级锁是排他的(在有锁的同时不可读写)开发发现:这个实例上的所有业务的数据库请求都变得极其缓慢。最后开发配合dba一起将该库拆散到了多个新的库中,将一库N集合转换为单库单集合,性能问题迎刃而解。
集合
1.集合名全部小写,禁止使用任何_
以外的特殊字符,禁止使用数字打头的集合名,如:123_abc
,禁止system打头
system是系统集合前缀
2.集合名称最多为64字符
3.为了避免库级锁带来的问题,应尽量对写入较大的集合使用“单库单集合”的结构,所以对于新增业务应尽量创建新库,而不是在现有库中创建新集合
一个库中写入较大的集合会影响其它集合的读写性能
4.如果评估单集合数据量较大,可以将一个大表拆分为多个小表,然后将每一个小表存放在独立的库中,由于mongodb是库级锁,因此这样做可以大幅减少并发写入带来的锁争用问题
某业务上线前进行压力测试(该业务为大量更新场景),在压测时发现更新量在1000/s以内的时候更新性能优秀(2ms左右),而在更新量达到2500/s之后更新性能严重下降(10ms左右),经过查看原因是由于并发过大导致锁争用严重,于是采取对应优化措施:开发针对这个大表做HASH分表处理,将一个集合拆分为16个集合并,将每个集合安置在独立的库中。这个方案理论上将此前的的锁争用消耗降低到了此前的1/16,再次进行压测,结论是更新性能达到了5000/s(达到IO瓶颈),远超业务3000/s的要求。
5.mongodb的集合拥有“自动清理过期数据”的功能,只需在该集合中文档的时间字段增加一个TTL索引即可实现该功能,但需要注意的是该字段的类型则必须是mongoDate()
文档
1.文档中的key禁止使用任何_
以外的特殊字符
2.尽量将同样类型的文档存放在一个集合中,将不同类型的文档分散在不同的集合中
相同类型的文档能够大幅度提高索引利用率,如果文档混杂存放则可能会出现查询经常需要全表扫描的情况
3.禁止使用_id,如:向_id中写入自定义内容
某业务的mongodb在放量后出现严重的写入性能问题,大致为:写入达到300/s的时候IO跑满,排查中发现,该业务在设计的时候为了方便,而将_id中写入了无序的类似md5的数据。mongodb的表与innodb相似,都是索引组织表,数据内容跟在主键后,而_id是mongodb中的默认主键,一旦_id的值为非自增,当数据量达到一定程度之后,每一次写入都可能导致主键的二叉树大幅度调整,这将是一个代价极大的写入,所以写入就会随着数据量的增大而下降,所以一定不要在_id中写入自定义的内容。
4.尽量不要让数组字段成为查询条件
某业务在一个表的数组字段上创建了一个索引,创建完毕之后发现表体积增大了很多很多,排查发现是由于索引体积的大幅度增大导致。在mongodb中,如果为一个数组字段添加索引,那么mongodb会主动为这个数组中的所有元素依次添加独立索引,例如:为数组字段{a:[x,y,z]}添加索引{a:1},实际上添加的索引为:
{a:[x:1]}
{a:[y:1]}
{a:[z:1]}
该业务的数组字段中有11个元素,那么等于一次创建了11条索引,这是索引体积大幅度增大的根本原因。另外,如果组合索引中存在数组字段,那么mongodb会为每一个元素与其它字段的组合创建一个独立的索引,例如:为数组字段{a:[x,y,z]}和{b:qqq}。添加索引{a:1,b:1},实际上添加的索引为:
{a:[x:1],b:1}
{a:[y:1],b:1}
{a:[z:1],b:1}
如果一个组合索引中存在两个数组字段,那么索引的数量将是两个数组字段中元素的笛卡儿积,所以mongodb不允许索引中存在一个以上的数组字段
5.如果字段较大,应尽量压缩存放
某业务上线后一直很正常,但在放量3倍之后发现mongodb服务器的网卡流量报警,IO压力报警,排查中发现,该业务讲一个超长的文本字段存。放在mongodb中,而这个字段的平均体积达到了7K。在并发为2000QPS的场景下,每次取出1~20条数据,导致这个mongodb每秒钟要发送将。近100MB的数据,而对于数据库而言,读写均为随机IO,所以在如此大的数据吞吐场景中,IO达到了报警阈值。由于文本是一个容易压缩的样本,所以我们对该字段进行了压缩存放,使其平均体积降低到了2K,而解压在业务端进行处理,最终将吞吐降低到了20MB/S左右。
6.如果字段较大且会成为查询条件,例如一长串的url,尽量转成md5后存放
某业务上线前进行压力测试,测试中发现某个场景下的查询性能不够理想,排查中发现该场景的查询条件类似:{url:xxxx},而url字段中的值大,部分都很长很长,该字段的平均体积达到了0.5K,在这种情况下索引的体积会变得很大从而导致虽然请求虽然能够走索引但效率并不够理想,于是dba配合业务开发一起对该场景进行优化:
- 将该字段的存放的内容由真实的url改为url内容md5后的值,字段体积得到了大幅度缩小,固定在了32位
- 查询时,用户请求通过url查询,而此时程序会将该url进行md5,然后用得到的值进行查询,由于所以体积大幅度缩小,所以查询速度有了极大的提高,优化完毕后再次进行压力测试,性能达标,为之前的6倍
7.由于mongodb是大小写敏感,如果字段无需大小写敏感,为了提高查询效率应尽量存放统一了大小写后的数据,如:全部小写。或为该字段增加一个统一了大小写的辅助字段。
某业务需要根据字段{a:XxX}来进行查询,在mongodb中a的值是大小写敏感的,并且无法配置为忽略大小写,但该业务场景为了满足查询需求,而需要忽略大小写,这个大小写敏感与否的矛盾导致业务需要使用正则来进行匹配:{a:/xxx/i},i参数在正则中表示忽略大小写,上线后发现,查询性能非常低下,在一个拥有200万文档的集合中一次查询需要消耗2.8~7秒,并发达到50QPS的时候mongodb实例所在服务器的CPU就跑到了973%。mongodb在查询条件中使用正则的时候,能够像普通精确匹配一样使用索引达到高效率的查询,但一旦使用了参数i来忽略大小写查询优化器就需要对每一个数据的大小写进行调整然后再进行匹配,此时这个请求就变成了全表扫描,这就是效率低下的根本原因。对于这种场景可以采用新建一个统一了大小的字段,例如全部小写:假设原字段为:{a:aAbB},那么为其添加一个全部为小写的对应字段:{a_low:aabb},然后通过字段a_low进行查询就能达到精确匹配,按照该方案改进后,该场景的查询耗时降低到了2毫秒,虽然新增字段导致实例会变大一些,但对于换来性能的大幅度提升还是非常值得的。
8.不要存放太长的字符串,如果这个字段为查询条件,那么确保该字段的值不超过1KB
mongodb的索引仅支持1K以内的字段,如果你存入的数据长度超过1K,那么它将无法被索引
索引
1.mongodb的组合索引使用策略与mysql一致,遵循”最左原则“
2.索引名称长度不要超过128字符
3.应尽量综合评估查询场景,通过评估尽可能的将单列索引并入组合索引以降低所以数量,结合1,2点
mongodb的组合索引规则和mysql一样,都遵循最左原理,假设一个组合索引为:{a:1,b:1,c:1},那么以下条件的查询是可以被用到的:
{a:1}
{a:1,b:2}
{a:1,b:2,c:3}
{}
以下条件的查询是不能用到索引的:
{b:1}
{bc:2}
{c:2}
另外在设计索引的时候可以通过该原理减少索引的数目,如果需要通过{a:xxx}或{a:xxx,b:xxx}来进行查询,那么创建索引:
{a:1,b:1}
即可同时满足这两个查询场景而无需再单独创建{a:1}索引
4.在创建组合索引的时候,应评估索引中包含的字段,尽量将数据基数大的字段放在组合索引的前面
某业务某场景下的查询十分缓慢,大概需要1.7秒左右,需要进行调优,该场景的查询和对应索引如下:
查询:{name:baidu,status:0}
索引:{status:1,name:1}
乍一看没什么问题,因为查询和索引十分匹配,但对该集合分析后发现该集合一共有150万文档,而status=0的有1499930由于这基本上占了99%的文档数目(数据基数很小),所以导致虽然使用了索引,但依然需要从149万行数据中找到name=baidu的数据但name字段,则有大量不同的数据(数据基数很大),所以如果将该组合索引调整为name在前,该查询即可先通过name字段抽出较少的数据,再通过status进行过滤,就快了:
{name:1.status:1}
调整后查询耗时降低到3~5毫秒
5.在数据量较大的时候,mongodb索引的创建是一个缓慢的过程,所以应当在上前线或数据量变得很大前尽量评估,按需创建会用到的索引
6.mongodb的索引创建是库级锁,在索引创建时该集合所在库不可读写,所以如需添加索引,请联系dba
某业务mongodb突然报警,dba紧急排查发现业务自行在一个较大的集合(300万行数据)上创建了一个索引导致该集合所在的库整个锁死,这是因为mongodb的锁为库级锁,而索引的调整也会锁住整个库直到索引调整完毕,此时与该库有关的读写操作以及与该实例有关的整体的,状态查询操作均会被阻塞,所以对于读写较大的实例这样创建索引是十分危险的,在mongodb2.4中可以用一个参数来规避主库的索引创建导致的阻塞:{‘background’:’true’},但当这一操作同步到从库后,从库将会前台执行,此时从库的读写会被阻塞,而在mongodb2.6中,该操作在从库也会同样被放入后台执行以达到同样不影响读写的目的,但是这个参数带来的新问题是:如果一个集合非常非常大,那么后台创建索引会变得很慢很慢,对于一些较为急迫的索引调整需求这是无法满足的。而dba可以采用rolling的方式,通过循环切主,下线从库添加索引的方式为整个集群添加索引,所以为了业务的稳定、安全,开发切勿自行添加索引,以防阻塞
7.mongodb支持TTL索引,该索引能够按你的需要自动删除XXX秒之前的数据并会尽量选择在业务低峰期执行删除操作
8.如果你存放的数据是地理位置信息,比如:经纬度数据,那么可以在该字段上添加mongodb支持的地理索引:2d及2dsphere,但他们是不同的,混用会导致结果不准确
2d:只能用于点对点索引,适用于平面地图和时间连续的数据,比如非常适用于游戏地图
2dsphere:允许指定点,线,和多边形。适用于地球表面类型的地图(球体)
如果在球体表面创建2d索引,则会导致极点附近出现大量扭曲变形,最终导致结果不准确
9.从mongodb2.4开始,支持索引的ICP功能,可以通过其合理减少索引数量【为什么呢?10】
从mongodb2.4开始,组合索引能够被更有效的利用,如:索引{x:1,y:1,z:1}可以被查询{x:1,z:1}所利用,如果x字段的数据基数很大,而该条件匹配到的数据有很少,在这种情况下无需专门添加{x:1,z:1}索引,索引{x:1,y:1,z:1}即可带来理想的性能,但需要注意的是,ICP性能并没有原生的连续的组合索引效率好,如果发现效率不佳那么还是需要添加单独的{x:1,z:1}索引。
操作性能
1.索引中的-1和1是不一样的,一个是逆序,一个是正序,应当根据自己的业务场景建立适合的索引排序。
2.在开发业务的时候尽量检查自己的程序性能,可以使用explain()函数检查你的查询执行详情,另外hint()函数相当于mysql中的force index()
3.查询中的某些$
操作符可能会导致性能低下,如$ne
,$not
,$exists
,$nin
,$or
,尽量在业务中不要使用
$exist
:因为松散的文档结构导致查询必须遍历每一个文档
$ne
:如果当取反的值为大多数,则会扫描整个索引
$not
:可能会导致查询优化器不知道应当使用哪个索引,所以会经常退化为全表扫描
$nin
:全表扫描
$or
:有多少个条件就会查询多少次,最后合并结果集,所以尽可能的使用$in
4.如果你的结合体积/文档数固定,那么建议创建capped(封顶)集合,这种集合的写入性能非常高并无需专门清理老旧数据,需要注意的是,capped表不支持remove()和update()
5.在写入数据的时候,如果你需要实现类似mysql中INSERT INTO ON DUPLICATE KEY UPDATE的功能,那么可以选择upsert()函数
6.不要一次取出太多的数据进行排序,mongodb目前支持对32MB以内的结果集进行排序,如果需要排序,那么请尽量限制结果集中的数据量
7.mongodb的聚合框架非常好用,能够通过简单的语法实现复杂的统计查询,并且性能也不错
8.如果需要清理掉一个集合中的所有数据,那么remove()的性能是非常低下的,该场景下应当使用drop()
remove()是逐行操作,所以在删除大量数据的时候性能很差。
9.写入大量数据的时候可以选择使用batchInsert,但目前mongodb每一次能够接受的最大消息长度为48MB,如果超出48MB,将会被自动拆分为多个48MB的消息
10.在使用数组字段做为查询条件的时候,将于覆盖索引无缘
这是因为数组是保存在索引中的,即便将数组字段从需要返回的字段中剔除,这样的索引仍然无法覆盖查询。
11.在查询中如果有范围条件,那么尽量和定值条件放在一起进行过滤,并在创建索引的时候将定值查询字段放在范围查询字段前
还没有评论,来说两句吧...