mongodb 开发规范

一.命名规则  

1.数据库命名规则

数据库名可以是满足以下条件的任意UTF-8字符串:
(1)不能是空字符串(”") ;
(2)不能含有”(空格)、.、$、/、、和(空字符);
(3)应全部小写;
(4)最多30字符。
注意事项: 数据库名最终会变成文件系统里面的文件,这也就是有如此多限制的原因。把数据库的名字放到集合名前面,得到就是集合的完全限定名,称为命名空间。例如:如果你在cms数据库中使用blog.posts集合,那么这个集合的命名空间就是cms.blog.posts。 命名空间的长度不得超过121字节,在实际使用当中应该小于100字节。
 

2.集合命名规则

必须满足下列条件的任意UTF-8字符串
(1)集合名不能是空字符串“”;
(2)集合名不能含有字符(空字符NULL),这个字符表示集合名的结尾;
(3)集合名不能以“system.”开头,这是为系统集合保留的前缀。例如system.users这个集合保存着数据库的用户信息,system.namespaces集合保存着所有数据库集合的信息;
(4)用户创建的集合名字不能含有保留字符$。除非你要访问系统创建的集合,否则不可在名字里出现$;
(5)首字母必须大写,组合词每个词首字母必须大写,例如NoticeQueue、UserBase、UserExpand。
 

3.文档的键命名规范

(1)键不能含有(空字符)。这个字符用来表示键的结尾;
(2)和$有特殊的意义,只有在特定环境下才能使用。通常来说就是被保留了,使用不当的话,驱动程序会提示;
(3)以下划线“_”开头的键是系统保留的,不可以“_”开头命名键名称;
(4)键名建议遵循英文简写、如因意义相近可以通过下划线加上全称的方式进行区别,但应遵循键名不超过35个字符例 如,id、state、title、content、mail_name、subtitle database_admin database_administrator;
(5)键引用必须采用集合名+被引用键名称。例如集合User的键id在集合UserInfo中被引用,必须用user_id作为键名; 一个集合中存在多次引用相同集合某一个键,则命名按照业务标识+集合名+被引用键名称。例如Message集合,发送者键名称:from_user_id 收信人键名称:to_User_id或者to_user_id (根据该业务实际情况调整,但只能统一一种规则);
(6)只有在遇到引用情况下,键中包含的集合名首字母需要大写,其他一律小写格式。

二.优化   

1.库集合优化

(1)在创建新的库前应尽量评估该库的体积、QPS等,提前与DBA讨论是应该新建一个库还是专门为该库创建一个新的集群;
(2)如果评估单集合数据量较大,可以将一个大表拆分为多个小表,然后将每一个小表存放在独立的库中或者sharding分表;
(3)特定场景下使用Capped Collection或TTL Indexes,以自动处理失效数据
(4)创建集合规则不同的业务场景是可以配置进行不同的配置:
a. 如果是读多写少的表在创建时我们可以尽量将page size设置的比较小,比如 16KB,如果表数据量不太大("internal_page_max=16KB,leaf_page_max=16KB,leaf_value_max=8KB,os_cache_max=1GB")
b. 如果这个读多写少的表数据量比较大,可以为其设置一个压缩算法,例如: "block_compressor=zlib,internal_page_max=16KB,leaf_page_max=16KB,leaf_value_max=8KB"
c. 注意:该zlib压缩算法不要使用,对cpu消耗特别大,如果使用snapp消耗20% cpu,而且使用zlib能消耗90%cpu,甚至100%
d. 如果是写多读少的表,可以将 leaf_page_max设置到1MB,并开启压缩算法,也可以为其制定操作系统层面page cache大小的os_cache_max 值,让它不会占用太多的page cache内存,防止影响读操作
e. 案例 db.createCollection( "logs", { storageEngine: { wiredTiger: {configString: "internal_page_max=16KB,leaf_page_max=16KB,leaf_value_max=8KB,os_cache_max=1GB" } } } ) 说明: 读多写少的表 internal_page_max=16KB 默认为4KB leaf_page_max=16KB 默认为32KB leaf_value_max=8KB 默认为64MB os_cache_max=1GB 默认为0 读多写少的表 而且数据量比较大 block_compressor=zlib 默认为snappy internal_page_max=16KB 默认为4KB leaf_page_max=16KB 默认为32KB leaf_value_max=8KB 默认为64MB

2.文档优化

(1)尽量将同样类型的文档存放在一个集合中,将不同类型的文档分散在不同的集合中;相同类型的文档能够大幅度提高索引利用率,如果文档混杂存放则可能会出现查询经常需要全表扫描的情况
(2)禁止使用_id,如:向_id中写入自定义内容,MongoDB的表与InnoDB相似,都是索引组织表,数据内容跟在主键后,而_id是MongoDB中的默认主键,一旦_id的值为非自增,当数据量达到一定程度之后,每一次写入都可能导致主键的二叉树大幅度调整,这将是一个代价极大的写入, 所以写入就会随着数据量的增大而下降,所以一定不要在_id中写入自定义的内容
(3)尽量不要让数组字段成为查询条件,如果为一个数组字段添加索引,那么MongoDB会主动为这个数组中的所有元素依次添加独立索引,例如: 为数组字段{a:[x,y,z]}添加索引{a:1},实际上添加的索引为:{a:[x:1]}、{a:[y:1]}、{a:[z:1]} 如果一个组合索引中存在两个数组字段,那么索引的数量将是两个数组字段中元素的笛卡儿积,所以MongoDB不允许索引中存在一个以上的数组字段
(4)不要存放太长的字符串,如果这个字段为查询条件,那么确保该字段的值不超过1KB,如果字段较大,应尽量压缩存放,如果字段较大且会成为查询条件,例如一长串的url,尽量转成md5后存放,
(5)由于MongoDB是大小写敏感的,如果字段无需大小写敏感,为了提高查询效率应尽量存放统一了大小写后的数据,如:全部小写或为该字段增加一个统一了大小写的辅助字段
(6)分片键必须有索引,分片键大小限制为512byte,一旦集合已经分片,不可以直接修改分片键。不接受向已进行分片的collection上插入无分片键的文档,也不支持空值插入
(7)片键的设计原则:
a. 所有的插入、更新、删除将会均匀发送到集群的所有分片中。
b. 所有的查询将会在集群中的所有分片中均匀地分发。
c. 所有的更新或者删除操作将会只面向相关的分片,不会发送到一个没有存储被修改数据的分片上。
d. 一个查询将不会被发送到没有存储被查询数据的分片上
(8)如若将日期类型选择为string,不同的日期格式的文档,不支持等值查询,不支持范围查询 解读:创建一个测试集合product,分别向集合插入Date:"20180425"和Date:"2018-04-25"两笔数据。等值查询、范围查询($gte, $lte)只能查到日期格式相同的数据,都为一笔数据
(9)通过$size查询数组大小,但是$size运算符不使用索引和限制准确匹配(不能指定$Sized 范围)。因此,如果需要基于数组的大小执行查询,可以在文档设计中增加size属性。 解读:例如在商品评价中,其他人可以对评价进行投票。为了阻止用户多次投票和对有帮助的评论进行排序,所以,评价文档设计是:在一个数组字段(voter_ids)保存了所有评论用户的ID,而数组大小缓存在helpful_votes字段里

3.索引

(1)MongoDB的索引仅支持1K以内的字段,如果你存入的数据长度超过1K,那么它将无法被索引
(2)MongoDB 的组合索引使用策略与 MySQL 一致,遵循"最左原则",优先使用覆盖索引,即Covered Query,复合索引字段顺序(较精确匹配字段、排序字段、范围查询字段)
(3)索引名称长度不要超过128字符
(4)索引数量<=2,应尽量综合评估查询场景,通过评估尽可能的将单列索引并入组合索引以降低所以数量,结合2,3点
(5)在数据量较大的时候,MongoDB 索引的创建是一个缓慢的过程,所以应当在上前线或数据量变得很大前尽量评估,按需创建会用到的索引
(6)MongoDB 支持 TTL 索引,该索引能够按你的需要自动删除XXX秒之前的数据并会尽量选择在业务低峰期执行删除操作;看业务是否需要这一类型索引
(7)如果你存放的数据是地理位置信息,比如:经纬度数据。那么可以在该字段上添加 MongoDB 支持的地理索引:2d 及2dsphere,但他们是不同的,混用会导致结果不准确; 2d:只能用于点对点索引,适用于平面地图和时间连续的数据,比如非常适用于游戏地图【2dsphere:允许指定点、线和多边形。适用于地球表面类型的地图(球体) 】如果在球体表面创建2d索引,则会导致极点附近出现大量扭曲变形,最终导致结果不准确
(8)MongoDB 的全文索引目前仍然处于“实验”阶段,性能并不够理想,当前不建议使用
(9)MongoDB2.4开始,支持索引的 ICP 功能,可以通过其合理减少索引数量,从 MongoDB2.4开始,组合索引能够被更有效的利用

4.查询操作优化

(1)在查询条件的键上,或者排序条件的键上必须创建索引;
(2)使用limit限定返回结果条数,最多不能超过100;
(3)skip+limit翻页,越往后面越慢。比较靠谱的做法是,先找出上次的id,翻页的时候不用skip: last_row_id = ObjectId('....'); db.activity_stream-<find({_id:{$lt:last_row_id },User_id:20 } ).sort( {_id:-1} ).limit(10);
(4)只查询使用到的字段,而不查询所有字段;
(5)对于Circular Fixed Size Collections合理使用;apped Collections比普通Collections的读写效率高
(6)索引中的-1和1是不一样的,一个是逆序,一个是正序,应当根据自己的业务场景建立适合的索引排序
(7)在开发业务的时候尽量检查自己的程序性能,可以使用explain() 函数检查你的查询执行详情,另外 hint() 函数相当于 MySQL 中的force index()
(8)查询中的某些$操作符可能会导致性能低下,如 $ne,$not,$exists,$nin,$or,尽量在业务中不要使用 a. $exist:因为松散的文档结构导致查询必须遍历每一个文档 b. $ne:如果当取反的值为大多数,则会扫描整个索引 c. $not:可能会导致查询优化器不知道应当使用哪个索引,所以会经常退化为全表扫描 d. $nin:全表扫描 e. $or:有多少个条件就会查询多少次,最后合并结果集,所以尽可能的使用 $in
(9)如果你结合体积大小/文档数固定,那么建议创建 capped(封顶)集合,这种集合的写入性能非常高并无需专门清理老旧数据,需要注意的是 capped 表不支持r emove() 和update()
(10)在写入数据的时候,如果你需要实现类似 MySQL 中INSERT INTO ON DUPLICATE KEY UPDATE 的功能,那么可以选择 upsert() 函数: db.analytice.update( {"url":"/blog"}, {"$inc":{"visits":1}}, true ) (11)不要一次取出太多的数据进行排序,MongoDB 目前支持对32MB以内的结果集进行排序,如果需要排序,那么请尽量限制结果集中的数据量
(12)MongoDB 的聚合框架非常好用,能够通过简单的语法实现复杂的统计查询,并且性能也不错,聚合框架是MongoDB的高级查询语言,它允许通过转换和合并由多个文档中的数据来生成新的单个文档里不存在的文档信息。可以把MongoDB的聚合框架等价于SQL的Group By语句,在聚合框架中,$project操作符允许过滤传递给管道下一个阶段的字段。限制每个文档传递的大小,可以改善性能,尤其是在处理大文档且只需要每个文档一部分数据的场景下
(13)如果需要清理掉一个集合中的所有数据,那么remove()的性能是非常低下的,该场景下应当使用drop(),remove()是逐行操作,所以在删除大量数据的时候性能很差
(14)写入大量数据的时候可以选择使用batchInsert,但目前MongoDB每一次能够接受的最大消息长度为48MB,如果超出48MB,将会被自动拆分为多个48MB的消息
(15)在使用数组字段做为查询条件的时候,将于覆盖索引无缘,这是因为数组是保存在索引中的,即便将数组字段从需要返回的字段中剔除,这样的索引仍然无法覆盖查询
(16)在查询中如果有范围条件,那么尽量和定值条件放在一起进行过滤,并在创建索引的时候将定值查询字段放在范围查询字段前
(17)更新操作:
a.先查询后更新,通过主键key更新,提高更新效率,
b.多文档更新,在默认情况下,只会更新匹配查询器的第一个文档。要更新所有的匹配文档,需要显式指定多文档更新模式--添加参数multi:true
c.在文档级别更新是原子性的,这意味着一条更新10个文档的语句可能在更新3个文档后由于某些原因失败,应用程序必须根据自己的策略来处理这些失败
d. update结合upsert可以用来处理,当文档存在时更新,文档不存在时插入。如果查询选择器匹配,更新就正常执行;如果没有匹配的文档,就会插入新的文档。新文档的字段是查询选择器和目标更新文档的逻辑合并

5.连接规范

(1)正确连接副本集,副本集提供了数据的保护、高可用和灾难恢复的机制。如果主节点宕机,其中一个从节点会自动提升为从节点。
(2)合理控制连接池的大小,限制连接数资源,可通过Connection String URL中的maxPoolSize 参数来配置连接池大小。
(3)复制集读选项 默认情况下,复制集的所有读请求都发到Primary,Driver可通过设置的Read Preference 来将读请求路由到其他的节点。
a. Primary:默认规则,所有读请求发到Primary。
b. PrimaryPreferred: Primary优先,如果Primary不可达,请求Secondary。
c. Secondary:所有的读请求都发到Secondary。
d. SecondaryPreferred:Secondary优先,当所有的Secondary不可达时,请求Primary。
e. Nearest:读请求发送到最近的可达节点上(通过ping探测得出最近的节点)
原文地址:https://www.cnblogs.com/xibuhaohao/p/11213090.html