- 云开发数据库设计指导
在我们要构建一个项目(应用程序)时,通常第一件事情就要设计数据库。与关系型数据库将数据存储在固定的表格(这些表格由行和列组成)里所不同的是,云开发的数据库使用结构化的文档来存储数据,不再是关系型数据库里每个行列交汇处都必须有且只有一个值,它可以是一个数组、一个对象,或者更加复杂的嵌套。 12.3.1 数据库的设计1、设计数据库需要预先思考哪些问题实现云开发数据库之前,需要了解存储的数据的性质,如何存储这些数据,以及将如何访问它们,这需要你预先就要做出决定,进而通过组织数据和页面数据交互来获得最佳性能。具体地说,你需要先预先思考如下问题: 页面交互需要使用哪些基本对象,时间、地址、价格、富文本、图片、商品属性等等这些实际的信息在数据库里对应的是啥数据类型?不同对象类型之间的关系是一对一、一对多还是多对多?如商品分类、详情页、评论页、购物车、会员信息、用户信息、配送地址等等这些复杂的关系是怎么联系起来的?云数据库添加新对象的频率有多高?集合里添加记录的频次是怎样的?往内嵌文档里添加字段的值的频率又是怎样的?修改记录以及记录里的字段的值的频率有多高?从数据库中删除记录或记录里的字段的频率有多高?根据条件查询数据库的频率有多高?是查询记录列表,还是记录里某个字段的值?查询记录或记录的值你将打算通过什么方式,是通过 ID、字段、条件还是其他方式?创建的集合哪个是最重要的?哪个会放在首页?哪个集合用户访问并发量会比较大?并发量大的集合应该怎么设计才能提升性能?哪些操作对数据的一致性要求比较高,需要进行原子操作或事务操作?(后面的原子操作和事务会介绍)哪个集合或哪个集合的记录的数据会增长比较快,数据量会比较大?哪个集合或哪个集合的记录会随着业务的发展,字段会有很大的调整? 2、功能的背后也是数据库的设计应用程序复杂业务功能的背后,都是简单的数据,在设计数据库的时候要清楚的知道哪些功能会执行什么样的数据操作,集合与集合、集合与字段之间有着什么关系。 比如新闻应用都会有文章列表以及文章详情页,这是两个功能,文章列表强调查询的是符合条件的记录;而文章详情页则是单个记录下的字段;这两者之间有什么差异?比如用户除了有个人信息之外还有身份读者与作者,读者与作者的身份是怎么体现的?管理员、编辑等人的角色呢?不同的角色在处理数据上又有哪些不同?比如用户的点赞、收藏、评论等这些是应该放到用户的集合里,还是应该放到文章的集合里,或者是单独拿出一个集合来存储这些数据?选择这个方式的依据是什么?前端通过表单增删改查数据在数据库里是怎么体现的?浏览页面、上拉下滑、搜索、轮播、菜单等在数据库是怎么体现的?文件上传、图片下载、地图数据获取、服务器时间等 API 是怎么与数据库结合的? 12.3.2 反范式化与范式化设计范式化(normalization) 是将数据像关系型数据库一样分散到不同的集合里,而不同的集合之间是可以通过唯一的 ID 来相互引用数据的。不过要引用这些数据往往需要进行多次查询或使用 lookup 进行联表查询。 而 反范式化(denormalization) 则是将文档所需的数据都嵌入到文档的内部,如果要更新数据,可能整个文档都要查出来,修改之后再存储到数据库里,如果没有更新指令这种可以进行字段级别的更新,大文档要新增字段性能会比较低下。反观范式化设计,由于集合比较分散,也就比较小,更新数据时可以只更新一个相对较小的文档。 数据既可以内嵌(反范式化),也可以采用引用(范式化),两种策略并没有优劣之分,也都有各自的优缺点,关键是要选择适合自己应用场景的方案。完全反范式化的设计(将文档所需要的所有数据都嵌入到一个文档里面)可以大大减少文档查询的次数。如果数据更新更频繁那么范式化的设计是一个比较好的选择,而如果数据查询更频繁,而不需要怎么更新,那就没有必要把数据分散到不同的集合而牺牲查询的效率。对于复杂的应用比如博客系统、商城系统,只用一个集合(完全反范式化设计)会导致集合过大,冗余数据更多,数据写入性能差等问题,这时候就需要进行一定的范式化设计,也就是用更多的集合,而不是更大的集合。 [图片] [图片] 像云开发数据库这种非关系型数据库,它的存储单位是文档,而文档的字段是可以嵌套数组和对象的,这种内嵌的方式把非关系型数据库的表与表之间的关系嵌套在了一个文档里,也就减少了需要跨集合操作的关联关系。 12.3.3 内嵌文档(内嵌数组或对象)在前面我们了解到云开发数据库的一个文档里可以内嵌非常多的数据,甚至做到一个完整的应用只需一个集合。比如一个用户,只有一个购物车在关系型数据库里,我们需要建两张表来存储数据,一张表是存储所有客户信息的用户列表 User,还有一张存储所有用户订单的订单列表 Order,但是云开发数据库可以将原本的多张表内嵌成一张表。 { "name": "小明", "age": 27, "address":"深圳南山腾讯大厦", "orders": [{ "item":"苹果", "price":15, "number":3 },{ "item":"火龙果", "price":18, "number":4 }] } } 采用这个内嵌式的设计模型,当我们要查询一个用户的信息和他的所有订单时,就可以只通过一次查询做到将用户的信息、所有的订单都获取到,而不像关系型数据库需要先在 User 表里查用户的信息,再根据用户的 id 去查所有订单。 同样一篇文章会有 N 个用户去评论产生 N 条评论数据,而这 N 条评论是只属于这一篇文章的,不存在评论既属于 A 文章,又属于 B 文章的情况。这种我们还是可以采用反范式化设计,将与该文章相关的评论都嵌入到这篇文章里: { "title": "为什么要学习云开发", "content": "云开发是腾讯云为移动开发者提供的一站式后端云服务", "comments": [{ "name": "李东bbsky", "created_on": "2020-03-21T10:01:22Z", "comment": "云开发是微信生态下的最推荐的后台技术解决方案" }, { "name": "小明", "created_on": "2020-03-21T11:01:22Z", "comment": "云开发学起来太简单啦" }] } 在我们要进入文章的详情页时,除了需要获取文章的信息,还要一次性把评论都读取出来,这种反范式化内嵌文档就能做到,也就是可以通过一次查询就能获取到所有需要的数据。但是如果文章都是属于大 V 一样的热点,经常会有几千条几万条的评论,将所有的评论都内嵌到文章记录里可能会存在记录溢出(比如超过 16M)、增删改查效率也会下降,这个时候就不适合用内嵌的方式,而是引用。 12.3.4 引用文档有时候数据与数据之间的关系会比较复杂,不再是一对一或者一对多的关系,比如共享协作时,一个用户可以发 N 个文档,而一个文档又有 N 个作者(用户),这种 N 对 N 的复杂关系,使用内嵌文档就不那么好处理了。 试想一下如果你只创建一个用户表,把 A 所参与编辑的文档都内嵌到相应记录的字段里,B 用户的也是,如果 A,B 用户都参与编辑过同一份文档,那么一份文档就被内嵌到了连个用户的记录了,如果这个文档有 N 个作者,就会被重复内嵌 N 次。如果我们只需要查用户编辑过哪些文档,这种方式就没有问题,但是如果要查一份文档被多少个作者编辑过,就比较困难了;如果文档更新比较频繁,那操作起来就更加复杂了,这时内嵌文档显然不合适,应该采用范式化的设计。 比如我们将用户存储到 user 集合里,将所有的文档存储到 file 集合里,集合与集合的会通过唯一的[代码]_id[代码]来连接,下面 user 集合主要存储用户的信息,而把需要引用的 files 集合记录的[代码]_id[代码]也写到 user 集合里, { "_id": "author10001", "name": "小云", "male":"female", "file": ["file200001","file200002","file200003"] } { "_id": "author10002", "male":"male", "name": "小开", "books": ["file200001","file200004"] } 而在 files 集合里,则存储所有文档的信息,在 files 集合里只需要有 user 集合引用的[代码]_id[代码]即可: { "_id": "file200001", "title": "云开发实战指南.pdf", "categories": "PDF文档", "size":"16M" } { "_id": "file200002", "title": "云数据库性能优化.doc", "categories": "Word文档", "size":"2M" } { "_id": "file200003", "title": "云开发入门指南.doc", "categories": "Word文档", "size":"4M" } { "_id": "file200004", "title": "云函数实战.doc", "categories": "Word文档", "size":"4M" } 如果我们想一次性查询用户参与编辑了哪些文件以及相应的文件信息,可以在云函数端使用聚合的 lookup,这样相当于两个集合整合到一个集合里面了。 const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); const db = cloud.database(); const _ = db.command; const $ = db.command.aggregate; exports.main = async (event, context) => { const res = db .collection("user") .aggregate() .lookup({ from: "files", localField: "file", foreignField: "_id", as: "bookList", }) .end(); return res; }; 而如果我们要修改某个指定文档的信息,直接根据 files 集合的_id 来查询就可以了。文档更新一次,所有参与编辑该文档的信息都会更新,保证了文件内容的一致性。 值得一提的是,尽管我们将复杂的关系通过范式化设计把数据分散到了不同的集合,但是和关系型数据库、Excel 一个字段一列还是不一样,我们还是可以把关系不那么复杂的数据用数组、对象的方式内嵌。如果每个用户参与编辑的文档特别多而每个文档参与共同编辑的用户又相对比较少,把 file 都内嵌到 user 集合里就比较耗性能了,这时候可以反过来,把 user 的 id 嵌入 files 集合里,所以数据库的设计与实际业务有着很大的关系。 //由于file数组过大,user集合不再内嵌file了 { "_id": "author10001", "name": "小云", "male":"female", } //把用户的id嵌入到files集合里,相当于以文档为主,作者为辅 { "_id": "file200001", "title": "云开发实战指南.pdf", "categories": "PDF文档", "size":"16M", "author":["author10001","author10002","author10003"] } 这里再说明一下,跨表查询和联表查询是两码事,跨表查询我们可以通过集合与集合之间有关联的字段(意义相同的字段)多次查询来查找结果;而联表查询则是通过关联的字段将多个集合的数据整列整列的合并到一起处理。如果你不需要返回跨集合的整列数据,就不建议用联表查询,更不要妄图联 N 张表,能跨表查询就跨表查询。 12.3.5 数据库设计的注意事项1、数据库的数据模式云开发数据库的数据模式比较灵活,关系型数据库要求你在插入数据之前必须先定义好一个表的模式结构,而云数据库的集合 collection 则并不限制记录 document 结构。关系型数据库对有什么字段、字段是什么类型、长度为多少等等,而云数据库既不需要预先定义,而且记录的结构也没有限制,同一个集合的记录的字段可以有很大的差异。 这种灵活性让对象和数据库文档之间的映射变得很容易。即使数据记录之间有很大的变化,每个文档也可以很好的映射到各条不同的记录。当然在实际使用中,同一个集合中的文档最好都有一个类似的结构(相同的字段、相同的内嵌文档结构)方便进行批量的增删改查以及进行聚合等操作。 随着应用程序使用时间的增长和需求变化,数据库的数据模式可能也需要相应地增长和改变。最简单的方式就是在原有的数据模式基础之上进行添加字段,这样就能保证数据库支持所有旧版的模式。比如用户信息表,由于业务需要需要增加一些字段,比如性别、年龄,云数据库可以很轻松添加,但是这会出现一些问题,就是以往收集的用户信息性别、年龄这些字段是空的,而只有新添加的用户才有。如果业务的数据变动比较大,文档的数据模式也会存在版本混乱的冲突,这个在数据库设计之初也是要思考的。 2、预填充数据如果已经知道未来要用到哪些字段,在第一次插入的时候就将这些字段预填充了,以后用到的时候就可以使用更新指令进行字段级别的更新,而不再需要再给集合来新增字段,这样的效率就会高很多。 { "_id":"user20200001", "nickname": "小明", "age": 27, "address":"", "school":[{ "middle":"" },{ "college":"" }] } 比如简历网站的用户信息表的 address、school,用户登录的时候不必填,但是投递简历前这些信息必填,如果没有预先设置这些字段,收集这些信息时就需要使用 doc 对文档进行记录级别的更新。 db.collection("user") .doc("user20200001") .update({ data: { address: "深圳", school: [ { middle: "华中一附中", }, { college: "清华大学", }, ], }, }); 但是如果预先设置了这些字段,就是使用更新操作符进行字段级别的更新,当集合越大,修改的内容又比较少时,使用更新操作符来更新文档,性能会大大提升。 db.collection("user") .doc("user20200001") .update({ data: { address: _.set("深圳"), "school.0.middle": _.set("华中一附中"), "school.1.college": _.set("清华"), }, }); 3、考虑文档的增长采用内嵌文档这种反范式化设计在查询时是有很大的好处的,但是有一些文档的更新操作,会在内嵌文档的数组里增加元素或者增加一个新字段,如果随着业务的需求这类操作导致文档的大小变大,比如我们为了方便把评论内置到内嵌文档里,早期这样的设计是没有问题的,但是如果评论常年累积的增加会导致内嵌文档过大,越是往后新增的评论会越是影响性能,而且云数据库的一个记录的上限是 16M。如果出现这种数据增长的情况,也会影响到反范式化的设计模式,那么你可能要重新设计下数据模型,在不同文档之间使用引用的方式而非内嵌的数据结构。 由于更新指令不仅可以对数据进行字段级别的微操(增删改),而且还是原子操作,因此它不仅性能优异还支持高并发。更值得一提的是,通过反范式化设计内嵌文档的方式,更新指令的原子操作可以替代一部分事务的功能,这个在原子操作和事务章节会有介绍。
2021-09-10 - 云开发入门
重磅打造的小程序学习路径课,从微信小程序到微信云开发体系化的学习,带来更加顺畅的学习体验。
2021-11-19 - 数据库性能优化指导
云开发的数据库虽然是高性能、支持弹性扩容,但是很多用户在使用的过程中,更加注重功能的实现,而忽视了数据库的设计、索引的创建以及语句的优化等对性能的影响,因此会遇到很多影响数据库性能的问题,因此这里特意总结一下云开发数据库性能优化的注意事项。 12.7.1 数据库性能与优化建议以下是一些影响数据库性能的优化建议,当然要结合具体的业务情况来处理,不能一概而论。尤其是一些请求量比较大、比较频繁,比如小程序首页的数据请求,数据库的优化要格外重视。 1、要合理使用索引 使用索引可以提高文档的查询、更新、删除、排序操作,所以要结合查询的情况,适当创建索引。要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。更多索引的细节在索引的章节里有介绍。 2、擅于结合查询情况创建组合索引 对于包含多个字段(键)条件的查询,创建包含这些字段的组合索引是个不错的解决方案。组合索引遵循最左前缀原则,因此创建顺序很重要,如果对组合索引不了解,可以结合索引的命中情况来判断组合索引是否生效。要善于使用组合索引做到用最少的索引覆盖最多的查询。 3、查询时要尽可能通过条件和 limit 限制数据 在查询里 where 可以限制处理文档的数量,而在聚合运算中 match 要放在 group 前面,减少 group 操作要处理的文档数量。无论是普通查询还是聚合查询都应该使用 limit 限制返回的数据数量。 其实云开发针对普通查询 db.collection('dbName').get()默认都有 limit 限制,在小程序端的限制为 20 条(自定义上限也是 20 条),在云函数端的限制为 100 条(自定义上限可以设置为 1000 条),聚合则在小程序端和云函数端默认都为 20 条(自定义没有上限,几万条都可以,前提是取出来的数据不能大于 16M),也就是云开发数据库已经自带了一些性能优化,我们不应该把这些默认的限制当成是一种束缚,而去随意突破这些限制。 4、推荐在小程序端增删改查数据库 可以结合数据库的安全规则,让数据库的增删改查在小程序端进行,这样速度会更快,而且还可以节省云函数的资源。 云开发数据库的增删改查可以在小程序端进行,也可以在云函数端进行,那到底应该把数据库的增删改查放在小程序端还是云函数端呢?一般情况下建议放在小程序端,这样就只会消耗数据库请求的次数,而不会额外增加消耗云函数的资源使用量 GBs、外网出流量。而云函数虽然有数据库操作的更高的权限,但是小程序端结合安全规则也是可以让数据库的权限粒度更细,也能满足大部分权限要求。 5、尽可能限制返回的字段等数据量 如果查询无需返回整个文档或只是用来判断键值是否存在,普通查询可以通过 filed、聚合查询可以通过 project 来限制返回的字段,减少网络流量和客户端的内存使用。 { "title": "为什么要学习云开发", "content": "云开发是腾讯云为移动开发者提供的一站式后端云服务", "comments": [{ "name": "李东bbsky", "created_on": "2020-03-21T10:01:22Z", "comment": "云开发是微信生态下的最推荐的后台技术解决方案" }, { "name": "小明", "created_on": "2020-03-21T11:01:22Z", "comment": "云开发学起来太简单啦" }] } 云数据库是关系型数据库,一个记录里可以嵌套非常多的数组和对象,如果取出整个记录里的所有嵌套内容就太耗性能流量了,比如上面的嵌套数组,有时候业务上并不需要显示 comments 里的某些字段,是可以通过 field 的点表示法来限制返回的字段的。 //不显示comments里的created_on .field({ "comments.created_on":0 }) //只显示comments里的comment,comments里的其他字段都不显示 .field({ "comments.comment":1 }) 6、查询量大时建议不要用正则查询 正则表达式查询不能使用索引,执行的时间比大多数选择器更长,所以业务量比较大的地方,能不用正则查询就不用正则查询(尽量用其他方式来代替正则查询),即使使用正则查询也一定要尽可能的缩写模糊匹配的范围,比如使用开始匹配符 ^ 或结束匹配符 $ 。 比如有人是这样用正则查询的,他想根据省市来筛选客户来源数据,但是客户来源的地址 address 填写的是”广东省深圳市“或”广东深圳“,省市数据并不规范一致,于是使用正则进行模糊查询,但是如果你需要经常根据地址来筛选客户来源,那你应该在数据库对数据进行处理,比如 province 和 city 来清洗重组数据从而替代模糊查询。 7、尽可能使用更新指令 通过更新指令对文档进行修改,通常可以获得更好的性能,因为更新指令不需要查询到记录就可以直接对文档进行字段级的更新,尤其是不需要更新整个文档只需要更新部分字段的场景。 还是上面的那个记录为例,比如我们需要给文章添加评论,也就是往 comments 数组里添加值,我们可以使用 [代码]_.push[代码]来给数组字段进行字段级别的操作,而不是取出整个记录,然后把评论用数组的 concat 或 push 的方法添加到记录里,再更新整个记录: .update({ data:{ comments:_.push([{ "name": "小明", "created_on": "2020-03-21T11:01:22Z", "comment": "云开发学起来太简单啦" }]) } }) 云开发数据库一个记录可能会嵌套很多层,因此也会很大,使用更新指令进行字段级别的微操比直接使用 update 这种记录级别的更新性能要更好。 8、不要对太多数据进行排序 不要一次性取出太多的数据并对数据进行排序,如果需要排序,请尽量限制结果集中的数据量,比如我们可以先用 where、match 等操作限制数据量,也就是通常要把 orderBy 放在普通查询或聚合查询的最后面。 这里尤其强调的是,发现有不少人由于对数据库的排序 orderBy 与翻页 skip 没有理解,竟然把数据库所有数据使用遍历取出来之后再来排序,哪怕是数据量只有百千条,这也是不正确的处理方式,应该禁止这么干。排序使用数据库的普通查询或聚合查询的 orderBy 就可以做到了,云开发默认的 limit 数据限制不会影响排序的结果,禁止遍历取出所有数据再来排序的愚蠢行为。 当然如果业务会需要经常对同一数据的多个字段来排序,比如商品经常会按最新上架、价格高低、产地、折扣力度等进行排序,则建议一次性取出这些数据,存储在缓存中,使用 JavaScript 的数组来进行排序,而不是用数据库查询。 9、尽量少在业务量大的地方用以下查询指令 查询中的某些查询指令可能会导致性能低下,如判断字段是否存在的[代码]exists[代码],要求值不在给定的数组内的[代码]nin[代码],表示需满足任意多个查询筛选条件[代码]or[代码],表示需不满足指定的条件[代码]not[代码],尽量少在业务使用量比较大的地方用这些查询指令。 这里所说的尽量少用不代表不用,而是能够用最直接的方式就用最直接的方式代替,让数据库查询尽可能的简单而不是搞的过于复杂,尽可能少让查询指令做这些复杂的事情。 10、集合中文档的数量可以定期归档 集合中文档的数据量会影响查询性能,对不用的数据或过期的数据可以进行定期归档并删除。比如我们也可以借助于定时触发器周期性的对数据库里的数据进行备份、删除。 11、不要让数据库请求干多余的事情,尽量少干事 能够使用 JavaScript 替代的计算、数组、对象操作等,就尽量用 JavaScript 处理;能通过数据库设计让数据库查询少计算的就尽量合理设计数据库,要尽可能的让数据库少干活,不能一次查询多个指令、正则查询套来套去的。 12、在数据库设计时可以用内嵌文档来取代 lookup 云开发数据库是非关系型数据库,可以对经常要使用 lookup 跨表查询的情况做反范式化的内嵌文档设计,通过这种方式取代联表查询 lookup 可以提升不少性能。 减少使用联表查询 lookup 的使用的方式要注意两点,一是通过内嵌文档的方式是可以减少关系型数据库那种表与表之间的关联关系的,比如要联表取出博客里最新的 10 篇文章以及文章里相应的评论,这在关系型数据库里原本是需要联表查询的,但是当把评论内嵌到文章的集合里时,就不需要联表了;二是有的时候我们只是需要跨表而不是联表,可以通过多次查询来取代联表。 13、推荐使用短字段名 和关系型数据库不同的是,云开发数据库是文档型数据库,集合中的每一个文档都需要存储字段名,因此字段名的长度相比关系型数据库来说会需要更多的存储空间。 "comments": [{ "name": "李东bbsky", "created_on": "2020-03-21T10:01:22Z", "comment": "云开发是微信生态下的最推荐的后台技术解决方案" }, { "name": "小明", "created_on": "2020-03-21T11:01:22Z", "comment": "云开发学起来太简单啦" }] 这里的字段名 name、created_on、comment 有多少个记录,有多少个嵌套的对象就会被写多少次,有时候比字段的值还要长,是比较占空间的。 12.7.2 数据库设计以及处理的优化建议1、增加冗余字段在业务上有些关键的数据可以通过间接的方式查询获取到,但是由于查询时会存在计算、跨表等问题,这个时候建议新增一些冗余字段。 比如我们要统计文章下面的评论数,可能你将文章的评论独立建了一个集合如 comments,这时候要获取每篇文章的评论数是可以根据文章的 id 条件来 count 该文章有多少条评论的。或者你也可以把每篇文章的评论数组作为子文档内嵌到每个文章记录的 comments 字段,这个时候可以通过数组的长度来算出该文章的评论数。类似于评论数的还有点赞量、收藏量等,这些虽然都是可以通过 count 或数组 length 的方式来间接获取到的,但是在评论数很多的情况下,count 和数组的 length 是非常耗性能的,而且 count 还需要独立占据一个请求。 遇到这种情况,建议在数据库设计时,要用所谓的冗余字段来记录每篇文章的点赞量、评论数、收藏量,在小程序端直接用 inc 原子自增的方式更新该字段的值。 { "title": "为什么要学习云开发", "content": "云开发是腾讯云为移动开发者提供的一站式后端云服务", "commentNum":2, //新增一个评论数的字段 "comments": [{ "name": "李东bbsky", "created_on": "2020-03-21T10:01:22Z", "comment": "云开发是微信生态下的最推荐的后台技术解决方案" }, { "name": "小明", "created_on": "2020-03-21T11:01:22Z", "comment": "云开发学起来太简单啦" }] } 比如我们希望在博客的首页展示文章列表,而每篇文章要显示评论总数。虽然我们可以通过 comments 的数组长度以及如果存在二级三级评论(尤其是这种情况),也是可以通过数组方法获取到评论数,但是不如直接查询新增的冗余字段[代码]commentNum[代码]来得直接。 2、虚假删除有时候我们的业务会需要用户经常删除数据库里面的记录或记录里的数组的情况,但是删除数据是非常耗费性能的一件事,碰到业务高峰期,数据库就会出现性能问题。这个时候,建议新增冗余字段做虚假删除,比如给记录添加 delete 的字段,默认值为 false,当执行删除的时候,可以将字段的值设置 true,查询时只显示 delete 为 false 的记录,这样数据在前端就不显示了,做到了虚假删除,在业务低谷时比如凌晨可以结合定时触发器每天这个时候清理一遍。 3、尽量不要把数据库请求放到循环体内我们经常会有查询数据库里的数据,并对数据进行处理之后再写回数据库的需求,如果查询到的数据有很多条时,就会需要我们进行循环处理,不过这个时候一定要注意,不要把数据库请求放到循环体内,而是先一次性查询多条数据,在循环体内对数据进行处理之后再一次性写回数据库。 当然小程序有些接口不能进行数组操作,只能一条一条执行,比如发送订阅消息、上传文件等操作等,这个避免的不了的例外。但是有些是可以通过数据库的设计来规避这个问题的,比如把经常要新增大量记录的数据库设计为只需要新增内嵌文档的数组数据等。 4、尽量使用一个数据库请求代替多个数据库请求在数据库的设计上以及在数据库请求的代码上,尽可能用一个数据库请求来代替多个数据库请求,尤其是用户最常访问的首页,如果一个页面的数据库请求太多,会导致数据库的并发问题。有些数据能够缓存到小程序端就缓存到小程序度,不必过分强调数据的一致性。 5、规划好文档适时创建空字段我们有这样一个集合 user,最终会用来存储用户的个人信息,比如当我们在用户点击登录时会获取用户的昵称和头像,于是一般的逻辑是我们会在数据库创建一个记录,如下所示: _id:"", userInfo:{ "name":"李东bbsky", "avatarUrl":"头像地址" } 但是更好的方式是,我们应该创建一个完整的记录(按照最终的字段设计),哪怕现在还没有数据,也要一致性建好这些空字段,方便以后直接使用 update 的方式来往里面填充数据。 _id:"", userInfo:{ "name":"李东bbsky", "avatarUrl":"头像地址", "email":"", "address":"" ... }, stars:[],//存储点赞的文章 collect:[] //存储收藏的文章 12.7.3 慢查询与告警目前我们没法直接查看数据库请求所花费的时间,但是有一些其他数据作为佐证,在云函数端进行数据库请求,如果云函数的执行时间超过 100ms 甚至更多,则基本可以判定为慢查询,数据库需要优化。这时,慢查询不仅会影响数据库的性能,还会影响云函数的性能。 我们知道云函数和云数据库的并发都是非常依赖他们的耗时的,如果数据库查询速度变慢,查询一次耗时由几十毫秒增加到几百毫秒,甚至以秒计算,都是十分耗费资源和影响并发的: 云函数资源使用量 GBs:资源使用量 = 函数配置内存 X 运行计费时长,如果云函数里有数据库请求耗费了运行时长,云函数资源使用量也会增加;不过云函数的并发统一上限为 1000,通常是很难达到的;数据库的 QPS = 数据库同时连接数 * 1000ms/数据库请求的执行时间,如果数据库请求的执行时间出现大幅上升,QPS 也就会成倍的下降,非常影响数据库的并发,会出现[代码]Connection num overrun[代码]的报错。我们可以在云开发控制台设置-告警设置来给指定的云函数尤其是业务调用最频繁的云函数设置运行时间以及云函数运行错误的告警,以便随时了解云开发环境的运行状况。
2021-09-10