MongoDB03 - MongoDB索引,事务和安全

news/2025/2/24 15:43:18

MongoDB索引,事务和安全

文章目录

  • MongoDB索引,事务和安全
    • 一:事务和锁
      • 1:MongoDB事务机制:不建议使用
      • 2:MongoDB的锁机制
    • 二:MongoDB的索引机制
      • 1:初始索引
      • 2:索引详解
        • 2.1:单列索引
        • 2.2:复合索引
        • 2.3:唯一索引
        • 2.4:部分索引
        • 2.5:TTL索引
        • 2.6:全文索引
        • 2.7:通配符索引
      • 3:explain执行计划
    • 三:安全与权限管理
      • 1:简单的用户创建和使用
      • 2:Mongo中的内置角色
      • 3:Mongo自定义角色

一:事务和锁

1:MongoDB事务机制:不建议使用

MongoDB作为数据库家族的一员,自然也支持事务机制,只不过相较于InnoDB的事务机制而言,MongoDB事务方面并没有那么强大

这倒不是因为官方技术欠缺,而是由于MongoDB的定位是:大数据、高拓展、高可用、分布式

因此在实现事务时,不仅仅要考虑单机事务,而且需要考虑分布式事务,复杂度上来之后,自然无法做到MySQL-InnoDB那种单机事务的强大性。

这里也列出MongoDB事务方面的改进过程,如下:

  • 3.0版本中,引入WiredTiger存储引擎,开始支持单文档事务;
  • 4.0版本中,开始支持多文档事务,以及副本集(主从复制)架构下的事务;
  • 4.2版本中,开始支持分片集群、分片式多副本集架构下的事务。
// 开启一个会话
var session = db.getMongo().startSession({
    // readPreference:定义读操作的节点优先级和模式
    // mode:指定读取模式
    // -> primary:只从主节点读取数据;
    // -> secondary:只在从节点上读取数据;
    // -> primaryPreferred:优先从主节点读取,主节点不可用,转到从节点读取;
    // -> secondaryPreferred:优先在从节点读取,从节点不可用,转到主节点读取;
    // -> nearest:从可用节点中选择最近的节点进行读取;
    readPreference:{mode: "primary"}
});
// 开启事务
session.startTransaction(
    {
        // readConcern:指定事务的读取模式
        // level:指定一致性级别
        // --> available:读取已提交的数据,可能包含尚未持久化的事务更改;
        // --> snapshot:读取事务开始时的一致快照,不包含未提交的事务更改;
        readConcern: {level:"snapshot"},  // 指定读模式为快照读
        // writeConcern:指定事务的写入模式
        // w:指定写操作的确认级别(同步模式)
        // --> [number]:写操作在写入指定数量的节点后,返回写入成功;
       	// --> majority: 写操作在写入大多数节点(半数以上)后,返回写入成功;
        // --> tagSetName:写操作在写入指定标签的节点后,返回写入成功
        // j:写入是否应被持久化到磁盘
        // --> wtimeout:指定写入确认的超时时间;
        writeConcern:{w: "majority"} // 写模式的同步模式级别为半同步,即写入半数以上节点后再返回成功
    }
);
// 获取要操作的集合对象
var trx_coll = session.getDatabase("库名").getCollection("集合名");

// 要在事务里执行的CRUD操作
// ......

// 回滚事务命令
session.abortTransaction();
// 提交事务命令
session.commitTransaction();
// 关闭会话命令
session.endSession();

上述命令了解即可,毕竟MongoDB本身就不适用于强事务的场景,原因如下:

  • MongoDB的事务必须在60s内完成,超时将自动取消(因为要考虑分布式环境);
  • 涉及到事务的分片集群中,不能有仲裁节点;
  • 事务会影响集群数据同步效率、节点数据迁移效率;
  • 多文档事务的所有操作,必须在主节点上完成,包括读操作;

综上所述,就算MongoDB支持事务,可实际使用起来也会有诸多限制,因此在不必要的情况下,不建议使用其事务机制。

2:MongoDB的锁机制

MongoDB 锁机制是其并发控制的重要组成部分,目的是为了确保多线程多用户访问下数据的完整性和一致性,主要分类两大类:MMAPv1 引擎的锁机制和 WiredTiger 引擎的锁机制。

在使用 MMAPv1 存储引擎的 MongoDB 版本中,全局锁时其主要的并发控制手段。全局锁有两种模式:

  • 读锁:允许多个读操作共享,但组织任何写操作
  • 写锁:独占锁,一旦获取将组织其他读写操作,直至锁释放

WiredTiger 引擎使用了更细粒度的锁机制,主要为:

  • 文档锁:锁定单个文档,允许多个并发读操作,但写操作会互斥。这大大减少了锁竞争,提高了并发写入能力,从而使得在高并发场景下也能保持较好的性能。
  • 多版本并发控制(MVCC):WiredTiger 实现了一种 MVCC 机制,为每个事务创建数据的多个版本。这样,读操作可以不受写操作的影响,看到事务开始时的一致性视图,而写操作则在新版本上进行,直到事务提交后才会对外可见。这增强了系统的并发能力,同时保证了事务的隔离性。
  • 范围锁:在某些情况下,为了保持数据一致性,WiredTiger可能会锁定一个文档范围,防止其他操作修改该范围内的数据。
  • 乐观锁:除了传统的锁机制,WiredTiger还采用了乐观锁策略,尤其在处理读写操作时。乐观锁依赖于文档版本控制,每个文档都有一个内部版本号。写操作前先读取版本号,写入时检查版本号是否改变,若未变则成功,否则重试。这种方式减少了锁的使用,提高了并发效率

非常复杂:有时间研究下大佬的笔记

// 这里简单的列出手动操作锁的命令:
// 获取锁
db.collection.fsyncLock();
// 释放锁
db.collection.fsyncUnlock();

二:MongoDB的索引机制

任何数据库都有索引这一核心功能,MongoDB自然不例外,而且MongoDB在索引方面特别完善,毕竟是新的数据库,肯定汇集百家之长

Mongo索引官方文档

早版本的MongoDB中,索引底层默认使用B-Tree结构

4.x版本后,MongoDB推出了V2版索引,默认使用变种B+Tree来作为索引的数据结构(和MySQL索引的数据结构相同)

1:初始索引

MongoDB会为每个集合生成一个默认的_id字段,该字段在每个文档中必须存在,可以手动赋值

如果不赋值则会默认生成一个ObjectId, 该字段则是集合的主键,MongoDB会基于该字段创建一个默认的主键索引

后续基于_id字段查询数据时,会走索引来提升查询效率。

当咱们基于其他字段查询时,由于未使用_id作为条件,这会导致find语句走全表查询,即从第一条数据开始,遍历完整个集合,从而检索到目标数据。

当集合中的数据量,达到百万、千万、甚至更高时,意味着效率会直线下滑,在这种情况下,必须得由我们手动为频繁作为查询条件的字段建立索引。

Mongo中的索引分类

  • 从字段数量的维度划分:单列,组合,多键,部分
  • 从排序的维度划分:升序,降序,多序
  • 从功能的维度划分:主键,普通,唯一,全文,空间
  • 从数据结构的维度划分:B+Tree,Hash
  • 从存储方式的维度划分:聚簇,非聚簇
  • 从索引性质的维度划分:稀疏,TTL,隐藏,通配符

在这里插入图片描述

2:索引详解

MongoDB中创建索引的命令:

db.collection.createIndex(<key and index type specification>, <options>);

前面提到的所有索引,都是通过这一个方法创建,不同类型的索引,通过里面的参数和选项来区分,下面说明一下参数和可选项。

第一个参数主要是传字段,以及索引类型,这里可以传一或多个字段,用于表示单列/复合索引。

第二个参数表示可选项,如下:

  • background:是否以后台形式创建索引,因为创建索引会导致其他操作阻塞;
  • unique:是否创建成唯一索引;
  • name:指定索引的名称;
  • sparse:是否对集合中不存在的索引字段的文档不启用索引;
  • expireAfterSeconds:指定存活时间,超时后会自动删除文档;
  • v:指定索引的版本号;
  • weights:指定索引的权重值,权值范围是1~99999,当一条语句命中多个索引时,会根据该值来选择;

以下述集合为例,演示各种索引的创建过程

db.animals.insert([
    {_id:1, name:"肥肥", age:3, hobby:"竹子", color:"黑白色"},
    {_id:2, name:"花花", color:"黑白色"},
    {_id:4, name:"黑熊", age:3, food:{name:"黄金竹", grade:"S"}},
    {_id:5, name:"白熊", age:4, food:{name:"翠绿竹", grade:"B"}},
    {_id:6, name:"棕熊", age:3, food:{name:"明月竹", grade:"A"}},
    {_id:7, name:"红熊", age:2, food:{name:"白玉竹", grade:"S"}},
    {_id:8, name:"粉熊", age:6, food:{name:"翡翠竹", grade:"A"}},
    {_id:9, name:"紫熊", age:3, food:{name:"烈日竹", grade:"S"}},
    {_id:10, name:"金熊", age:6, food:{name:"黄金竹", grade:"S"}}
]);
2.1:单列索引
// 基于name字段,创建一个名为idx_name的单列普通索引,排序方式为降序
db.animals.createIndex(
    {name: -1}, 
    {name: "idx_name"}
);

🎉 对于单字段的索引而言,排序方式并不重要,因为索引底层默认是B+Tree,每个文档之间会有双向指针,为此,MongoDB基于单字段索引查询时,既可以向前、也可以向后查找数据

在这里插入图片描述
创建完成后,可以通过db.animals.getIndexes()命令查询索引

在这里插入图片描述

多键索引

例如现在将集合中的爱好字段,变为一个数组:

{
    _id:1, 
    name:"肥肥", 
    age:3, 
    hobby:[
        "竹子", "睡觉"
    ], 
    color:"黑白色"
}

现在给hobby字段创建一个索引,这时叫啥索引?多键索引!

因为这里是基于单个数组类型的字段在建立索引,所以MongoDB会为数组中的每个元素,都生成索引的条目(即索引键)

由于一个文档的数组字段,拥有多个元素,因此会创建多个索引键,这也是“多键索引”的名字由来。

2.2:复合索引

复合索引是指基于多个字段创建的索引,例如:

db.animals.createIndex(
    {name:-1, age:1}, // 依据name降序,age升序创建一个聚合索引
    {name:"idx_name_age"} // 复合索引的名称
);

🎉 这个排序就有意义了,MongoDB生成索引键时,会按照指定的顺序,来将索引键插入到树中。

索引键=索引字段的值,比如现在一个文档的name=张三、age=3,索引键为张三3

注意:由于这里的顺序是{name:-1, age:1},所以当排序查询时,支持sort({name:-1,age:1})、sort({name:1,age:-1}),因为这两个顺序和树的组成顺序要么完全相同、相反

而当执行sort({name:-1,age:-1})、sort({name:1,age:1})排序查询时,将不会使用索引,因为这时和树的顺序冲突。

2.3:唯一索引

必须创建在不会出现重复值的字段上,基于唯一索引查找数据时,找到第一个满足条件的数据,就会立马停止匹配,毕竟该字段的值在集合中是唯一的

db.animals.createIndex(
    {name:1}, // 在name字段上正序创建索引
    {unique: true} // 声明是唯一索引
);

只需要将unique设置为true即可,如果尝试插入已有的name,将会触发报错

两个都是空也认为是冲突

db.animals.insertOne(
    {_id: 66, age: 12}
)

db.animals.insertOne(
    {_id: 77, age: 13}
)

在这里插入图片描述

这种情况怎么解决呢,声明name索引是稀疏索引即可

// 将刚才的给删除了
db.animals.dropIndex("name_1");

// 在创建一个,这次指明这个索引不但是唯一的,还是稀疏的,允许重复的null值
db.animals.createIndex(
    {name: 1},
    {unique: true, sparse: true}
)
2.4:部分索引

部分索引即使用字段的一部分开创建索引,但必须要结合partialFilterExpression选项来实现,

db.animals.createIndex(
  {hobby: 1}, // 给 hobby 字段创建索引
  {partialFilterExpression: {
      hobby: {
        // 只为存在hobby字段的文档创建索引
        $exists: true,
        // 通过$substr操作符,截取前3个字节作为索引键
        $expr: {$eq: [{$substr:["$hobby", 0, 3] },"prefix"]}
      }
    }}
);

其实这就类似于MySQL中的前缀索引,不过MongoDB的中的部分索引功能更强大,还可以只为集合中的一部分文档创建索引

db.animal.createIndex(
   {age: -1},
   // 只为集合中年龄大于2岁的文档创建索引
   {partialFilterExpression: {
       age: {$gt: 2}
   }}
);
2.5:TTL索引

可以基于它实现过期自动删除的效果,主要依靠expireAfterSeconds选项来创建

只能在Date、ISODate类型的字段上建立TTL索引,在其他类型的字段上建立TTL索引,文档永远不会过期。

db.test_ttl.insertMany([
    // new Date()表示插入当前时间
    {_id:1, time:new Date()},
    {_id:2, time:new Date()},
    {_id:3, time:new Date()},
    {_id:4, time:new Date()},
    {_id:5, notes:"这条数据用于观察TTL删除特性"} // 10s之后,将只剩这条数据
]);

db.test_ttl.find();

db.test_ttl.createIndex(
    {time: 1},  // 依据时间字段创建索引
    {expireAfterSeconds: 10} // 给定的过期时间为10s
);
2.6:全文索引

MySQL中想实现模糊查询,一般会采用like关键字;而在MongoDB中想实现模糊查询,官方并没有提供相关方法与操作符,只能通过自己写正则的形式,实现模糊查找的功能

那有没有更好的方法呢?

答案是有,为相应字段创建全文索引即可。

在数据量不大不小(几百万左右)、查询又不是特别复杂的情况下,直接上ElasticSearch、Solr等中间件,显得有点大材小用

此时全文索引就是这类搜索引擎的平替。相较于MySQLMongoDB提供的全文索引,功能方面会更加强大。

db.animals.createIndex(
    {name: "text"},
    {name: "full_text_index"}
)

这里对name字段建立了一个全文索引,和创建普通索引的区别在于:在字段后面加了一个text

不过要注意,MongoDB全文索引停用词、词干和词器的规则,默认为英语,想要更改,这里涉及到创建索引时的两个可选项:

  • default_language:指定全文索引停用词、词干和词器的规则,默认为english
  • language_override:指定全文索引语言覆盖的范围,默认为language

不过注意,不管任何技术栈的全文索引,对中文的支持都不太友好,分词方面总会有点不完善

所以MongoDB全文索引直接不支持中文,当你试图通过default_language:"chinese"时,会直接给你返回报错

当然,正是由于MongoDB的全文索引不支持中文,因此就算你给一个字符串字段,建立了全文索引后,也无法实现全文搜索,如下:

db.animals.find(
    {$text: {$search: "熊"}}
);

⚠️ 在前面给出的集合数据中,name包含“熊”的数据有好几条,但这条语句执行之后的结果为null。想要解决这个问题,必须要手动安装第三方的中文分词插件,如mmseg、jieba等。当然,如果你字段中的值是英文,这自然是支持的,什么都不需要。

2.7:通配符索引

在前面提到过“内嵌文档”这个概念,这是指将另一个文档,以字段值的形式嵌入到一个文档中。

结合MongoDB可以动态插入各种字段的特性,每个内嵌文档的字段,也可以灵活变化,例如前面给出的数据:

{_id:4, name:"黑熊", age:3, food:{name:"黄金竹", grade:"S"}},
    
{_id:5, name:"白熊", age:4, food:{name:"翠绿竹", grade:"B"}},
......

这些数据中都内嵌了一个food文档,虽然现在插入的都是固定的name、grade字段,但我们可以随时插入新的字段,例如:

db.animals.insertOne(
    {
    	_id:99, 
        name:"星熊", 
        age: 1,
        food: {
            name:"星光竹", 
            grade:"S", 
            quality_inspector: ["竹大","竹二"]
        }
    }
);

这时新插入的文档,其food字段又多了一个quality_inspector质检员的属性

对于这种动态变化的字段,可不可以建立索引呢?MongoDB4.2中引入了“通配符索引”来支持对未知或任意字段的查询操作

创建的语法如下:

db.animals.createIndex({"food.$**": 1});

3:explain执行计划

通过该命令,能有效帮咱们分析语句的执行情况,和MySQL的explain作用一致

db.<collection>.find().explain(<verbose>);

explain方法同样有三个模式可选,这里简单列出来:

  • queryPlanner:返回执行计划的详细信息,包括查询计划、集合信息、查询条件、最佳计划、查询方式、服务信息等(默认模式);
  • exectionStats:列出最佳执行计划的执行情况和被拒绝的计划等信息(即语句最终执行的方案);
  • allPlansExecution:选择并执行最佳执行计划,同时输出其他所有执行计划的信息;

一般排查find()查询缓慢问题时,可以先指定第二个模式,查看最佳执行计划的信息;

如果怀疑MongoDB没选择好索引,则可以再指定第三个模式,查看其他执行计划

如果的确是因为走错了索引,这时你可以通过hint强制指定要使用的索引,如下:

db.collection_name.find(查询条件).hint(索引名);

执行explain之后,可以发现输出了很多信息,主要关注的就是这个stage这个值:是最重要的字段,相当于mysql explain中的type

带包这本次查询的类型,该字段可能出现的值以及含义如下:

含义
COLLSCAN扫描整个集合进行查询;
IXSCAN通过索引进行查询;
COUNT_SCAN使用索引在进行count操作;
COUNTSCAN没使用索引在进行count操作;
FETCH根据索引键去磁盘拿具体的数据
SORT执行了sort排序查询;
LIMIT使用了limit限制返回行数;
SKIP使用了skip跳过了某些数据;
IDHACK通过_id主键查询数据;
SHARD_MERGE从多个分片中查询、合并数据;
SHARDING_FILTER通过mongos对分片集群执行查询操作;
SUBPLA未使用索引的$or查询;
TEXT使用全文索引进行查询;
PROJECTION本次查询指定了返回的结果集字段(投影查询)

在这里插入图片描述

// 在age上创建一个索引
db.animals.createIndex({age: -1}, {name: "idx_age"})
// 假设执行的语句如下
db.animals.find({age: {$gt: 6}}).limit(1).skip(1).explain();

在这里插入图片描述

这里咱们只需要带SCAN后缀的,因为其他都属于命令执行的“阶段”,并不属于具体的类型

explain会将一条语句执行的每个阶段,都详细列出来,每个阶段都会有stage字段

我们要做的,就是确保每个阶段都能用上索引即可

如果某一阶段出现COLLSCAN,在数据量较大的情况下,都有可能导致查询缓慢。

其次,咱们需要关心keysExamined、docsExamined两个字段的值(exectionStats模式下才能看到)

  • 前者代表扫描的索引键数量,后者代表扫描的文档数量,前者越大,代表索引字段值的离散性太差
  • 后者的值越大,一般代表着没建立索引。

三:安全与权限管理

1:简单的用户创建和使用

通常为了保证数据安全性,Redis、MySQL、MQ、ES……,通常都会配置账号/密码,一来可以提高安全等级,二来还可以针对不同库、操作设置权限,极大程度上降低了数据的安全风险。

同样,在MongoDB中也支持创建账号、密码,以及分配权限,并且还支持角色的概念,可以先为角色分配权限,再为用户绑定角色,从而节省大量重复的权限分配工作。

同时,MongoDB中还内置了大量常用角色,方便于咱们快速分配权限,不过并没有默认的账号

所以想要启用MongoDB的访问控制,还需要先创建一个账号:

// 1:切换到admin
use admin;

// 2:创建一个用户,并且赋予root角色(root角色只能分配给admin库)
db.createUser(
	{"user": "cui", "pwd": "123456", "roles": ["root"]}
)
// 3:退出客户端
quit;

接着再关闭MongoDB服务,重新启动时开启访问控制,必须使用账号密码连接才允许操作:

[root@~]# /soft/mongodb/bin/mongod -shutdown -f /soft/mongodb/conf/standalone/mongodb.conf
[root@~]# /soft/mongodb/bin/mongod -auth -f /soft/mongodb/conf/standalone/mongodb.conf
[root@~]# /soft/mongodb/mongosh/bin/mongosh 192.168.229.135:27017

或者这里也可以直接修改配置文件:

[root@~]# vi /soft/mongodb/conf/standalone/mongodb.conf

# 在配置文件结尾加上这两行
security:
    authorization: enabled

然后通过不带-auth的命令启动,效果同样是相同的。

接着先切换到咱们前面创建的cui库,查询一下animals集合试试看:

use cui;
db.animals.find({_id:1});
MongoServerError: command find requires authentication

此时就会看到对应报错,提示目前未授权,所以无法执行命令,因此这里需要登录一下,不过登录必须要切换到admin库下才可以,否则会提示认证失败:

db.auth("cui", "123456");
{ ok: 1 }

use cui;
db.animals.find({_id:1});
[ { _id: 1, name: '肥肥', age: 3, hobby: '竹子', color: '黑白色' } ]

认证成功后,再次切回cui库查询,此时会发现数据依旧可以查询出来。

当然,如果不想每次连接时都切换到admin库下登录,然后再切换回来,此时可以在cui库下再创建一个用户,如下:

// 先切换到cui库
use cui;

// 再在cui库下创建一个cui用户,并分配dbOwner角色
db.createUser(
    {
        "user":"cui", 
     	"pwd":"123456", 
        // 这个dbOwner是啥,会在下面的内置角色中说明
     	"roles":[{"role":"dbOwner", "db":"cui"}]
    }
);

// 退出连接
quit;

然后可以再次连接MongoDB服务,这时直接切换到cui库下登录后,也照样可以读写数据

2:Mongo中的内置角色

可以通过下述命令来查询MongoDB所有内置角色:

use admin;

db.runCommand({rolesInfo: 1, showBuiltinRoles: true});
角色含义
root超级管理员权限,可以执行任何操作;
read只读用户,不允许对数据库执行写入操作;
readWrite读写用户,允许对数据执行读写操作;
dbAdmin数据库管理员(如创建和删除数据库),不允许读写数据;
userAdmin用户管理员(如创建和删除用户),不允许读写数据;
dbOwner同时拥有dbAdmin、userAdmin两个角色的权限,且允许读写数据;
backup具有备份和恢复权限,不允许读写数据;
restore只具有数据恢复权限,不允许读写数据;
clusterAdmin集群超级管理员,可以执行集群中任意操作,允许读写数据;
clusterManager集群管理员,只可以管理集群节点、配置等;
clusterMonitor集群监视员,允许监控集群的状态和性能,不允许读写数据;

这些内置角色,可以在创建用户的时候分配,一个用户同时可以绑定多个角色。但如果你想要的权限,内置角色并不提供,也可以自定义角色

3:Mongo自定义角色

自定义角色的语法

use cui;

db.createRole(
    {
    // 自定义角色的名称
    role:"xxxRole",
    // 自定义角色拥有的权限集
    privileges: [
        {
           // 自定义角色可操作的资源 
           resource:
            {
                // 当前角色可操作cui库
                db:"cui",
                // 当前角色具体可操作的集合(多个传数组,所有写"")
                collection:""
            },
            // 当前角色拥有的权限
            actions: ["find", "update", "insert", "remove"]
        }
        ],
        // 当前角色是否继承其他角色,如果指定了其他角色,当前角色自动继承父亲的所有权限
        roles: []
    }
);

通过该方式,诸位可以灵活的创建出各种适用于业务的角色,最后再附上一些相关命令:

// 给指定角色增加权限 ---------> grantPrivilegesToRole
db.grantPrivilegesToRole(
    "角色名称",
    [{
        resource: {
          db: "库名",
          collection: ""
        }, 
        actions: ["权限1","……"]
    }]
);

// 回收指定角色的权限 ----------> revokePrivilegesFromRole
db.revokePrivilegesFromRole(
    "角色名称",
    [{
        resource: {
          db: "库名",
          collection: ""
        }, 
        actions: ["权限1","……"]
    }]
);

// 删除角色(要先进入角色所在的库)
use cui;
db.dropRole("角色名称");


// 查看当前库的所有角色
show roles;

// 查看当前库中所有用户
show users;

http://www.niftyadmin.cn/n/5864543.html

相关文章

【每日八股】计算机网络篇(一):概述

OSI 的 7 层网络模型&#xff1f; OSI&#xff08;Open Systems Interconnection&#xff0c;开放互联系统&#xff09;是由国际标准化组织&#xff08;ISO&#xff09;提出的一种网络通信模型。 自上而下&#xff0c;OSI 可以被分为七层&#xff0c;分别是&#xff1a;应用层…

如何通过 Docker 在没有域名的情况下快速上线客服系统

很多小伙伴想上线完全私有化的客服系统&#xff0c;却因域名注册备案比较麻烦而望而却步。 其实不需要域名&#xff0c;只要租用云服务器&#xff0c;获得公网 IP 地址之后&#xff0c;就可以快速上线客服系统。 首先租用一台公网服务器&#xff0c;选择 Ubuntu 24.10 安装 …

全星研发管理APQP软件系统:让研发项目管理化繁为简,助力企业高效研发

全星研发管理APQP软件系统&#xff1a;让研发项目管理化繁为简&#xff0c;助力企业高效研发 在竞争激烈的市场环境下&#xff0c;企业研发项目面临着时间紧、任务重、协同难等挑战。传统的项目管理方式效率低下&#xff0c;难以满足快速迭代的需求。 全星研发管理APQP软件系…

无人机仿真、感知、规划

文章目录 1.仿真环境1.1 博客教学1.2 教学视频1基础无人机仿真教学视频介绍2 XTDrone无人机仿真与控制技术全面教程3 ROS机器人集群仿真与实践教程 1.3 开源项目及插件1 ROS2-Gazebo Drone Simulation Plugin2 RotorS_UAV_Gazebo_Simulator3 自主无人机与Aruco导航教程4 基于 A…

PyEcharts 数据可视化:从入门到实战

一、PyEcharts 简介 PyEcharts 是基于百度开源可视化库 ECharts 的 Python 数据可视化工具&#xff0c;支持生成交互式的 HTML 格式图表。相较于 Matplotlib 等静态图表库&#xff0c;PyEcharts 具有以下优势&#xff1a; 丰富的图表类型&#xff08;30&#xff09;动态交互功…

安全生产月安全知识竞赛主持稿串词

女:尊敬的各位领导、各位来宾 男:各位参赛选手、观众朋友们 合:大家好&#xff5e; 女:安全是天&#xff0c;有了这一份天&#xff0c;我们的员工就会多一份幸福&#xff0c; 我们的企业就会多一丝光彩。 男:安全是地&#xff0c;有了这一片地&#xff0c;我们的员工就多了一…

HTML列表,表格和表单

列表 在 HTML 中&#xff0c;列表&#xff08;List&#xff09;是常见的一种布局方式。列表分为两种类型&#xff1a;有序列表&#xff08;Ordered List&#xff09;和无序列表&#xff08;Unordered List&#xff09;。 无序列表 无序列表&#xff08;Unordered List&#…

2025最新Python机器视觉实战:基于OpenCV与YOLOv8的实时目标检测与跟踪(附完整代码)

2025最新Python机器视觉实战:基于OpenCV与YOLOv8的实时目标检测与跟踪(附完整代码) 摘要:本文基于OpenCV与YOLOv8模型,实现实时目标检测与跟踪功能,支持多类别目标识别与运动轨迹绘制。代码兼容Python 3.7+,步骤清晰且经过稳定性测试,适合中高级开发者参考。所有依赖库…