【MongoDB】MongoDB的核心-索引原理及索引优化、及查询聚合优化实战案例(超详细)

news2024/11/14 6:26:43

在这里插入图片描述

文章目录

    • 一、数据库查询效率问题引出索引需求
    • 二、索引的基本原理及作用
      • (一)索引的创建及数据组织
      • (二)不同类型的索引
      • (三)索引的额外属性
    • 三、索引的优化与查询计划分析
      • (一)通过profiling监测慢请求
      • (二)查询计划分析优化索引使用
    • 四、查询聚合优化
      • (一)案例背景
        • 问题描述
        • 问题分析
          • 1. 定位慢查询
          • 2. 分析慢查询语句
            • 第一步:`$match`操作
            • 第二步:`$project`操作
            • 第三步:`$group`操作
        • 查看DB/Server/Collection的状态
          • 1. DB状态
          • 2. 查看`orders`这个collection的状态
        • 性能优化
          • 1. 性能优化 - 索引
          • 2. 性能优化 - 聚合大量数据
        • 小结

更多相关内容可查看

一、数据库查询效率问题引出索引需求

当在使用MongoDB等数据库进行集合查询时,如果遇到查询效率低下的情况,就可能需要考虑使用索引了。以MongoDB为例,在向集合插入多个文档后,每个文档经过底层存储引擎持久化会有一个位置信息(如mmapv1引擎里是『文件id + 文件内offset』,wiredtiger存储引擎里是其生成的一个key),通过这个位置信息能从存储引擎里读出该文档。

mongo-9552:PRIMARY> db.person.find()
{ "_id" : ObjectId("571b5da31b0d530a03b3ce82"), "name" : "jack", "age" : 19 }
{ "_id" : ObjectId("571b5dae1b0d530a03b3ce83"), "name" : "rose", "age" : 20 }
{ "_id" : ObjectId("571b5db81b0d530a03b3ce84"), "name" : "jack", "age" : 18 }
{ "_id" : ObjectId("571b5dc21b0d530a03b3ce85"), "name" : "tony", "age" : 21 }
{ "_id" : ObjectId("571b5dc21b0d530a03b3ce86"), "name" : "adam", "age" : 18 }

假设要执行一个查询操作,比如db.person.find( {age: 18} ),如果没有索引,就需要遍历所有的文档(即进行“全表扫描”),根据位置信息读出文档后,再对比age字段是否为18。当集合文档数量较少时,全表扫描的开销可能不大,但当文档数量达到百万、千万甚至上亿时,全表扫描的开销会非常大,一个查询耗费数十秒甚至几分钟都有可能。

二、索引的基本原理及作用

(一)索引的创建及数据组织

比如上面的例子里,person集合里包含插入了5个文档,假设其存储后位置信息如下

位置信息文档
pos1{“name” : “jack”, “age” : 19 }
pos2{“name” : “rose”, “age” : 20 }
pos3{“name” : “jack”, “age” : 18 }
pos4{“name” : “tony”, “age” : 21}
pos5{“name” : “adam”, “age” : 18}

如果想加速 db.person.find( {age: 18} ),就可以考虑对person表的age字段建立索引。

db.person.createIndex( {age: 1} )  // 按age字段创建升序索引

建立索引后,MongoDB会额外存储一份按age字段升序排序的索引数据,索引结构类似如下,索引通常采用类似btree的结构持久化存储,以保证从索引里快速(O(logN)的时间复杂度)找出某个age值对应的位置信息,然后根据位置信息就能读取出对应的文档。

age位置信息
18pos3
18pos5
19pos1
20pos2
21pos4

简单来说,索引就是将文档按照某个(或某些)字段顺序组织起来,以便能根据该字段高效地进行查询。它至少能优化以下场景的效率:

  • 查询场景:比如查询年龄为18的所有人,有了索引就无需全表扫描,可直接通过索引快速定位到符合条件的文档。
  • 更新/删除场景:在将年龄为18的所有人的信息进行更新或删除时,因为更新或删除操作需要先根据条件查询出所有符合条件的文档,所以本质上也是在优化查询环节。
  • 排序场景:将所有人的信息按年龄排序时,如果没有索引,需要全表扫描文档,然后再对扫描的结果进行排序;而有了索引,可利用索引的有序性更高效地完成排序。

MongoDB默认会为插入的文档生成_id字段(如果应用本身没有指定该字段),并且为了保证能根据文档id快速查询文档,MongoDB默认会为集合创建_id字段的索引。

mongo-9552:PRIMARY> db.person.getIndexes() // 查询集合的索引信息
[
    {
        "ns" : "test.person",  // 集合名
        "v" : 1,               // 索引版本
        "key" : {              // 索引的字段及排序方向
            "_id" : 1           // 根据_id字段升序索引
        },
        "name" : "_id_"        // 索引的名称
    }
]

(二)不同类型的索引

MongoDB支持多种类型的索引,每种类型适用于不同的使用场合:

  • 单字段索引(Single Field Index)

    • 通过db.person.createIndex( {age: 1} )语句可针对age创建单字段索引,能加速对age字段的各种查询请求,是最常见的索引形式,MongoDB默认创建的_id索引也属于这种类型。
    • {age: 1}代表升序索引,也可通过{age: -1}来指定降序索引,对于单字段索引,升序/降序效果是一样的。
      db.person.createIndex( {age: 1, name: 1} ) 
      
  • 复合索引 (Compound Index)

    • 它是单字段索引的升级版本,针对多个字段联合创建索引,先按第一个字段排序,第一个字段相同的文档按第二个字段排序,依次类推。例如,通过db.person.createIndex( {age: 1, name: 1} )可针对agename这2个字段创建一个复合索引。
    • 复合索引能满足的查询场景比单字段索引更丰富,不光能满足多个字段组合起来的查询(如db.person.find( {age: 18, name: "jack"} )),也能满足匹配复合索引前缀的查询(如{age: 1}{age: 1, name: 1}的前缀,所以db.person.find( {age: 18} )的查询也能通过该索引来加速),但像db.person.find( {name: "jack"} )这种只涉及部分字段且不符合前缀规则的查询则无法使用该复合索引。在创建复合索引时,字段的顺序除了受查询需求影响,还需考虑字段的值分布情况。比如age字段取值有限,相同age的文档较多,而name字段取值丰富,相同name的文档较少,此时先按name字段查找,再在相同name的文档里查找age字段会更为高效。
      db.person.createIndex( {name: 1, age: 1} ) 
      
  • 多key索引 (Multikey Index)

    • 当索引的字段为数组时,创建出的索引称为多key索引。例如,在person表加入一个habbit字段(数组)用于描述兴趣爱好,通过db.person.createIndex( {habbit: 1} )可自动创建多key索引,用于查询有相同兴趣爱好的人。
       {"name" : "jack", "age" : 19, habbit: ["football, runnning"]}
       db.person.createIndex( {habbit: 1} )  // 自动创建多key索引
       db.person.find( {habbit: "football"} )
      
  • 其他类型索引

    • 哈希索引(Hashed Index):按照某个字段的hash值来建立索引,目前主要用于MongoDB Sharded Cluster的Hash分片,hash索引只能满足字段完全匹配的查询,不能满足范围查询等。
    • 地理位置索引(Geospatial Index):能很好地解决O2O的应用场景,比如“查找附近的美食”、“查找某个区域内的车站”等。
    • 文本索引(Text Index):能解决快速文本查找的需求,比如对于一个博客文章集合,可针对博客的内容建立文本索引,以便根据博客内容快速查找。

(三)索引的额外属性

MongoDB除了支持多种不同类型的索引,还能对索引定制一些特殊的属性:

  • 唯一索引 (unique index):保证索引对应的字段不会出现相同的值,比如_id索引就是唯一索引。
  • TTL索引:可以针对某个时间字段,指定文档的过期时间(经过指定时间后过期 或 在某个时间点过期)。
  • 部分索引 (partial index):只针对符合某个特定条件的文档建立索引,在3.2版本才支持该特性。
  • 稀疏索引(sparse index):只针对存在索引字段的文档建立索引,可看做是部分索引的一种特殊情况。

三、索引的优化与查询计划分析

(一)通过profiling监测慢请求

MongoDB支持对DB的请求进行profiling,目前支持3种级别的profiling:

  • 0级:不开启profiling。
  • 1级:将处理时间超过某个阈值(默认100ms)的请求都记录到DB下的system.profile集合(类似于mysql、redis的slowlog),生产环境通常建议使用此级别,并根据自身需求配置合理的阈值,用于监测慢请求的情况,以便及时进行索引优化。
  • 2级:将所有的请求都记录到DB下的system.profile集合,生产环境需慎用。

(二)查询计划分析优化索引使用

当索引已经建立了,但查询还是很慢时,就需要深入分析索引的使用情况,可通过查看详细的查询计划来决定如何优化。通过执行计划可以看出以下问题:

  • 根据某个/些字段查询,但没有建立索引。
  • 根据某个/些字段查询,但建立了多个索引,执行查询时没有使用预期的索引。

例如,建立索引前,db.person.find( {age: 18} )必须执行COLLSCAN(全表扫描);

mongo-9552:PRIMARY> db.person.find({age: 18}).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.person",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "age" : {
                "$eq" : 18
            }
        },
        "winningPlan" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "age" : {
                    "$eq" : 18
                }
            },
            "direction" : "forward"
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "localhost",
        "port" : 9552,
        "version" : "3.2.3",
        "gitVersion" : "b326ba837cf6f49d65c2f85e1b70f6f31ece7937"
    },
    "ok" : 1
}

建立索引后,通过查询计划可以看出,先进行[IXSCAN](从索引中查找),然后FETCH`,读取出满足条件的文档。

mongo-9552:PRIMARY> db.person.find({age: 18}).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.person",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "age" : {
                "$eq" : 18
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "age" : 1
                },
                "indexName" : "age_1",
                "isMultiKey" : false,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "age" : [
                        "[18.0, 18.0]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "localhost",
        "port" : 9552,
        "version" : "3.2.3",
        "gitVersion" : "b326ba837cf6f49d65c2f85e1b70f6f31ece7937"
    },
    "ok" : 1
}

需要注意的是,索引并不是越多越好,集合的索引太多,会影响写入、更新的性能,因为每次写入都需要更新所有索引的数据。所以system.profile里的慢请求可能是索引建立得不够导致,也可能是索引过多导致。

四、查询聚合优化

(一)案例背景

我们有一个电商订单分析系统,使用MongoDB存储订单数据。当执行一个分析接口,获取特定店铺在某一周内的订单商品分类统计信息时,发现查询速度非常慢,严重影响用户体验。

问题描述

执行订单分析接口,查询特定店铺(假设店铺ID为“20001”)在某一周(2024 - 05 - 01T00:00:00.000Z到2024 - 05 - 07T23:59:59.999Z)内的订单商品分类统计,需要花费约12秒,这明显不符合性能要求。

问题分析
1. 定位慢查询

首先查看当前mongo profile的级别,通过db.getProfilingLevel()发现其为0,即默认没有记录。设置profile级别为记录慢查询模式,设置阈值为1000ms,即db.setProfilingLevel(1, 1000)。再次执行订单分析查询接口,查看Profile记录。

2. 分析慢查询语句

通过查看Profile记录,发现执行的查询是一个聚合管道(pipeline):

第一步:$match操作
{
    "$match": {
        "storeId": "20001",
        "$and": [
            {
                "orderTime": {
                    "$gte": ISODate("2024-05-01T00:00:00.000Z"),
                    "$lte": ISODate("2024-05-07T23:59:59.999Z")
                }
            }
        ]
    }
}

用于匹配店铺ID为“20001”且订单时间在指定一周内的订单记录。

第二步:$project操作
{
    "$project": {
        "productCategory": 1,
        "orderDate": {
            "$concat": [
                {
                    "$substr": [
                        {
                            "$year": [
                                "$orderTime"
                            ]
                        },
                        0,
                        4
                    ]
                },
                "-",
                {
                    "$substr": [
                        {
                            "$month": [
                                "$orderTime"
                            ]
                        },
                        0,
                        2
                    ]
                },
                "-",
                {
                    "$substr": [
                        {
                            "$dayOfMonth": [
                                "$orderTime"
                            ]
                        },
                        0,
                        2
                    ]
                }
            ]
        }
    }
}

除了提取productCategory字段外,还对orderTime字段进行处理,拼接为“yyyy - MM - dd”格式,并将其命名为orderDate

第三步:$group操作
{
    "$group": {
        "_id": {
            "orderDate": "$orderDate",
            "productCategory": "$productCategory"
        },
        "count": {
            "$sum": 1
        }
    }
}

orderDateproductCategory进行分组,统计不同日期和商品分类对应的订单数量。

从Profile中可以看到相关指标:

  • millis:花费了12010毫秒返回查询结果。
  • ts:命令执行时间。
  • info:命令内容。
  • query:代表查询。
  • nsecommerce.orders(代表查询的库与集合)。
  • nreturned:返回记录数及用时。
  • reslen:返回的结果集大小(字节数)。
  • nscanned:扫描记录数量。

发现nscanned数很大,接近记录总数,可能没有使用索引查询。

查看DB/Server/Collection的状态
1. DB状态

查看数据库整体状态,包括服务器版本、运行时间、连接数、各种操作计数器(如插入、查询、更新、删除等操作的次数)、存储引擎信息等。示例部分信息如下:

{
    "host": "ECOMMONGODB",
    "version": "6.0.5",
    "process": "mongod",
    "pid": NumberLong(2005),
    "uptime": 12345678.0,
    "uptimeMillis": NumberLong(12345678901),
    "uptimeEstimate": NumberLong(12345678),
    "localTime": ISODate("2024-05-08T10:20:30.123Z"),
    "asserts": {
        "regular": 0,
        "warning": 0,
        "msg": 0,
        "user": 12345,
        "rollovers": 0
    },
    "connections": {
        "current": 120,
        "available": 800,
        "totalCreated": 13000
    },
    // 其他更多信息...
    "ok": 1.0
}
2. 查看orders这个collection的状态
{
    "ns": "ecommerce.orders",
    "size": 987654321,
    "count": 3500000,
    "avgObjSize": 282,
    "storageSize": 234567890,
    "capped": false,
    "wiredTiger": {
        // wiredTiger存储引擎相关详细信息...
    },
    "nindexes": 1,
    "totalIndexSize": 30123456,
    "indexSizes": {
        "_id_": 30123456
    },
    "ok": 1.0
}
性能优化
1. 性能优化 - 索引

目前只有_id索引,接下来对orders集合创建storeIdorderTimeproductCategory字段的索引:

db.orders.ensureIndex({"storeId": 1, "orderTime": 1, "productCategory": 1});
db.orders.ensureIndex({"orderTime": 1});
db.orders.ensureIndex({"productCategory": 1});
db.orders.ensureIndex({"storeId": 1});

创建索引后,查询特定店铺一周内的订单商品分类统计信息,时间缩短到了500ms,效果显著。但当查询一个月的数据时,仍然需要15秒。

通过增加索引小结:添加索引解决了针对索引字段查询的效率问题,但对于大量数据的聚合操作,仅靠索引不能完全解决性能问题。例如,在没有索引的情况下,从500万条数据中找出特定店铺的订单可能需要全表扫描,耗时很长;而有了索引,命中索引查询(IXSCAN)速度提升明显。不过,对于聚合操作,随着数据量增大,性能问题依然存在。同时,判断效率优化情况应该看执行计划,而不仅仅是执行时间,因为执行时间可能受到多种因素影响。

2. 性能优化 - 聚合大量数据

对于这种查询聚合大量数据的问题,考虑到这是一个类似OLAP的操作,对其性能期望不能过高,因为大量数据的I/O操作远超OLTP操作。但仍有一定的优化空间:

  • 在订单插入或更新时,对每个店铺每天的每个商品分类的订单数量进行实时计数,并存储在一个专门的缓存集合中。例如,以{storeId: "20001", orderDate: "2024-05-01", productCategory: "Electronics", count: 10}的形式存储。
  • 每隔一段时间(如每天凌晨)对缓存集合进行一次完整的统计和更新,确保数据的准确性。这样在查询订单商品分类统计信息时,可以直接从缓存集合中获取数据,大大减少了查询和聚合的时间。
小结
  • 慢查询定位:通过Profile分析慢查询。
  • 查询优化:通过添加相应索引提升查询速度。
  • 聚合大数据方案:对于类似OLAP的聚合操作,要合理降低性能期望。从源头入手,在数据插入或更新时做好部分统计工作,缓存结果,以便在查询时直接使用,从而提升整体性能。同时,要结合执行计划来评估优化效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2239986.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

约束(MYSQL)

not null(非空) unique(唯一) default(默认约束,规定值) 主键约束primary key(非空且唯一) auto_increment(自增类型) 复合主键 check&#xff08…

如何保证RabbitMQ的可靠性传输

文章目录 producer到broke生产者到交换机:confirm交换机到队列:returns模式队列溢出:可以采用死信等方式①ConfirmCallback接口②ReturnCallback接口 Broke内部Broke到达消费者 producer到broke 发送方确认 生产者到交换机:conf…

数据库参数备份

MySQL #!/bin/bash # 获取当前日期和时间的时间戳 TIMESTAMP$(date "%Y%m%d-%H%M%S")# 0、创建目录 mkdir /tmp/parameter_$TIMESTAMP/# 1、获取所有命名空间 echo "1、获取所有命名空间" NAMESPACES$(kubectl get ns | grep qfusion- | grep -v qfusion-…

拦截器实现http请求访问本地图片

本文来记录下拦截器实现http请求访问本地图片 文章目录 概述代码实现本文小结 概述 如下图,本机(服务器)存储的图片想要在浏览器上通过Url地址访问: 浏览器直接访问 代码实现 烂机器实现文件真实地址和物理地址之间的映射 Slf4j Configuration public cl…

【数据结构】快排之三路划分

目录 一、前言 二、 快排性能的关键点分析 三、 三路划分基本思想 四、 思路分析 五、提醒 六、代码实现 一、前言 继续对快速排序的深入优化进行探讨 二、 快排性能的关键点分析 决定快排性能的关键点是每次单趟排序后,key对数组的分割。 如果每次选key都能…

Web安全之SQL注入---基础

文章目录 SQL注入简介SQL注入基础SQL注入分类SQL注入流程 SQL注入简介 什么是SQL注入? SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理…

iOS 18.1,未公开的新功能

童锦程祖师爷曾说过:“发誓可以,发朋友圈不行。”表面上看是渣男语录,实际上也说明了人们对隐私的看重。 在当今生活中,智能手机可能是最私密的电子产品,没有之一。不管是照片、联系人、短信、APP数据,甚至…

网页版五子棋——对战模块(服务器端开发②)

前一篇文章:网页版五子棋——对战模块(服务器端开发①)-CSDN博客 项目源代码:Java: 利用Java解题与实现部分功能及小项目的代码集合 - Gitee.com 目录 前言 一、创建并注册 GameAPI 类 1.创建 GameAPI 类 2.注册 GameAPI 类 …

★ C++进阶篇 ★ 异常

Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将和大家一起学习C中的异常 ~ ​❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️ 澄岚主页&#xff1a;椎名澄嵐-CSDN博客 C基础篇专栏&#xff1a;★ C基础篇 ★_椎名澄嵐的博客-CSDN博客 C进阶篇专栏&am…

MFC图形函数学习08——绘图函数的重载介绍

在《MFC图形函数学习06——画椭圆弧线函数》中介绍了CPoint类、POINT结构体&#xff1b;在《MFC图形函数学习07——画扇形函数》中介绍了CRect类、RECT结构体。在介绍完后&#xff0c;没有介绍它们怎样使用。实际上&#xff0c;这些类和结构体对象或指针也是我们学习过的绘图函…

尽量通俗易懂地概述.Net U nity跨语言/跨平台相关知识

本文参考来自唐老狮,Unity3D高级编程:主程手记,ai等途径 仅作学习笔记交流分享 目录 1. .Net是什么? 2. .Net框架的核心要点? 跨语言和跨平台 .Net x Unity跨平台发展史 Net Framework 2002 Unity跨平台之 Mono 2004 Unity跨平台之 IL2CPP 2015 二者区别 .NET Core …

Flink执行sql时报错

[ERROR] Could not execute SQL statement. Reason: java.lang.ClassNotFoundException: org.apache.flink.table.planner.delegation.ParserFactory flink-1.15.4的lib里面存在flink-sql-connector-hive-3.1.2_2.12-1.15.4.jar时&#xff0c;似乎会跟hdfs产生冲突&#xff0c…

实现API接口的自动化

API接口自动化测试的最佳实践有哪些&#xff1f; API接口自动化测试的最佳实践包括以下几个方面&#xff1a; 确定测试范围和目标&#xff1a;明确需要测试的API接口和功能点&#xff0c;确定测试的目标和预期结果 编写测试用例&#xff1a;根据API文档和需求&#xff0c;编…

uni-app中使用 unicloud 云开发平台③

文章目录 六、hbuilderX 中使用 unicloud 云开发平台文档传统业务开发流程什么是 unicloudunicloud 优点开发流程uncloud 构成云数据库云存储及 CDN创建云函数工程七、unicloud api 操作云函数调用云函数实现云数据库基本增删改查1. 获取数据库引用云存储操作六、hbuilderX 中使…

【缓存策略】你知道 Refresh-ahead(预刷新)这个缓存策略吗?

👉博主介绍: 博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家,WEB架构师,阿里云专家博主,华为云云享专家,51CTO 专家博主 ⛪️ 个人社区:个人社区 💞 个人主页:个人主页 🙉 专栏地址: ✅ Java 中级 🙉八股文专题:剑指大厂,手撕 J…

零基础Java第十六期:抽象类接口(二)

目录 一、接口&#xff08;补&#xff09; 1.1. 数组对象排序 1.2. 克隆接口 1.3. 浅拷贝和深拷贝 1.4. 抽象类和接口的区别 一、接口&#xff08;补&#xff09; 1.1. 数组对象排序 我们在讲一维数组的时候&#xff0c;使用到冒泡排序来对数组里的元素进行从小到大或从大…

django入门【05】模型介绍(二)——字段选项

文章目录 1、null 和 blank示例说明⭐ null 和 blank 结合使用的几种情况总结&#xff1a; 2、choices**choices 在 Django 中有以下几种形式&#xff1a;**&#xff08;1&#xff09; **简单的列表或元组形式**&#xff08;2&#xff09; **字典映射形式**&#xff08;3&#…

数据量大Excel卡顿严重?选对报表工具提高10倍效率

当几万行的数据把软件频频跑崩&#xff0c;当珍贵的数据资源无法便捷复用&#xff0c;当数据填报的本地文档在各个电脑中传来传去……在各大岗位要求中频频出现的Excel&#xff0c;作为个人办公软件绝无仅有&#xff0c;但作为企业场景下的报表工具&#xff0c;效率显然不足。 …

如何用WordPress和Shopify提升SEO表现?

选择合适的建站程序对于SEO优化非常重要。目前&#xff0c;WordPress和Shopify是两种备受推崇的建站平台&#xff0c;各有优势。 WordPress最大的优点是灵活性。它支持大量SEO插件&#xff0c;帮助你调整元标签、生成站点地图、优化内容结构等。这些功能让你能够轻松地提升网站…

vue 计算属性get set

<template><div id"app"><h1>用户信息</h1><p>全名&#xff1a;{{ fullName }}</p><input v-model"fullName" placeholder"请输入全名" /><p>姓&#xff1a;{{ firstName }}</p><p>…