【一文详解】知识分享:(MySQL关系型数据库知识进阶)

这篇具有很好参考价值的文章主要介绍了【一文详解】知识分享:(MySQL关系型数据库知识进阶)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

进阶

体系结构

Mysql体系结构:

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

  • 连接层

    位于最上层,是一些客户端和连接服务,主要完成一些类似于连接处理,授权认证及相关的安全方案。

    服务器也会为安全接入的每个客户端验证它所具有的操作权限。

  • 服务层

    第二层,主要完成大多数的核心服务功能,如sql接口,并完成缓存的查询,sql的分析和优化,部分内置函数的执行。

    所有跨存储引擎的功能也在这一层实现,如:过程,函数等。

  • 引擎层

    第三层,存储引擎真正的负责了mysql中数据的存储和提取,服务器通过API和存储引擎进行通信。

    不同的存储引擎具有不同的功能,可以根据自己的需要来选取合适的存储引擎。

  • 存储层

    最底层,主要是将数据存储在文件系统之上,并完成与存储引擎的交互。

存储引擎

简介

存储引擎就是存储数据,建立索引,更新/查询数据等技术的实现方式。

存储引擎是基于表而不是基于库的,所以存储引擎也可以被称为表引擎

在 MySQL 5.5 之后,默认的存储引擎是InnoDB。

相关操作:

-- 查询建表语句(以account表为例)
show create table account;
-- 建表时指定存储引擎
CREATE TABLE 表名(
    ...
) ENGINE=INNODB;
-- 查看当前数据库支持的存储引擎
show engines;

InnoDB

InnoDB 是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB 是默认的 MySQL 引擎

特点

  • DML 操作遵循 ACID 模型,支持事务
  • 行级锁,提高并发访问性能
  • 支持外键约束,保证数据的完整性和正确性

文件

  • xxx.ibd: xxx代表表名,使用InnoDB 作为引擎的数据表都会对应这样一个表空间文件,其存储对应数据表的表结构(frm、sdi)、数据和索引。

参数:

innodb_file_per_table,决定多张表共享一个表空间还是每张表对应一个表空间


知识点:

查看 Mysql 变量:
show variables like 'innodb_file_per_table';

从ibd文件提取表结构数据:
(在cmd运行)

ibd2sdi xxx.ibd

InnoDB 逻辑存储结构:

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

MyISAM

MyISAM 是 MySQL 早期的默认存储引擎。

特点:

  • 不支持事务不支持外键
  • 支持表锁,不支持行锁
  • 访问速度快

文件:

  • xxx.sdi: 存储表结构信息
  • xxx.MYD: 存储数据
  • xxx.MYI: 存储索引

Memory

Memory 引擎的表数据是存储在内存中的,受硬件问题、断电问题的影响,只能将这些表作为临时表或缓存使用。

特点:

  • 存放在内存中,速度快
  • hash索引(默认)

文件:

  • xxx.sdi: 存储表结构信息

存储引擎对比

特点 InnoDB MyISAM Memory
存储限制 64TB
事务安全 支持 - -
锁机制 行锁 表锁 表锁
B+tree索引 支持 支持 支持
Hash索引 - - 支持
全文索引 支持(5.6版本之后) 支持 -
空间使用 N/A
内存使用 中等
批量插入速度
支持外键 支持 - -

存储引擎的选择

在选择存储引擎时,应该根据系统的特点选择合适的存储引擎。

对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。

  • InnoDB: 如果应用对事物的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,则 InnoDB 是比较合适的选择
  • MyISAM: 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不高,那这个存储引擎是非常合适的。
  • Memory: 将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。Memory 的缺陷是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性

如: 电商中的足迹和评论适合使用 MyISAM 引擎,缓存适合使用 Memory 引擎。

性能分析

查看执行频次

查看当前数据库的 INSERT, UPDATE, DELETE, SELECT 等语句的访问频次:
SHOW GLOBAL STATUS LIKE 'Com_______';

或者

SHOW SESSION STATUS LIKE 'Com_______';

例子:

show global status like 'Com_______'

慢查询日志

慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。

日志文件位置:/var/lib/mysql/localhost-slow.log

MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:

(更改后记得重启MySQL服务)

# 开启慢查询日志开关
slow_query_log=1
# 设置慢查询日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2

查看慢查询日志开关状态:
show variables like 'slow_query_log';

profile

show profile 能在做SQL优化时帮我们了解时间都耗费在哪里

通过 have_profiling 参数,能看到当前 MySQL 是否支持 profile 操作:
SELECT @@have_profiling;
profiling 默认关闭,可以通过set语句在session/global级别开启 profiling:
SET profiling = 1;
查看所有语句的耗时:
show profiles;
查看指定query_id的SQL语句各个阶段的耗时:
show profile for query query_id;
查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query query_id;

explain

EXPLAIN 或者 DESC 命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序
语法:

-- 直接在select语句之前加上关键字 explain / desc
EXPLAIN SELECT 字段列表 FROM 表名 HWERE 条件;

例子:

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

EXPLAIN 结果的各字段含义:

  • id:select 查询的序列号,这是查询中每个操作的唯一标识符

    表示查询中执行 select 子句或者操作表的顺序(id相同,执行顺序从上到下;id不同,值越大越先执行)

  • select_type:表示 SELECT 的类型,常见取值有 SIMPLE(简单表,即不适用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询)等

  • table:显示操作涉及的表的名称

  • partitions:表示查询操作所涉及的分区

  • type:表示连接类型,指示数据库引擎在表之间进行连接的方式.

    性能由好到差的连接类型为 NULL、system、const(表中只有一行匹配,主键或唯一索引的等值查询)、eq_ref(使用唯一索引进行查找)、ref(使用非唯一索引进行查找)、range(使用索引进行范围扫描)、index(全索引扫描[索引树])、all(全表扫描[磁盘])

    【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

  • possible_keys:可能应用在这张表上的索引,一个或多个,如果为 NULL,则没有使用索引

  • Key:实际使用的索引,如果为 NULL,则没有使用索引

  • Key_len:表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好

  • rows:MySQL估计要检索的行数,在InnoDB引擎的表中,是一个估计值,可能并不总是准确的

  • filtered:表示返回结果的行数占需读取行数的百分比,filtered的值越大越好

  • Extra: 表示额外的信息。

    一些常见的值有:

    • Using where: 表示使用了WHERE子句进行过滤。
    • Using index: 表示使用了索引覆盖查询,数据直接从索引中获取而无需访问表。
    • Using temporary: 表示使用了临时表来存储中间结果。
    • Using filesort: 表示使用了文件排序。

索引

索引是帮助 MySQL 高效获取数据数据结构(有序)

在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构之上实现高级查询数据的算法,这种数据结构就是索引。

个人理解: 数据表中的某个/某些字段会被选出来作为B+树节点的索引值,从而快速找到相应的行数据。

​ 其中具有多个列的索引称为联合索引复合索引

优点:

  • 提高数据检索效率,降低数据库的IO成本
  • 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗

缺点:

  • 索引列也是要占用空间的
  • 索引大大提高了查询效率,但降低了更新的速度,比如 INSERT、UPDATE、DELETE

索引结构

索引结构 描述
B+ Tree 最常见的索引类型,大部分存储引擎都支持B+树索引
Hash 底层数据结构是用哈希表实现,只有精确匹配索引列的查询才有效,不支持范围查询
R-Tree(空间索引) 空间索引是 MyISAM 存储引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
Full-Text(全文索引) 是一种通过建立倒排索引,快速匹配文档的方式,类似于 Lucene, Solr, ES
索引 InnoDB MyISAM Memory
B+ Tree索引 支持 支持 支持
Hash索引 不支持 不支持 支持
R-Tree索引 不支持 支持 不支持
Full-text 5.6版本后支持 支持 不支持
B Tree

发展历程:

二叉树【左小右大】:

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

二叉树的缺点可以用红黑树来解决:

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

红黑树也存在大数据量情况下,层级较深,检索速度慢的问题

为了解决上述问题,可以使用 B-Tree 结构。
B-Tree (多路平衡查找树) 以一棵最大度数(max-degree,指一个节点的子节点个数)为5(5阶)的 b-tree 为例(5阶意味着每个节点最多存储4个key,有5个指针,5个子节点)

【无论是叶子节点还是非叶子节点,都会保存数据】

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

B+ Tree

不是每个节点都有数据,数据存储在叶子节点的单向链表中。

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

与 B Tree 的区别:

  • 所有的数据都会出现在叶子节点
  • 叶子节点形成一个单向链表
Mysql索引数据结构

MySQL 索引数据结构对经典的 B+Tree 进行了优化。

在原 B+Tree 的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的 B+Tree,提高区间访问的性能。

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

Hash

哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。
如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突(也称为hash碰撞),可以通过链表来解决。

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

特点:

  • Hash索引只能用于对等比较(=、in),不支持范围查询(betwwn、>、<、…)
  • 无法利用索引完成排序操作
  • 查询效率高,通常只需要一次检索就可以了,效率通常要高于 B+Tree 索引

存储引擎支持:

  • Memory
  • InnoDB: 具有自适应hash功能,hash索引是存储引擎根据 B+Tree 索引在指定条件下自动构建的
为什么 InnoDB 存储引擎选择使用 B+Tree 索引结构?
  • 相对于二叉树,层级更少,搜索效率高
  • 对于 B-Tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针也跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低
  • 相对于 Hash 索引,B+Tree 支持范围匹配查询及排序操作

索引分类

常规分类
分类 含义 特点 关键字
主键索引 针对于表中主键创建的索引 默认自动创建,只能有一个 PRIMARY
唯一索引 避免同一个表中某数据列中的值重复 可以有多个 UNIQUE
普通索引 快速定位特定数据 可以有多个 INDEX
全文索引 全文索引查找的是文本中的关键词,而不是比较索引中的值 可以有多个 FULLTEXT
InnoDB中的分类

在 InnoDB 存储引擎中,根据索引的存储形式,又可以分为以下两种:

分类 含义 特点
聚集索引(Clustered Index) 行数据与索引放一块(在一个B+树),索引结构的叶子节点保存了数据表的行数据 必须有,而且只有一个,一次即可查找到行数据
二级索引(非聚集索引)(Secondary Index) 行数据与索引分开存储(在两个B+树),索引结构的叶子节点关联的是数据表行数据的主键,不是真正的数据表的行数据,所以还需要一次回表查询(利用聚集索引查询行数据) 可以存在多个,且需要两次才能查找到行数据!

主键索引也被称为聚簇索引(clustered index),其余都称呼为非聚集索引也被称为二级索引(secondary index)

上面所讲的聚集索引和二级索引的概念主要是在InnoDB中讨论的!

MyISAM也支持聚集索引,但它将表的数据和索引分开存储,而不像InnoDB那样聚集在一起。在MyISAM中,表的数据存储在一个文件中,而索引存储在另一个文件中。

Memory存储引擎将所有数据存储在内存中,不会在磁盘上保留永久的存储结构。因此,关于聚集索引和二级索引的概念在Memory存储引擎中可能不太适用。

聚集索引的选取规则

在InnoDB中,只存在一个聚集索引,而聚集索引的选取规则:

  • 如果存在主键,主键索引就是聚集索引(主键字段作为聚集索引)
  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引(第一个唯一字段作为聚集索引)
  • 如果表没有主键或没有合适的唯一索引,则 InnoDB 会自动生成一个 rowid 作为隐藏的聚集索引(自动生成一个rowid作为聚集索引)

演示图:

在如下的演示图中,针对主键id字段建立了聚集索引,针对name字段建立了普通索引(二级索引),

每一个索引在 InnoDB 里面对应一棵B+树,那么此时就存在着两棵B+树:【一个聚集索引的B+树,一个二级索引的B+树】

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

举例图示:

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

上图的流程为:

  1. 在name索引树上找到名称为Arm的节点 ,主键id为10
  2. 从id索引树上找到id为10的节点, 获取对应的行数据
  3. 从行数据中获取相应字段数据的值

在流程中从非主键索引树搜索回到主键索引树搜索的过程称为:回表,在本次查询中因为查询结果只存在主键索引树中,我们必须回表才能查询到结果。

再举例:

以下图为例,假设现在有一个表,存在id、name、age三个字段,其中id为主键,因此id为聚集索引,name建立索引为非聚集索引。关于id和name的索引,有如下的B+树,可以看到,聚集索引的叶子节点存储的是主键和行记录,非聚集索引的叶子节点存储的是主键。

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

回表查询:

从上面的索引存储结构来看,我们可以看到,在主键索引树上,通过主键就可以一次性查出我们所需要的数据,速度很快。这很直观,因为主键就和行记录存储在一起,定位到了主键就定位到了所要找的包含所有字段的记录。

但是对于非聚集索引,如上面的右图,我们可以看到,需要先根据name所在的索引树找到对应主键,然后再一次通过主键索引树查询到所要的记录(行数据),这个过程叫做回表查询。

所以尽量不要用select *,容易出现回表查询,降低效率,除非有联合索引包含了所有字段

索引覆盖:

上面的回表查询无疑会降低查询的效率,那么有没有办法让它不回表呢?这就是索引覆盖

索引覆盖是指一个查询的结果可以完全通过索引来获取,而不需要访问实际的数据行,即从非主键索引中就能查到的记录,而不需要查询主键索引中的记录,避免了回表的产生减少了树的搜索次数,显著提升性能,这样就避免了回表。

当一个查询所有需要的列都包含在索引中时,可以说这个查询是通过索引覆盖的。

通俗来说,就是数据库引擎不需要去实际的数据表中读取数据行,而是直接通过索引就能够得到查询的结果,从而提高查询的性能

举个例子:

假设有一张数据表(id,name,age),其中id为主键(对应了聚集索引),并插入部分数据,此时数据表所含数据如下:

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

然后我们以name和age两个字段建立联合索引,对应的B+索引树如下:

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

此时如果执行如下sql:

SELECT age FROM student WHERE name = '小李'

那么此时的流程为:

  1. 在name,age联合索引树上找到名称为小李的节点
  2. 此时节点索引里包含信息age 直接返回 12即可【不需回表】

注意:

1.尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能找到),减少 select *

2.如何确定数据库成功使用了覆盖索引呢?

答: 当发起一个索引覆盖查询时,在explain 语句的extra列可以看到using index的信息。

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享


思考题
  1. 在InnoDB存储引擎中,以下 SQL 语句,哪个执行效率高?为什么?
-- 备注:id为主键,name字段创建的有索引
--      所以id字段对应的是聚集索引,name字段上创建的索引是二级索引
select * from user where id = 10;select * from user where name = 'Arm';

答:第一条语句,因为第二条需要回表查询,相当于两个步骤,所以比第一句慢。

  1. InnoDB 主键索引的 B+Tree 高度为多少?

答:假设B+树的每个节点大小为一页且为16KB,数据表的一行数据大小为1kB,一页中可以存储16行这样的数据(即每个B+节点可存储16个行数据)。且假设InnoDB 的指针占用6个字节的空间,主键字段假设为bigint,占用字节数为8.
可得公式:n * 8 + (n + 1) * 6 = 16 * 1024,其中 8 表示 bigint 占用的字节数,n 表示当前B+树节点存储的主键key的数量,

(n + 1) 表示指针数量(比key多一个)。算出n约为1170

如果树的高度为2(最下一层叶子节点的数量为1171),那么他能存储的数据量(数据行数)大概为:1171 * 16 = 18736
如果树的高度为3(最下一层叶子节点的数量为1171 *1171),那么他能存储的数据量(数据行数)大概为:1171 * 1171 * 16 = 21939856。另外,如果有成千上万的数据,那么就要考虑分表(避免树的层级较深,检索速度慢的问题),涉及运维篇知识。

  1. 面试题:一张表,有四个字段(id, username, password, status),由于数据量大,需要对以下SQL语句进行优化,该如何进行才是最优方案
    select id, username, password from tb_user where username='itcast';

答:给username和password字段建立联合索引,则不需要回表查询,直接覆盖索引(索引覆盖)。

单列索引和联合索引

单列索引

单列索引:即一个索引只包含单个列。

创建语法:

CREATE INDEX index_name ON table_name (column_name);

示例: 假设有一个表 employees 包含列 employee_idfirst_namelast_name,如果我们为 first_name 列创建单列索引:

CREATE INDEX idx_first_name ON employees (first_name);

这将创建一个名为 idx_first_name 的单列索引,用于加速对 first_name 列的查询。

注意:

  • 适用于针对单个列的查询。
  • 简单,占用较少的存储空间。
联合索引(复合索引)

联合索引:即一个索引包含了多个列。

索引的底层是一颗B+树,那么联合索引的底层也是一颗B+树,只不过联合索引的B+树节点中存储的是键值。

且由于构建一棵B+树只能根据一个值来确定索引关系,所以数据库依赖联合索引的最左的字段来构建。

举例:创建一个(a,b)的联合索引,那么它的索引树就是下图的样子。

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

可以看到a的值是有顺序的,1,1,2,2,3,3,而b的值是没有顺序的1,2,1,4,1,2。但是我们又可发现a在等值的情况下,b值又是按顺序排列的,但是这种顺序是相对的。这是因为MySQL创建联合索引的规则是首先会对联合索引的最左边第一个字段排序,在第一个字段的排序基础上,然后在对第二个字段进行排序。

创建语法:

CREATE INDEX index_name ON table_name (column1, column2, ...);

示例: 假设我们为 employees 表的 first_namelast_name 列创建联合索引:

CREATE INDEX idx_firstname_lastname ON employees (first_name, last_name);

这将创建一个名为 idx_firstname_lastname 的联合索引,用于加速对 first_namelast_name 列的组合查询。

注意:

  • 适用于涉及多个列的查询,尤其是在涉及这些列的组合查询时。

  • 多列的组合查询可能会更高效。

  • 由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。

    ORDER BY子句也遵循此规则。

  • 建立了一个联合索引,比如(col1,col2,col3),则其等价于/相当于建立了如下的几个索引: (col1),(col1,col2),(col1,col2,col3)!!

注意事项
  • 在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时建议建立联合索引,而非单列索引
  • 多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询

前缀索引

前缀索引是一种索引类型,它并不包含整个列的值,而只包含列值的前缀部分

这种索引可以用来减少索引的存储空间,提高查询性能,特别是在对长文本列或大字符串进行索引时。

创建前缀索引的语法
CREATE INDEX index_name ON table_name (column_name(length));

其中,length 是指定前缀长度的参数。通过指定 length,你可以选择索引列值的前几个字符,而不是整个列的值。

示例

假设有一个表 products 包含一个长文本列 description,我们希望对这个列创建前缀索引:

CREATE INDEX idx_description_prefix ON products (description(100));

在这个例子中,idx_description_prefix 是一个前缀索引,只包含 description 列值的前100个字符。

这样的索引可以提高查询性能,同时占用更少的存储空间。

为什么使用前缀索引?
  1. 节省存储空间: 长文本列或大字符串的完整索引可能会占用大量存储空间,使用前缀索引可以减少索引的大小,提高存储效率。
  2. 提高查询性能: 在某些场景下,只需使用列值的前缀部分进行查询,而不需要完整的列值。使用前缀索引可以减少索引树的深度,提高查询性能。
注意事项:
  1. 选择合适的前缀长度: 前缀长度的选择需要权衡存储空间和查询性能。太短的前缀可能导致索引的选择性下降,而太长可能会失去减小存储空间的效果。
  2. 仅在需要时使用: 不是所有的列都适合使用前缀索引,只有在查询中确实需要使用列的前缀部分时才使用前缀索引。

总的来说,前缀索引是一种用于优化存储和查询性能的技术,特别适用于对大字符串或长文本列进行索引的场景。

索引相关sql

在创建表的同时,创建索引:

1.创建主键索引:

-- 例子中,通过将 PRIMARY KEY 关键字放在列定义上,创建了名为 id 的主键索引。
CREATE TABLE your_table (
 id INT PRIMARY KEY,
 name VARCHAR(50),
 age INT
);

2.创建唯一索引:

-- 例子中,通过在列定义上使用 UNIQUE 关键字,创建了名为 username 的唯一索引。
CREATE TABLE your_table (
 username VARCHAR(50) UNIQUE,
 email VARCHAR(100),
 age INT
);

3.创建普通索引

-- 写法1:通过在列定义后面使用 INDEX 关键字,创建了名为 idx_category 的普通索引。
CREATE TABLE your_table (
 category_id INT,
 product_name VARCHAR(100),
 price DECIMAL(10, 2),
 INDEX idx_category (category_id)
);

-- 写法2::通过在列定义后面使用 Key 关键字,创建了名为 idx_example 的普通索引。
CREATE TABLE your_table (
 column1 INT,
 column2 VARCHAR(50),
 KEY idx_example (column1, column2)
);

--实际上,这两个关键字在这个语境中是可以互换使用的,都表示创建普通索引。选择使用哪一个关键字通常是个人或团队的偏好问题,对于MySQL来说,它们是等效的。
已有数据表后,创建索引:

CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name, ...);

如果不CREATE 后面不加索引类型参数,则创建的是常规索引(默认)


  1. 常规索引(默认): 如果你不指定任何特殊类型,直接创建索引,那么就是常规索引。这种索引允许表中存在相同的索引值,适用于一般的查询需求。

    CREATE INDEX index_name ON table_name (index_col_name, ...);
    
  2. 唯一索引: 如果你希望索引列的值在整个表中是唯一的,不允许重复,你可以创建唯一索引。这对于确保数据完整性很有用。

    CREATE UNIQUE INDEX unique_index_name ON table_name (index_col_name, ...);
    
  3. 全文索引: 如果你需要在文本数据中进行全文本的搜索和匹配,你可以创建全文索引。

    CREATE FULLTEXT INDEX fulltext_index_name ON table_name (index_col_name, ...);
    

区别:

  • 常规索引允许索引列字段出现重复值,唯一索引不允许索引列字段出现重复值。
  • 全文索引用于文本数据字段的全文搜索。
查看索引:

SHOW INDEX FROM table_name;

删除索引:

DROP INDEX index_name ON table_name;

案例:

-- name字段为姓名字段,该字段的值可能会重复,为该字段创建索引
create index idx_user_name on tb_user(name);
-- phone手机号字段的值非空,且唯一,为该字段创建唯一索引
create unique index idx_user_phone on tb_user (phone);
-- 为profession, age, status创建联合索引(多个字段作为索引)
create index idx_user_pro_age_stat on tb_user(profession, age, status);
-- 为email建立合适的索引来提升查询效率
create index idx_user_email on tb_user(email);
-- 删除索引
drop index idx_user_email on tb_user;

索引失效情况

  1. 在索引列上进行运算操作,索引将失效。如:explain select * from tb_user where substring(phone, 10, 2) = '15';
  2. 字符串类型字段使用时,不加引号(推荐单引号),索引将失效。如:explain select * from tb_user where phone = 17799990015;,此处phone的值没有加引号
  3. 模糊查询中,如果仅仅是尾部模糊匹配,索引不会是失效;如果是头部模糊匹配,索引失效。如:explain select * from tb_user where profession like '%工程';,前后都有 % 也会失效。
  4. 用 or 分割开的条件,如果 or 其中一个条件的列没有索引,那么涉及的索引都不会被用到。
  5. 如果 MySQL 评估使用索引比全表更慢,则不使用索引。

索引设计原则

  1. 针对于数据量较大,且查询比较频繁的表建立索引
  2. 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
  3. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
  4. 如果是字符串类型的字段,字段长度较长,可以针对于字段的特点,建立前缀索引
  5. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
  6. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价就越大,会影响增删改的效率
  7. 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询

索引使用原则/规则

在设计查询的sql语句时需要注意一些原则,以充分利用索引来提高查询性能。

联合索引最左前缀法则

首先要知道,最左匹配原则都是针对联合索引来说的,有必要了解一下联合索引的原理。

了解了联合索引,那么为什么会有最左匹配原则这种说法也就理解了。

我们都知道索引的底层是一颗B+树,那么联合索引当然也是一颗B+树,只不过联合索引的健值数量不是一个,而是多个。

而构建一颗B+树只能根据一个值来构建,因此数据库依据联合索引中最左的字段来构建B+树。

例子:

假如创建一个(a,b)的联合索引,那么它的索引树是这样的:

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享

可以看到a的值是有顺序的,1,1,2,2,3,3,而b的值是没有顺序的1,2,1,4,1,2。所以b = 2这种查询条件没有办法利用索引,因为联合索引首先是按a排序的,而跳过了a,单独一个b是无序的。【这就对应了最左前缀法则的第一部分内容,下面会讲

同时我们还可以发现在a值相等的情况下,b值又是按顺序排列的,但是这种顺序是相对有序的,不是绝对有序的。所以最左匹配原则遇上范围查询就会停止,剩下的字段都无法使用索引。例如a = 1 and b = 2 a,b字段都可以使用索引,因为在a值确定的情况下b是相对有序的,而a>1and b=2,a字段可以匹配上索引,但b值不可以,因为a的值是一个范围(不是一个值),在这个范围中b是无序的。但可以用>=或者<=来规避索引失效问题,因为=号会让a出现一个定值,a只要是定值,那么b就是相对有序的了。

这就对应了最左前缀法则的第二部分内容,下面会讲


如果创建了联合索引,则在书写sql查询语句时,要遵守最左前缀法则!它包括两部分内容:

第一部分:

  • sql查询语句的查询条件需从联合索引的最左列开始,并且不跳过联合索引中的列,如果跳跃某一列,联合索引将部分失效(后面的字段索引失效)。换句话说: 即如果查询的时候查询条件精确匹配索引字段从最左边开始的连续一列或几列,则此索引生效

  • 举例:

    假设有一个联合索引 (col1, col2)。最左前缀法则指的是,如果你想要充分利用这个索引,查询条件必须从最左边的列开始,并且不跳过中间的列。如果跳过了某一列,那么索引就会部分失效(因为此时后面的字段是无序的,只有未跳过之前的列是有序)。

    -- 良好的使用方式(可以命中索引)
    SELECT * FROM your_table WHERE col1 = 'value1';
    
    -- 良好的使用方式(可以命中索引)
    -- 这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 col2= xx and col1 =xx,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的。
    SELECT * FROM your_table WHERE col1 = 'value1' AND col2 = 'value2';
    
    -- 不良的使用方式(最左前缀法则失效)
    -- 。这是因为MySQL创建联合索引的规则是首先会对联合索引的最左边第一个字段排序,在第一个字段的排序基础上,然后在对第二个字段进行排序。所以col2=xx这种查询条件没有办法利用索引。
    SELECT * FROM your_table WHERE col2 = 'value2';
    

    在第一个例子中,查询条件从 (col1, col2) 的最左列开始,索引能够被充分利用。

    而在第二个例子中,查询条件跳过了 col1,导致 (col1, col2) 联合索引最左前缀法则失效。

  • 注意:

    • 查询的时候如果联合索引的多个字段都用上了,但是顺序不同,如 col2= xx and col1 =xx,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的。

第二部分:

  • sql查询语句的查询条件在进行范围查询时,如果联合索引中的某一列字段出现了 < >,between,like等,则该列字段右侧所有索引字段失效。【但可以用>=或者<=来规避索引失效问题

  • 举例:

    假设有一个联合索引(a,b,c,d),此时有如下的sql语句:

    select * from your_table where a=1 and b=2 and c>3 and d=4;
    

    那么d是用不到索引的,因为c字段是一个范围查询,它之后的字段会停止匹配索引。


避免select *

尽量不要用select *,容易出现回表查询,降低效率,除非有联合索引包含了所有字段

SQL提示

SQL提示是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示从而传递给数据库来达到优化操作的目的。

如,使用索引:
explain select * from tb_user use index(idx_user_pro) where profession="软件工程";
不使用哪个索引:
explain select * from tb_user ignore index(idx_user_pro) where profession="软件工程";
必须使用哪个索引:
explain select * from tb_user force index(idx_user_pro) where profession="软件工程";

use 是建议,实际使用哪个索引 MySQL 还会自己权衡运行速度去更改,force就是无论如何都强制使用该索引。

sql优化

主键优化

在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(Index organized table, IOT)

  1. 合理选择主键(主键应具有唯一性)

    主键列的值应该具有唯一性,确保每一行都能被唯一标识。

    CREATE TABLE your_table (
        id INT PRIMARY KEY AUTO_INCREMENT,
        name VARCHAR(50),
        ...
    );
    
  2. 避免过长的主键

    避免使用过长的主键,因为主键通常会被用作其他表的外键,较长的主键可能导致索引的大小增加,从而影响查询性能。

    CREATE TABLE your_table (
        user_id INT PRIMARY KEY,  -- 避免使用过长的主键
        name VARCHAR(50),
        ...
    );
    
  3. 利用自增主键

    插入数据时,尽量选择顺序插入,选择使用 AUTO_INCREMENT 自增主键

    自增主键通常可以提高插入性能,并确保新插入的数据总是添加到索引的末尾。

    CREATE TABLE your_table (
        id INT PRIMARY KEY AUTO_INCREMENT,
        name VARCHAR(50),
        ...
    );
    
  4. 合理使用聚集索引(指定存储引擎为InnoDB)

    在InnoDB等支持聚集索引的数据库中,主键通常是聚簇索引,这意味着行数据的物理存储顺序与主键的顺序相同。

    这有助于提高范围查询的性能。

    CREATE TABLE your_table (
        id INT PRIMARY KEY,
        name VARCHAR(50),
        ...
    ) ENGINE=InnoDB;
    
  5. 业务操作时,避免对主键的修改

    主键的值不宜频繁更新,因为更新主键可能涉及到对其他索引的维护,导致性能开销。

    -- 不推荐的更新方式
    UPDATE your_table SET id = id + 1 WHERE ...;
    
  6. 尽量不要使用 UUID(尽管UUID有全局唯一性的优势) 做主键或者是其他的自然主键,如身份证号.

插入数据优化

  1. 批量插入:

    批量插入是提高插入性能的有效手段。 相较于逐条插入,批量插入能减少事务处理和日志写入的开销,提高整体性能。

    -- 逐条插入
    INSERT INTO your_table (column1, column2) VALUES (value1, value2);
    INSERT INTO your_table (column1, column2) VALUES (value3, value4);
    
    -- 批量插入
    INSERT INTO your_table (column1, column2) VALUES
    (value1, value2),
    (value3, value4),
    (value5, value6);
    

    如果一次性需要插入大批量数据,使用insert语句插入性能较低,此时可以使用MySQL数据库提供的load指令插入。

    # 客户端连接服务端时,加上参数 --local-infile(这一行在bash/cmd界面输入)
    mysql --local-infile -u root -p
    # 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
    set global local_infile = 1;
    select @@local_infile;
    # 执行load指令将准备好的数据,加载到表结构中
    load data local infile '/root/sql1.log' into table 'tb_user' fields terminated by ',' lines terminated by '\n';
    
  2. 禁用索引

    在大批量插入数据时,可以考虑在插入过程中禁用索引,然后再启用索引。这有助于减少索引维护的开销。

    -- 禁用索引
    ALTER TABLE your_table DISABLE KEYS;
    
    -- 执行插入操作
    
    -- 启用索引
    ALTER TABLE your_table ENABLE KEYS;
    
  3. 合理设置事务块,手动提交事务

    对于事务性要求不是很高的插入操作,可以考虑将插入语句放在更大的事务块中,减少事务的开销,然后手动提交事务。

    START TRANSACTION;
    
    -- 执行插入操作
    
    COMMIT;
    
  4. 主键顺序插入数据

    按照主键的顺序插入数据,有助于减少数据页的频繁分裂和移动,提高写入性能

    -- 主键顺序插入
    INSERT INTO your_table (id, column1, column2) VALUES (1, 'value1', 'value2');
    INSERT INTO your_table (id, column1, column2) VALUES (2, 'value3', 'value4');
    INSERT INTO your_table (id, column1, column2) VALUES (3, 'value5', 'value6');
    

update优化

  1. 避免全表更新

    避免不带WHERE条件的全表更新,因为这样的更新会涉及到整个表的数据,性能开销较大。

    -- 不推荐的全表更新
    UPDATE your_table SET column1 = value1;
    
  2. 确保更新操作的WHERE条件涉及的列上有合适的索引,以提高检索效率和避免行锁升级为表锁

    InnoDB 的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。

    如以下两条语句:

    update student set no = '123' where id = 1;-- 这句由于id有主键索引,所以只会锁这一行;
    update student set no = '123' where name = 'test'; -- 这句由于name没有索引,所以会把整张表都锁住进行数据更新,解决方法是给name字段添加索引
    
  3. 使用limit限制更新行数

    在进行大量更新时,可以使用LIMIT限制每次更新的行数,避免一次性锁定大量数据。

    UPDATE your_table 
    SET column1 = value1 
    WHERE condition1 
    LIMIT 1000;
    

group by优化

  1. 使用索引

    确保 GROUP BY 语句中涉及的列上有合适的索引,以提高检索效率。

    分组操作时,索引的使用也是满足最左前缀法则的

    -- 假设category有合适的索引
    SELECT category, AVG(price)
    FROM products
    WHERE date BETWEEN '2022-01-01' AND '2022-12-31'
    GROUP BY category;
    
  2. 避免过多的group by列

    尽量减少 GROUP BY 语句中的列数,避免不必要的计算。

    -- 不推荐的写法,GROUP BY 列数过多
    SELECT category, subcategory, AVG(price)
    FROM products
    GROUP BY category, subcategory;
    
    -- 推荐的写法,尽量减少 GROUP BY 列数
    SELECT category, AVG(price)
    FROM products
    GROUP BY category;
    

order by优化

补充explain结果的extra字段知识:

  1. Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sort buffer 中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序
  2. Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高

如果order by字段全部使用升序排序或者降序排序,则都会走索引,但是如果一个字段升序排序,另一个字段降序排序,则不会走索引,explain的extra信息显示的是Using index, Using filesort,如果要优化掉Using filesort,则需要另外再创建一个索引,如:create index idx_user_age_phone_ad on tb_user(age asc, phone desc);,此时使用select id, age, phone from tb_user order by age asc, phone desc;会全部走索引。

总结:

  • 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则
  • 尽量使用覆盖索引
  • 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)
  • 如果不可避免出现filesort,大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)

limit优化

  1. 避免使用子查询

    在 LIMIT 查询中,尽量避免使用子查询,因为子查询可能导致性能下降。

    -- 下面的语句是错误的,因为 MySQL 不支持 in 里面使用 limit
    select * from tb_sku where id in (select id from tb_sku order by id limit 9000000, 10);
    
    -- 不推荐的写法,使用子查询
    SELECT column1, column2
    FROM your_table
    WHERE id IN (SELECT id FROM another_table WHERE some_condition)
    LIMIT 10;
    
    -- 推荐的写法,使用 JOIN
    SELECT t1.column1, t1.column2
    FROM your_table t1
    JOIN another_table t2 ON t1.id = t2.id
    WHERE t2.some_condition
    LIMIT 10;
    
  2. 使用覆盖索引

    如果可以使用覆盖索引,可以减少对数据表的实际访问,提高性能。

    -- 假设有合适的覆盖索引
    SELECT column1, column2
    FROM your_table
    WHERE some_condition
    ORDER BY indexed_column
    LIMIT 10;
    
    -- 通过覆盖索引加快速度,直接通过主键索引进行排序及查询
    select id from tb_sku order by id limit 9000000, 10;
    
  3. 避免全表扫描

    LIMIT 查询时,尽量避免对整个表进行扫描。通过使用索引和合适的 WHERE 子句,减小查询的数据集。

    -- 不推荐的写法,全表扫描
    SELECT column1, column2
    FROM your_table
    LIMIT 10;
    
    -- 推荐的写法,使用索引和 WHERE 子句
    SELECT column1, column2
    FROM your_table
    WHERE some_condition
    LIMIT 10;
    

count优化

补充知识:

  • MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高(前提是不适用where);

    而InnoDB 在执行 count(*) 时,则需要把数据一行一行地从引擎里面读出来,然后累计计数,效率慢。

  • 如果count函数的参数(count里面写的那个字段)不是NULL(字段值不为NULL),累计值就加一,最后返回累计值

  • count的几种用法: count(*)、count(主键)、count(字段)、count(1)

  • count的几种用法的性能:

    • count(*)跟count(主键)一样,因为主键不能为空;且两者计数时都不会忽略列值为NULL的情况

    • count(字段)只计算字段值不为NULL的行

    • count(1)引擎会为每行添加一个1,然后就count这个1,返回结果也跟count(*)一样;

      这是因为 COUNT 函数需要对指定的列或表达式进行计数,而 COUNT(1) 中的 “1” 不是实际的数据列,而是一个常量。因此,数据库引擎为每一行生成一个 “1”,然后对这些 “1” 进行计数,最终得到行数。

    • count(null)返回0

    • 按效率排序:count(字段) < count(主键) < count(1) < count(*),所以尽量使用 count(*)

总结:

  1. 由于考虑效率和性能,尽量使用count(*)

    -- 不推荐的写法,COUNT(column)
    SELECT COUNT(column)
    FROM your_table
    WHERE some_condition;
    
    -- 推荐的写法,COUNT(*)
    SELECT COUNT(*)
    FROM your_table
    WHERE some_condition;
    
  2. 避免使用COUNT(DISTINCT column)

    在某些情况下,COUNT(DISTINCT column) 可能会导致性能下降,尽量避免使用

    -- 不推荐的写法,COUNT(DISTINCT column)
    SELECT COUNT(DISTINCT column)
    FROM your_table
    WHERE some_condition;
    
    -- 推荐的写法,使用其他方式处理
    SELECT COUNT(*)
    FROM (SELECT DISTINCT column FROM your_table WHERE some_condition) AS subquery;
    


The End!!创作不易,欢迎点赞/评论!!欢迎关注我的WXGZ号!

【一文详解】知识分享:(MySQL关系型数据库知识进阶),数据库,数据库,mysql,java,数据库开发,经验分享文章来源地址https://www.toymoban.com/news/detail-784641.html

到了这里,关于【一文详解】知识分享:(MySQL关系型数据库知识进阶)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Sqlserver_Oracle_Mysql_Postgresql不同关系型数据库之主从延迟的理解和实验

    关系型数据库主从节点的延迟是否和隔离级别有关联,个人认为两者没有直接关系,主从延迟在关系型数据库中一般和这两个时间有关:事务日志从主节点传输到从节点的时间+事务日志在从节点的应用时间 事务日志从主节点传输到从节点的时间,相关因素有以下2点: 1、事

    2024年02月14日
    浏览(53)
  • 关系型数据库和非关系型数据库

     关系型数据库是以 关系(表格) 为基础的数据库,它采用了 SQL(Structured Query Language)作为数据操作语言,常见的关系型数据库包括 MySQL、Oracle、SQL Server 等。 非关系型数据库则是基于 文档、键值、列族 等方式存储数据的数据库,它通常没有固定的表结构,因此也被称为

    2024年02月09日
    浏览(45)
  • 关系型数据库与非关系型数据库类比

    关系型数据库和非关系型数据库都有多种不同类型,每种类型都针对不同的数据存储需求和使用场景。以下是一些常见的关系型数据库和非关系型数据库类型: 关系型数据库类型: MySQL: 一种开源的关系型数据库管理系统,用于处理结构化数据,适用于各种规模的应用。

    2024年02月11日
    浏览(40)
  • 关系型和非关系型数据库的区别

    关系型数据库,是指采用了关系模型来组织数据的数据库,关系型数据库的最大特点就是事务的一致性。关系型数据天然就是表格式的,因此数据存储在数据表的行和列中。数据表可以彼此关联协作存储,也很容易提取数据。 优点 易于维护:都是使用表结构,格式一致。 使

    2024年02月13日
    浏览(90)
  • 关系型非关系型数据库区别,以MongoDB为例在express中连接MongoDB示例

    目录 关系型数据库 关系型数据库常见的类型有: 关系型数据库的优点包括: 非关系型数据库 非关系型数据库常见的类型有: 非关系型数据库的特点包括: 关系型数据库和非关系型数据库区别 MongoDB是什么 MongoDB优势: 在Express中连接MongoDB步骤 Schema 关系型数据库是以关系模

    2024年01月16日
    浏览(77)
  • 关系型数据库设计

    目录 1.数据库设计的重要性及定义 1.1 数据库设计的重要性 1.1.1 失败的数据库设计造成的后果  1.1.2 优秀的数据库设计带来的好处  1.2 数据库设计的定义  2.数据库需求分析  2.1 需求分析的步骤 2.1.1 收集信息 2.1.2 标识实体 2.1.3 标识每个实体的详细信息  2.1.4 标识实体之

    2024年04月10日
    浏览(66)
  • 非关系型数据库

    一、什么是非关系型数据库? 随着互联网的飞速发展,人们对数据存储和管理的需求越来越高,传统的关系型数据库遇到了越来越多的挑战。为了满足海量数据存储和高性能查询的需求,非关系型数据库(NoSQL)应运而生。 非关系型数据库是指不使用关系模型进行数据组织和

    2024年02月07日
    浏览(48)
  • 数据库介绍-非关系型数据库

    NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库。 NoSQL 不依赖业务逻辑方式存储,数据存储的类型不需要一个固定形式。因此大大的增加了数据库的扩展能力。 不遵循 SQL 标准 不支持 ACID 远超于 SQL 的性能 易扩展 大读写量,高性能 数据模型灵活 高可用

    2024年02月16日
    浏览(55)
  • Redis_非关系型数据库

    也叫Not Only SQL(不仅仅是SQL, 不用 sql语言操作的数据库), 一般指 非关系型数据库 关系型数据库: 以数据库表为单位存储,表与表之间存在某种关系 非关系型数据库: 数据与数据之间没有关系, 数据就是以键值对的形式存储, 通过键获取到值 在互联网发展中: 大致经历三个时期,w

    2024年02月16日
    浏览(41)
  • Elasticsearch与关系型数据库集成

    Elasticsearch是一个开源的搜索和分析引擎,基于Lucene库开发,具有高性能、可扩展性和实时性等特点。关系型数据库则是一种结构化数据库管理系统,以表格形式存储数据,支持SQL查询语言。在现实应用中,Elasticsearch与关系型数据库往往需要进行集成,以实现更高效、灵活的

    2024年02月20日
    浏览(72)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包