MongoDB 是非关系型数据库的典型代表,DB-Engines Ranking 数据显示,近年来,MongoDB 在 NoSQL 领域一直独占鳌头。MongoDB 是为快速开发互联网应用而设计的数据库系统,其数据模型和持久化策略就是为了构建高读/写的性能,并且可以方面的弹性拓展。
目前公司使用到的 MongoDB 的主要场景有 库存中心(原料出入库、商品出入库、商品上下架变动、与其它系统平台的交互报文等)、物流配送(订单的物流信息、配送信息、地理位置信息等)、日志中心(系统应用和APP的log信息、调用依赖信息等)、商品中心(商品数据、推送信息等)、运维管理平台(收集记录的变更信息等)等。随着MongoDB的普及和使用量的快速增长,为了规范使用,便于管理和获取更高的性能,整理此文档。
命名规则
mongoDB 版本选择:默认新装数据库使用MonGoDB 3.X 社区版。建议3.2.10+
数据库设计规范数据库名可以是满足以下条件的任意UTF-8字符串:
- (1)不能出现除“_”字符以外的特殊字符;
- (2)不能含有”(空格)、.、$、/、、和(空字符);
- (3)应全部小写;
- (4)最多30字符。
- (5)禁止使用数字打头的库名
集合命名规则必须满足下列条件的任意UTF-8字符串
- (1)集合名不能是空字符串“”;不能出现除“_”字符以外的特殊字符,禁止使用数字开头的名称;
- (2)集合名不能以“system.”开头,这是为系统集合保留的前缀。例如system.users这个集合保存着数据库的用户信息,system.namespaces集合保存着所有数据库集合的信息;
- (3)用户创建的集合名字不能含有保留字符。除非你要访问系统创建的集合,否则不可在名字里出现;
- (4)集合名应简洁明了,尽量都使用小写;
字段命名规范
- (1)字段不能含有(空字符)。
- (2)禁止使用数字开头的字段名;
- (3)不可以“”开头命名字段名称,不能出现除“”字符以外的特殊字符;
- (4)字段引用必须采用集合名+被引用字段名称。例如集合user的键id在集合user_info中被引用,用user_id作为键名;
- (5)只有在遇到引用情况下,字段中包含的集合名首字母需要大写,其他一律小写格式。
- (6)如果字段较大,应尽量压缩存放
- (7)如果字段较大且会成为查询条件,例如一长串的url,可以转成md5后存放
- (8)禁止自定义_id的值。
数据库设计规范
合理容量规划和库级拆分创建新的数据库时,提前进行容量规划库的集合数,存储容量,QPS等, 是放在已有集群,还是新创 建集群部署。
避免把所有集合都放在同一个数据库,造成一个库中集合过多;
业务禁止使用id字段;业务避免向id字段写入自定义的业务数据:因MongoDB的 Jd字段默认是主键, 类似于Mysql InnoDB表的主键,如果业务写入无序数据(如uuid/md5),集合本身是B+ Tree,为保证树的平衡,会大副度调整内部存储数据结构;写入数据的代价很大,容易导致写入性能低;
MongoDB 数据是大小写敏感的,如业务不区分大小,建议冗余一个全部大写或小写字段,用于不区分大小写的数据检索效率*mongo
中数据查询是大小写敏感的,例如{f,"aA"}的查询条件, 不能匹配字段为“aa”,“AA", “Aa”
值的文档。有的业务需忽略大小,需通过正则方式进行处理{f:aa/}
,虽实现忽略大小功能,但查询效率很低,同时很耗CPU资源。解决这类需求,希望冗余一个全大写(或小写)的字段,用于业务忽略大小的检索需求。例如对f字段冗余tupper字 段,存储字段内容全大写{f_upper."AA'}
对高频大字段进行压缩存储:很多高频的查询,如果存在返回较大字段数据(如10 KB以上),当QPS增加后很容易把MongoDB服务器网络带宽占满。或写入频次较高,会导数oplog实体很大。建议这类高频和较大的数据, 在业务层进行 压缩后,再存入MongoDB中。
ObjectId存储时,作为ObjectId存储,不可存成字符串类型;原因:
- 第一,方便查询(字符串和ObjectId不能相互匹配)
- 第二,ObjectId 含有有用的信息,如从插入的时间戳可以得知创建日期;
- 第三,字符串表示的ObjectId要多占用两倍的磁盘空间;
索引设计规范
- (1)MongoDB的索引仅支持1K以内的字段,如果你存入的数据长度超过1K,那么它将无法被索引
- (2)索引名称长度不要过长;命名方式:idx_字段名 ;组合索引建议包含所有字段名,过长的字段名可以采用缩写形式。
- (3)唯一索引命名规范:uniq_字段名称,应尽量综合评估查询场景,通过评估尽可能的将单列索引并入组合索引以降低所以数量;
- (4)索引越多,插入或修改记录就会导致 mongodb 越慢。
- (5)创建索引要在后台创建,避免阻塞业务正常DML和查询。db.works.createIndex({a:1,b:1},{"name":'idx_字段名'},{background:true}) (6)禁止在数组字段上创建索引;
- (7)在创建组合索引的时候,应评估索引中包含的字段,尽量将选择性高(唯一值多的数据)的字段放在组合索引的前面;
- (8)在开发业务的时候尽量检查自己的程序性能,多使用explain()查看执行计划;
- (9)禁止冗余索引。例如索引
idx_account_sName_createTime {"account" : 1,"sName" : 1,"createTime" : -1}
和索引idx_account {"account" : 1}
索引冗余,可删除idx_account
索引。
查询规范
- (1)查询语句是否使用到索引,在查询条件的键上,或者排序条件的键上必须有索引(数据量较小的集合除外);
- (2)使用limit()限定返回结果集的大小,减少数据库服务器的资源消耗,以及网络传输的数据量;
- (3)只查询使用到的字段,而不查询所有字段;尽量不要让数组字段成为查询条件;
- (4)执行remove()删除操作,未带查询条件,警告或报错;
- (5)查询中的某些操作符可能会导致性能低下,如ne,,exists,,or,$where尽量在业务中不要使用;
- (6)MongoDB 的组合索引使用策略 遵循"最左原则",优先使用覆盖索引,查询语句遵守复合索引字段顺序;
- (7)必要时使用hint()强制使用某个索引查询;
- (8)更新操作时,先查询后更新,通过主键key更新,可以提高更新效率;
应用程序连接配置合理设置读写分离,减少主节点压力,提高可集群可扩展性:
- (1)mongo客户端通过只读偏好(read -preference)属性设置,决定客户端只读查询的路由规则。
- (2)mongo客户端默认所有查询都路由到主节点查询,而很多应用程序只读业务一-致性不高 (接受秒级别的同步延时), 可把只读查询路由到从节点。MongoDB正常 复制同步延时在1秒内。
- (3)如果业务只读查询,对数据- -致性要求不高(比如最坏情况按受60秒延时).建议程序drver的只读偏好属性设置为 secondaryPreferred。
mongo客户端只读偏好支持5种模式
分片键规范
- 分片键的几个原则:
- 分片键是不可变。
- 分片键必须是索引。
- 分片键大小限制512bytes。
- 分片键用于路由查询。
- MongoDB不接受已进行collection级分片的collection上插入无分片键的文档。
好片键的要素
好的 shard key 应该拥有如下特性:
- 1.key 分布足够离散 (sufficient cardinality)
- 2.写请求均匀分布 (evenly distributed write)
- 3.尽量避免 scatter-gather 查询 (targeted read)MongoDB的内部机制保证了每个副本集(RS)包含了同样数量的块。
片键的选择决定了三个重要的方面:
读和写的分布其中最重要的一点是读和写的分布
如果你总是朝一台机器写,那么这台机器将会成为写瓶颈,则你的集群的写性能将会降低。这无关乎你的集群有多少个节点,因为所有的写操作都只在一个地方进行。因此,你不应该使用单调递增的_id或时间戳作为片键,这样将会导致你一直往最后一个副本集中添加数据。
相类似的是如果你的读操作一直都在同一个副本集上,那么你最好祈求你的任务能在机器内存所能承受的范围之内。通过副本集将读请求划分开能够使你的工作数据集大小随着分片数线性扩展。这样的话你能够将负载压力均分到各台机器的内存和磁盘之上。
数据块的大小
其次是数据块的大小。MongoDB能够将大的数据块划分成更小的,但这种情况仅仅在片键不同的情况下发生。如果你有巨量的数据文档都使用了同样的片键,那么你相应的会得到巨大的数据块。出现巨大块是非常不好的,不仅仅因为它会导致数据的不平均分布,还因为一旦这个数据块的大小超过某个值,那么你就不能够在分片之间移动它了。
每个查询命中的分片数目
最后一点,如果能够保证大部分的查询请求都能够命中尽可能少的分片那就最好了。对于一个查询请求来说,其延迟直接取决于最慢的那个命中服务器的延迟;所以你命中的分片越少,那么理论上来说查询将会越快。这一点并不是硬性的规定,不过如果能够做到充分考虑那么应该是很有利的。因为数据块在分片上的分布仅仅是近似的遵循片键的顺序,而并不是严格的强制指定。
几种分片键的策略
Hashed id 可以使用数据文档_id的哈希作为片键
读和写都能够平均分布,并且它能够保证每个文档都有不同的片键所以数据块能够很精细。对多个文档的查询必将命中所有的分片。
递增的sharding key
数据文件挪动小(优势)因为数据文件递增,所以会把insert的写IO永久放在最后一片上,造成最后一片的写热点。同时,随着最后一片的数据量增大,将不断的发生迁移至之前的片上。
随机的sharding key
数据分布均匀,insert的写IO均匀分布在多个片上。(优势)对多个文档的查询必将命中所有的分片;大量的随机IO,磁盘不堪重荷。
混合型key
为防止巨大块的产生,建议使用组合键,引入 _id
来细化。{keyname: 1, _id: 1}
原则就是:keyname 可以是一个经常被查询的字段,尽可能基数较大;_id
字段是有非常多不同的值可以供mongodb进行分割,这种策略适合大多业务情况;如果实在找不到keyname这样字段,那么就对_id进行Hashed吧。
来源:https://www.lsjlt.com/news/47351.html