MySQL架构设计

news2024/12/26 10:37:53

MySQL架构设计

查询语句:

select * from users;

返回结果为:

问题:

一条SQL查询语句是如何执行的?

Server层

MySQL架构可以分为server层Engine层两部分:

连接器(connector)

1. 连接管理

连接器的首要任务是管理客户端的连接。客户端与MySQL服务器之间的通信是通过连接来实现的。每次客户端向MySQL发起请求时,首先要建立一个连接。这个连接类似于一条通道,客户端通过这条通道向服务器发送SQL查询,服务器则通过这条通道返回结果。

1.1 连接类型

MySQL支持两种主要的连接类型:

  • 短连接(Short-lived Connection):这种连接在执行完一次请求后立即关闭。每次查询都需要重新建立连接,适合一些轻量级的操作或分布式系统中不频繁的交互。
  • 长连接(Persistent Connection):连接在建立后会保持一段时间,不会在每次请求后立即关闭。长连接在高频率请求的场景中效率更高,因为省去了频繁建立和关闭连接的开销。
1.2 连接数限制

MySQL服务器通常会限制同时打开的连接数。如果连接数超出配置的上限,新的连接请求会被拒绝。MySQL通过参数max_connections来设置最大连接数,管理员可以根据系统资源和应用需求调整该参数。

2. 权限验证

在客户端成功建立连接后,连接器的下一个任务是对用户进行权限验证。

2.1 用户身份验证
  • 当用户尝试连接MySQL时,必须提供用户名和密码。连接器会验证这些凭据是否正确。MySQL通过存储在mysql.user表中的用户信息进行比对。
  • 如果用户名或密码错误,连接器会拒绝连接请求,并向客户端返回错误信息。
2.2 权限检查
  • 除了身份验证,连接器还会检查用户是否有权限执行所请求的操作。这包括对数据库、表、视图、存储过程等对象的访问权限。权限验证在连接建立时进行,也可能在后续操作中再次检查(例如执行一个需要更高权限的操作时)。
  • MySQL的权限系统是非常细粒度的,可以为不同用户、不同的数据库对象授予不同级别的访问权限(如SELECT、INSERT、UPDATE等)。
3. 连接维护

在连接建立后,连接器负责维持该连接,直到它被关闭。

3.1 连接状态
  • 在连接的生命周期内,连接器会管理连接的状态。MySQL提供了多种状态标志,用于描述连接的当前状态(例如Sleep表示空闲状态,Query表示正在执行查询)。
  • 连接可以处于活动状态或空闲状态。对于长连接,如果在一段时间内没有活动请求,连接可能会进入空闲状态。管理员可以通过配置参数(如wait_timeout)来控制空闲连接的超时时间。
3.2 连接关闭
  • 连接可以由客户端主动关闭,或者在客户端未明确关闭连接的情况下,由服务器根据配置的超时时间被动关闭。关闭连接时,连接器会释放与该连接相关的所有资源。
4. 连接池(Connection Pool)

为了提高性能和资源利用率,许多应用程序会使用连接池技术。

4.1 工作原理
  • 连接池是一个缓存池,里面保存了多个连接实例。当一个请求到来时,应用程序不必重新建立一个新的连接,而是从连接池中获取一个空闲连接来使用。操作完成后,连接被返回到池中以供后续使用。
  • 连接池减少了频繁建立和关闭连接的开销,提高了系统的响应速度,特别是在高并发环境中。
4.2 MySQL与连接池
  • MySQL自身并没有内置的连接池管理功能,但大多数应用框架或中间件(如Java的JDBC)都提供了连接池的支持。通过合理配置连接池,可以显著提升应用程序的性能。
5. 潜在问题

尽管连接器功能强大,但也可能遇到一些常见问题:

  • 连接过多:如果连接数超过了MySQL服务器的处理能力,可能导致系统资源耗尽,性能下降。
  • 长连接资源占用:长连接虽然减少了连接开销,但如果使用不当,可能导致资源长期占用,例如内存泄漏等问题。
  • 连接断开:网络不稳定可能导致连接中断,客户端需要具备相应的错误处理机制来应对这种情况。

思考:

(1)一个客户端只会和MySQL服务器建立一个连接吗?

(2)只能有一个客户端和MySQL服务器建立连接吗?

答:

(1)一个客户端可以和MySQL服务器进行多个连接的建立。

(2)MySQL服务器支持并发处理。

连接器负责和客户端建立连接、获取权限、维持和管理连接。

连接命令:

mysql -h$ip -p$port -u$user -p

在完成TCP握手之后,连接器就会基于我们给的账号密码进行身份验证,去mysql.user表中进行比对。

  • 验证不通过:"Access denied for user"错误
  • 验证通过:连接器会到权限表里面查出拥有的权限,之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限
show processlist   -- 查看连接状态

查询缓存

MySQL8 往后的版本都舍弃了这个功能。

通过了连接管理后,MySQL已经拿到了SQL语句,然后根据SQL语句去查询缓存。

缓存命中:当一个SQL查询语句执行时,MySQL首先会检查查询缓存中是否有该语句的结果。如果有,并且缓存内容依然有效,MySQL会直接返回缓存中的结果,而无需重新执行查询。这种情况下,查询速度会非常快,因为省去了执行SQL语句所需的所有步骤(如解析、优化和执行)。

缓存存储:如果查询结果没有在缓存中命中,MySQL会正常执行SQL语句,并将执行结果存入查询缓存中,以便相同的查询在下次执行时可以直接使用缓存。

缓存失效:查询缓存并不是永远有效的。任何涉及到查询相关表的变更操作(如INSERTUPDATEDELETETRUNCATEALTER TABLE等)都会导致缓存中与该表相关的所有查询失效。这是因为一旦表的数据发生了变化,缓存中的查询结果可能不再准确,需要重新计算。

缓存的缺点

缓存失效频率高:查询缓存的一个主要问题是其失效机制。如果表中的数据被频繁更新,相关的缓存会不断失效,导致缓存的命中率很低。这不仅没有提高性能,反而可能会增加系统的负担,因为缓存管理本身也会消耗资源。

全局锁问题:在MySQL 5.7及之前的版本中,查询缓存使用了全局锁,这意味着任何一个更新操作都会导致缓存锁定,影响所有查询的并发执行。这在高并发环境下会导致严重的性能瓶颈。

内存碎片:查询缓存中的数据会导致内存碎片化。当查询缓存中的可用空间不足以存储新的查询结果时,MySQL需要执行内存整理操作,这个过程也会对性能产生负面影响。

举个栗子🌰

数据库更新:现在假设一个后台管理系统的用户(如管理员)在某个时间点通过一个SQL操作更新了“热销产品”列表中的一个商品信息(例如更改了商品价格或库存)。

全局锁生效:在MySQL 5.7及之前的版本中,当表中的任何数据发生变化(如UPDATEINSERTDELETE等操作),查询缓存中与该表相关的所有缓存都会失效。为了保证数据一致性,MySQL需要在更新操作执行时锁住整个查询缓存,确保在缓存失效和清理的过程中不会有新的查询请求试图访问缓存。

阻塞并发查询:如果此时有多个用户(如用户B、C、D等)正在同时访问“热销产品”页面,他们的查询会发现缓存已失效,需要重新执行查询。然而,由于全局锁的存在,这些查询请求必须等待缓存的清理和更新操作完成后才能继续。这种锁定会阻塞其他所有的查询操作,导致用户体验变差,页面响应时间变长。

分析器

缓存如果没有命中,就要开始真正执行语句了。

首先,MySQL需要知道要做什么,因此需要对SQL语句做解析。

词法分析(Lexical Analysis)

词法分析是分析器的第一步,也被称为扫描阶段。在这个阶段,SQL查询被拆解成一个个最小的语法单元,称为“词法单元”(Tokens)。词法单元可以是SQL关键字、标识符(如表名、列名)、运算符(如+-)、文字常量(如字符串、数字)、括号等。

SELECT name FROM users WHERE id = 10;

词法分析器将其拆解为以下词法单元:

  • SELECT
  • name
  • FROM
  • users
  • WHERE
  • id
  • =
  • 10
  • ;

语法分析(Syntax Analysis)

语法规则的定义

语法分析器使用上下文无关文法(Context-Free Grammar, CFG)来描述SQL语言的语法规则。这些规则定义了SQL语句如何由一系列词法单元构成,并规定了各个词法单元之间的合法组合方式。

一个简单的SQL SELECT语句的语法规则可能如下:

<query> ::= SELECT <select_list> FROM <table_list> [WHERE <condition>];
<select_list> ::= <column> [, <select_list>]
<table_list> ::= <table> [, <table_list>]
<condition> ::= <expression> [AND <condition>] | <expression> [OR <condition>]
<expression> ::= <column> <operator> <value>

这些规则定义了一个SQL查询的结构:SELECT关键字必须跟随一个列列表,然后是FROM关键字以及一个表列表,可选的WHERE子句用来定义查询条件。

构建解析树

如果说有某个单词拼错了,或者说SQL语句缺少FROM子句,都会引发语法错误

SELECT name WHERE id = 10;

优化器(optimizer)

通过了分析器,说明 SQL 字符串符合语法规范,现在 MySQL 服务器要执行 SQL 语句了。

MySQL 服务器要怎么执行呢?

那么就需要产出执行计划,交给 MySQL 服务器执行,所以来到了优化器阶段。

优化器不仅仅只是生成执行计划这么简单,这个过程它会帮你优化 SQL 语句

1. 生成执行计划

优化器会生成一个或多个执行计划,这些计划描述了如何访问数据以及如何执行查询的各个部分。执行计划包括以下内容:

  • 访问路径选择:决定使用哪种方法来访问表中的数据。例如,优化器会选择是使用全表扫描、索引扫描、索引覆盖扫描,还是通过键查找来访问数据。
  • 连接顺序选择:当查询涉及多个表的连接(JOIN)时,优化器会决定连接这些表的顺序以及连接方法(如嵌套循环连接、合并连接、哈希连接)。
  • 子查询优化:对于包含子查询的SQL语句,优化器会决定是否将子查询重写为连接(JOIN),或者是否可以将其改写为更简单的查询形式。
  • 选择合适的算法:例如,决定排序操作是通过内存中的排序算法还是通过磁盘上的临时表来进行。
2. 成本评估

优化器基于“成本模型”来评估每个执行计划的成本。成本通常包括以下几个方面:

  • I/O成本:读取数据所需的磁盘I/O操作的数量。
  • CPU成本:处理数据所需的CPU时间,包括计算、比较、数据转换等操作。
  • 内存使用:操作所需的内存量。

优化器会尝试选择总体成本最低的执行计划。这是因为在数据库查询中,I/O操作和CPU消耗通常是性能瓶颈,优化器旨在通过减少这些消耗来提高查询性能。

3. 查询重写

优化器可能会对查询进行重写,以生成一个等价但更高效的查询。常见的重写操作包括:

  • 条件下推:将过滤条件尽可能早地应用到查询的各个阶段,以减少不必要的数据处理。
  • 表达式简化:例如,将WHERE 1=1简化为不做任何操作,或者将常量表达式提前计算。
  • 视图合并和子查询展开:将视图查询合并到主查询中,或者将子查询转换为连接(JOIN)操作。
4. 索引选择

优化器负责选择最合适的索引来加速查询。如果查询涉及多个索引,优化器会决定是否使用单一索引、多索引组合,或者完全不用索引。

5. 多表连接优化

在多表连接查询中,表的连接顺序对性能有很大影响。优化器会分析表的大小、索引的可用性、连接条件等,决定最优的连接顺序和连接方法。

例如:执行下面这样的语句

mysql> select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;
  • 既可以先从表 t1 里面取出 c=10 的记录的 ID 值,再根据 ID 值关联到表 t2,再判断 t2 里面 d 的值是否等于 20
  • 也可以先从表 t2 里面取出 d=20 的记录的 ID 值,再根据 ID 值关联到 t1,再判断 t1 里面 c 的值是否等于 10。

这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。

截止到现在,还没有真正去读写真实的表,仅仅只是产出了一个执行计划。

执行器(Actuator)

MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执

行语句。

开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示

mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user 'Hayaizo'@'localhost' for table
'T'

如果有权限,就会根据表的 Engine 选择来调用对应的引擎接口。

例如:

user_info 表的存储引擎是InnoDB

select * from user_info where name = "Hayaizo";
  • 如果 name 列没有声明任何索引,执行步骤如下:
  1. 调用 innoDB 引擎接口获取表的第一行,判断 name 是否等于 Hayaizo 。如果不是,跳过。如果是,

将结果保存。

  1. 调用 innoDB 引擎接口获取表的下一行,重复相同逻辑,一直到表的最后一行。
  2. 将所有满足条件的结果集返回给客户端。

也就是暴力遍历,O(N)的时间复杂度。

  • 如果 name 列有索引,执行步骤如下:
  1. 调用innoDB引擎接口获取索引树,基于索引树快速查找到name等于Hayaizo的所有主键id
  2. 将所有满足条件的主键id去主键索引树中查询信息,这个操作也叫做回表。
  3. 将满足条件结果集全都返回给客户端

Engine层

什么是存储引擎?

引擎(Engine),我们都知道是机器发动机的核心所在,数据库存储引擎便是数据库的底层软件组织。

数据库使用数据存储引擎实现存储、处理和保护数据的核心服务

不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎,还可以获得

特定的功能。现在许多不同的数据库管理系统都支持多种不同的数据引擎。MySql的核心就是插件式存

储引擎。

MySQL支持哪些存储引擎?

SHOW ENGINES

可以发现,MySQL目前支持多种数据库存储引擎,默认引擎为InnoDB,且是唯一支持事务的存储引擎。

常见的存储引擎对比

InnoDB引擎

概述:InnoDB是事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键,InnoDB是默认的MySQL引擎。

优点

  • 支持外键。
  • 支持事务,支持4个事务隔离级别,保证数据的完整性。
  • 支持行级锁。
  • InnoDB是为处理巨量数据量的最大性能设计。
    • 在以前的版本中,字典数据以元数据文件、非事务表等来存储。现在这些元数据文件被删除了。比如:.frm, .par 等都在MySQL8.0中不存在了,只有一个.ibd的文件,用于存放索引和数据(在InnoDB中索引即数据)。

缺点

  • 对比MyISAM的存储引擎,InnoDB写的处理效率差一些,并且会占用更多的磁盘空间以保存数据。
  • MyISAM只缓存索引,不缓存真实数据;InnoDB不仅缓存索引还缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响。

MyISAM存储引擎

概述:MyISAM基于ISAM存储引擎,并对其进行扩展。它是在Web、数据仓储和其他应用环境下最常使用的存储引擎之一。MyISAM拥有较高的插入、查询速度,但不支持事务。

主要特性

  • MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM不支持事务、行级锁、外键,有一个毫无疑问的缺陷就是崩溃后无法安全恢复。
  • 优势是访问的速度快,对事务完整性没有要求或者以SELECT、INSERT为主的应用。
  • 表级锁定形式,数据在更新时锁定整个表。
  • 数据文件的结构为:
    • 表名.frm 存储表结构
    • 表名.MYD 存储数据(MYData)
    • 表名.MYI 存储索引(MYIndex)

应用场景:只读应用或者以读为主的业务


MEMORY存储引擎

概述:MEMORY存储引擎将表中的数据存储到内存中,为查询和引用其他表数据提供快速访问。

主要特性

  • 同时支持哈希(HASH)索引和B+树索引。
  • Memory表至少比MyISAM表要快一个数量级。
  • Memory表的大小是受限制的,表的大小主要取决于两个参数,分别是max_rows和max_heap_table_size。其中,max_rows可以在创建表时指定;max_heap_table_size的大小默认为16MB,可以按照需要进行扩大。
  • 数据文件与索引文件分开存储,表的结构存储在.frm文件中,数据放在内存中
  • 缺点:其数据易丢失,生命周期短。

从您提供的图片中提取的表格如下:

特点MyISAMBDBMemoryInnoDBArchive
存储限制没有没有64TB没有
事务安全不支持支持不支持支持不支持
锁机制表锁页锁表锁行锁行锁
B树索引支持支持支持支持支持
哈希索引不支持支持支持支持不支持
全文索引支持不支持不支持支持不支持
集群索引不支持不支持不支持支持不支持
数据缓存不支持支持支持支持不支持
索引缓存支持支持支持支持不支持
数据可压缩支持支持不支持支持支持
空间使用N/A非常低
内存使用中等
批量插入的速度非常高
支持外键不支持支持不支持支持不支持

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

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

相关文章

关于python中的get,set方法

一般在python面向对象中&#xff0c;每个类内都会有其所对应的属性。 而在定义属性值的时候&#xff0c;可能还得会对这些属性值进行修改和获取。为了确保代码的安全性&#xff0c;封装性和可操作性&#xff0c;我们可以使用get&#xff0c;set方式去操控。 get方法用于获取属…

贝莱德与摩根大通的最新季度持仓分析

近期&#xff0c;华尔街的两大投资巨头贝莱德和摩根大通公布了其2024年第二季度的13F报告&#xff0c;揭示了他们在投资组合上的最新动向。通过分析这些持仓数据&#xff0c;我们可以更清楚地了解这些顶级投资机构的投资策略和市场偏好。 贝莱德的科技巨头与能源投资 根据贝莱…

Trilium Notes:你的个人知识库!【送源码】

简介 Trilium Notes是一款功能全面的层次化笔记应用&#xff0c;通过树形结构构建个人知识库&#xff0c;支持Markdown编辑、网页内容剪切、笔记搜索与映射&#xff0c;并特别提供了基于画布的自由涂画功能&#xff0c;极大地提升了笔记的灵活性和创造性&#xff0c;是知识管理…

移动UI:把握好这9点,轻松设计积分兑换页面。

设计移动UI的积分兑换页面需要考虑用户体验和交互设计&#xff0c;以下是一些设计建议&#xff1a; 1. 清晰的积分信息展示&#xff1a; 在页面顶部或者中间位置展示用户当前的积分数量&#xff0c;让用户清晰地了解自己的积分情况。 2. 商品列表展示&#xff1a; 以列表或者…

[数据集][目标检测]违规撑伞检测数据集VOC+YOLO格式341张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;341 标注数量(xml文件个数)&#xff1a;341 标注数量(txt文件个数)&#xff1a;341 标注类别…

浅看MySQL数据库

有这么一句话&#xff1a;“一个不会数据库的程序员不是合格的程序员”。有点夸张&#xff0c;但是确是如此。透彻学习数据库是要学习好多知识&#xff0c;需要学的东西也是偏难的。我们今天来看数据库MySQL的一些简单基础东西&#xff0c;跟着小编一起来看一下吧。 什么是数据…

什么是树的先/中/后序遍历

前言 很久没有写c教程&#xff08;有几个月&#xff09;了&#xff0c;今天来讲讲树 为什么之前不讲树因为我不会但我现在能写出一些树的选择题了 首先我们画一棵树&#xff1a; 有些潦草 遍历是什么 我们要研究先/中/后序遍历&#xff0c;我们应该先知道什么叫遍历 遍历…

论文精要:《对静态分析缺陷报告进行聚类,以降低维护成本》

1. 前言 周末找到一篇《对静态分析缺陷报告进行聚类&#xff0c;以降低维护成本》&#xff0c;读了之后受到不少启发&#xff0c;特此将笔记整理出来。 论文出处&#xff1a; Published in: 2013 20th Working Conference on Reverse Engineering (WCRE)Date of Conference:…

疫情期间高校人员管理

TOC springboot322疫情期间高校人员管理 绪论 1.1 选题背景 当人们发现随着生产规模的不断扩大&#xff0c;人为计算方面才是一个巨大的短板&#xff0c;所以发明了各种计算设备&#xff0c;从结绳记事&#xff0c;到算筹&#xff0c;以及算盘&#xff0c;到如今的计算机&a…

C语言家教记录(六)

导语 本次授课的内容如下&#xff1a;指针&#xff0c;指针和数组 辅助教材为 《C语言程序设计现代方法&#xff08;第2版&#xff09;》 指针 指针变量 计算机按字节划分地址&#xff0c;每个地址访问一个字节 指针变量指向变量的地址&#xff0c;指的是变量第一个字节的…

MySQL InnoDB中一个update语句从执行到提交的全过程(1)

目录 一、开启事务 二、sql解析、查询计划生成 三、查询要修改的数据 1、读buffer pool的过程 buffer pool的结构组成 1&#xff09;Free List (空闲链表) 2&#xff09;LRU List (最近最少使用链表) 3&#xff09;Flush List (刷新链表) 三条链表之间的关系 2、怎么…

[Linux][OS][信号的保存和处理]

目录 信号的处理 1. 在内核中的表示 2. 相关概念 3. 信号集操作函数 4.sigprocmask 5.sigpending 信号的捕捉 重谈地址空间 信号的处理 1. 在内核中的表示 普通信号&#xff0c;多次产生只会记录一次 信号范围 [1,31]&#xff0c;每一种信号都要有自己的一种处理方式…

Java Spring|day3.SpringBoot

Spring Boot 定义 从本质上来说&#xff0c;Spring Boot就是Spring&#xff0c;它做了那些没有它你自己也会去做的Spring Bean配置。Spring Boot使用“习惯优于配置”的理念让你的项目快速地运行起来&#xff0c;使用Spring Boot很容易创建一个能独立运行、准生产级别、基于S…

SpringBoot + Hadoop + HDFS + Vue 实现一个简单的文件管理系统

1. 安装前的准备工作 1.1 更新系统并安装必要的工具 在终端中运行以下命令&#xff1a; sudo apt-get update sudo apt-get install -y ssh rsync curl1.2 安装 Java 如果系统中没有安装 Java&#xff0c;可以通过以下命令安装 OpenJDK&#xff1a; sudo apt-get install …

软件安全测试的必要性,第三方软件测试机构进行安全测试好处简析

在当前信息技术迅猛发展的时代&#xff0c;软件的安全性显得尤为重要。随着越来越多的企业依赖软件进行日常运营&#xff0c;软件漏洞和安全隐患所带来的风险也逐渐上升。因此&#xff0c;软件安全测试不再是可有可无的选择&#xff0c;而是每个企业必须考虑的关键环节。 一、…

Node之npm常用命令与package.json文件

新书速览|Vue.jsNode.js全栈开发实战-CSDN博客 《Vue.jsNode.js全栈开发实战&#xff08;第2版&#xff09;&#xff08;Web前端技术丛书&#xff09;》(王金柱)【摘要 书评 试读】- 京东图书 (jd.com) npm常用命令 npm默认与Node.js一起安装&#xff0c;可以在命令行中输入…

达梦数据库系列—49.审计功能

目录 1、打开审计 2、审计级别 系统级审计 语句级审计 对象级审计 3、审计文件管理 删除审计文件 查看审计信息 4、审计分析 审计分析工具Analyzer 审计分析工具dmaudtool 1、打开审计 0&#xff1a;关闭审计1&#xff1a;打开普通审计2&#xff1a;打开普通审计和…

qt 5.15.2 使用pdf

1. 安装Qt 5.15.2 选择对应编译器和源码&#xff1a; 安装后已有pdf对应的库和源码&#xff1a; 将QtPdf和QtPdfWidgets目录拷贝到msvc2019_64/include(根据需要用到的编译器而定) 将C:\Qt\5.15.2\Src\qtwebengine\src\pdf\api所有文件拷贝到C:\Qt\5.15.2\msvc2019_64\include\…

低功耗蓝牙协议栈如何学习?

目录 1. 基础知识 1.1 了解BLE基本概念 1.2 阅读BLE规范 2. 实践和开发工具 2.1 使用开发板 2.2 安装开发环境 3. 学习资源 3.1 官方文档和示例 3.2 在线课程和教程 4. 实践项目 4.1 简单项目 4.2 复杂项目 5. 深入理解协议栈 5.1 分析协议栈 5.2 调试和分析 6…

v-charts的下载与引用

近期&#xff0c;因项目需要&#xff0c;需用v-charts做一些登录后首页的展示图&#xff0c;按之前的逻辑echarts官网都有API直接拿来用就行了&#xff0c;但是我的项目框架是vuespringboot&#xff0c;我的vue版本是2.6.1&#xff0c;这个v-charts文档在网上搜了搜&#xff0c…