《MySQL必修课:瞬间搞定大厂高频MySQL面试题,轻松拿下心仪职位!》

文章目录

请你说一下数据库优化

架构设计层面优化:

  • 主从集群:保证服务的高可用性
  • 读写分离:避免读写冲突,导致的性能问题
  • 分库分表:分库节省单个服务器的IO压力,分表降低单表数据量,提升SQL查询效率
  • 热点缓存:针对热点数据,引入高效的分布式数据库(Redis、MongoDB),缓解Mysql的访问压力,提升数据检索性能

SQL层面优化规则:

  • SQL的查询一定要基于索引来进行数据扫描
  • 查询有效的列信息即可,少用 代替列信息* (防止不必要的回表)
  • 永远用小结果集去驱动大结果集
  • 使用索引扫描,联合索引中的列,从左往右命中越多越好
  • 尽可能使用SQL语句用到的索引完成排序 (用索引字段去排序)
  • 避免索引失效
    • 避免索引列上使用函数或者运算符,这样会导致索引失效。
    • where字句中like %号,尽量放置在右边
    • 索引失效的几种情况:

(1) Like的参数以通配符开头时

(2) where条件不符合最左前缀原则时

(3) 使用!= 或 <> 操作符时

(4) 索引列参与计算(使用函数或者运算符)

(5) 对字段进行null值判断

(6) 使用or来连接条件

建表层面优化:

  • 在表中建立索引,优先考虑 where、order by 使用到的字段。
  • 尽量使用数字型字段(如性别,男:1 女:2),若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。
    • 这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
  • 查询数据量大的表会造成查询缓慢。主要的原因是扫描行数过多。这个时候可以通过程序,分段分页进行查询,循环遍历,将结果合并处理进行展示。
  • 用 varchar/nvarchar 代替 char/nchar。
    • 尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
    • 不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL 也包含在内),都是占用 100 个字符的空间的,如果是 varchar 这样的变长字段, null 不占用空间。

数据库调优

  • 平时在项目中有做过一些优化,首先是数据库设计的时候会先考虑到优化问题,比如表的字段类型、长度、各表之间的关系,以及创建合适的索引。其次是在开发过程中出现sql问题后的优化,一般是查询时间慢,在这个阶段需要查看慢查询日志以及explain的信息,然后调整sql语句,比如索引的创建或修改。

  • 比如 select id, name, sex where name = 'zhangsan' ; 可将name跟sex组成联合索引,来避免回表。

事务

一个事务是一个完整的业务逻辑,不可再分。

事务的存在使数据更加的安全,完整。

例如:银行账户转账,从A账户向B账户转账1000,需要执行两条DML语句update t_act set money=money-1000 where actno='A';update t_act set money=money+1000 where actno='B'; 。此时需要保证这两条更新语句必须同时成功或同时失败。

tips: 如果要让多条DML语句同时执行成功或执行失败,则需要使用事务

事务执行流程:

  1. 开启事务机制
  2. 执行一条或多条DML语句(在缓存中执行,不直接影响文件)
  3. 提交或回滚事务
    • commit; 提交事务。缓存区的内容更新到文件,清空缓存区
    • rollback; 回滚事务。清空缓存区。

事务的四大特性(ACID)

  • 原子性,要么全部执行成功,要么全部执行失败
  • 一致性,数据在事务开始到结束的过程中一致
  • 隔离性,事务之间相互隔离,互不影响
  • 持久性,事务完成后的数据要被持久化下来

事务是指逻辑上的一组操作,要么都执行,要么都不执行,

事务的四大特性是如何实现的?

  • 原子性:通过undolog实现,事务执行失败之后会通过undolog回滚到之前的状态。例如delete一条记录,undolog就记录一条insert。

  • 一致性:其他三个特性如果实现了,该特性也就实现了。

  • 隔离性:利用MVCC和锁 机制。

  • 持久性:通过redolog实现,redolog会记录数据库的每一个写操作。当数据库空闲的时候会将redolog的内容更新到数据库中,然后擦除redolog的内容。

事务的隔离级别

  • 谈到事务隔离级别,就必须谈到并发事务,可能产生的问题脏写、脏读、不可重复读、幻读
    • 脏写:最后的事务覆盖了其他事务所做的更新
    • 脏读:事务A读取到了事务B已经修改尚未提交的数据
    • 不可重复读:同一事务内部的数据,在不同时刻读取到的不一致
    • 幻读:事务A读取到事务B提交的新增数据,不符合隔离性。
  • 读未提交,不能避免脏读、不可重复读、幻读
  • 读取已提交,主要解决脏读问题
  • 可重复读,主要解决不可重复读的问题
  • 串行化, 主要解决幻读的问题

并发事务带来的问题

* 脏读(Dirty read): 事务A正在访问数据并进行修改,未提交到数据库,此时事务B访问并使用了这个未提交的数据。那么事务B读到的就是脏数据。
* (脏写)丢失修改(Lost to modify):事务A读取一个数据,事务B也读取该数据,事务A修改该数据后,事务B也修改了该数据,这样造成事务A的修改结果丢失。
* 不可重复读(Unrepeatableread):事务A多次读取同一数据,事务A未结束,事务B访问并修改了该数据,造成事务A前后2次读取的结果不一样;
* **幻读(Phantom read)😗*与不可重复读类似。事务A读取几行数据,接着并发事务B插入一些数据,事务A在随后读取中,发现多了一些原本不存在的记录,好像发生幻觉。

不可重复度和幻读区别:

不可重复读的重点是修改,幻读的重点在于新增或者删除。

mysql怎么修改事务隔离级别

mysql怎么修改事务隔离级别-mysql教程-PHP中文网

  • 方法1:执行命令修改

    • //查看当前事物级别:
      SELECT @@tx_isolation;
      //设置mysql的隔离级别:
      set session transaction isolation level 需要设置的事务隔离级别
      
      //设置read uncommitted级别:
      set session transaction isolation level read uncommitted;
      //设置read committed级别:
      set session transaction isolation level read committed;
      //设置repeatable read级别:
      set session transaction isolation level repeatable read;
      //设serializable级别:
      set session transaction isolation level serializable;
      
      
  • 方法2:mysql.ini配置修改

    • 打开mysql.ini配置文件,在最后加上

    • #可选参数有:READ-UNCOMMITTED,READ-COMMITTED,REPEATABLE-READ,SERIALIZABLE.
      [mysqld]
      transaction-isolation = REPEATABLE-READ
      
      
    • 这里全局默认是REPEATABLE-READ,其实MySQL本来默认也是这个级别

索引

什么是索引

  • 索引是用来优化查询速度的一种数据结构,可以用来加快分组和排序的速度以及表之间的连接,但是建立索引需要占用物理空间,对应增删改后的索引数据需要进行动态维护,在大数据量情况下,效率会很低。

索引的作用

  • 数据是存储在磁盘上的,倘若没有索引,会加载所有数据到内存中,磁盘IO次数会很多,查询速度会很慢,但是有了索引,就不需要加载所有的数据到内存中,访问磁盘IO的频率也会少了很多,比如:B+Tree的深度只有2到4层,相当于2到4次磁盘IO就能查询到数据。

什么时候需要建索引

  • 经常用where条件查询的字段、经常连接的字段、经常需要排序的字段。

什么时候不使用索引

  • 查询条件用不到的字段、经常增删改的字段、参与计算的字段、区分度不高的字段、表记录较少

索引的设计原则

  • 查询更快,占用空间小。

    • 推荐建立索引:where的查询条件、进行连接的列、经常排序的字段。
    • 不推荐建立索引:数据量小的表、频繁增删改的字段、参与计算的字段、区分度不高的字段、涉及查询少的字段
    • 对于字符类型,如果是是长字段串可以指定一个前缀长度,可以节省磁盘空间。
    • 不要过度使用索引,索引需要占用额外的磁盘空间降低写操作的性能,修改字段时,会重建索引,数据量越大花费的时间也越多。
    • 最左前缀原则
  • 不要盲目的建索引。索引需要额外的磁盘空间,而维护索引的成本增加。

  • 使索引尽可能的小的占用内存空间。选int还是varchar?因为varchar占用的内存是可变的,而int是固定占用4B,所以当varchar大于4B的时候用int,小于4B的时候用varchar。

  • 经常做查询或排序的字段应该建索引

  • 索引字段应该少做更新删除操作。因为要维护索引。

  • 外键字段最好建索引。在多表联查中,索引要建立在副表中。

Mysql的索引数据结构

  • 常见的索引结构
    • 二叉树
      • 每个节点最多只有两个子节点
      • 左子节点值 < 根节点值;右子节点值 > 根节点值;
    • 红黑树
      • 节点非红即黑
      • 根节点是黑节点
      • 红节点的子节点一定是黑节点
      • 节点到达叶子节点相同路径包含相同数量的黑节点
      • 叶子节点都是黑节点
    • 哈希表
      • 本质就是一个数组,通过将key进行hash计算出数组的下标进行存放元素,当遇到hash碰撞的时候,可以采用链表进行存储
      • 对于拿元素的时候,将key进行hash计算定位到元素的槽位,然后去查找
      • 哈希表比较适合=、in等值查询,效率很高,但是不支持范围查询而且会占用更多的空间
    • B-Tree
      • 叶子节点具有相同深度,叶子节点的指针为空
      • 所有索引元素不重复
      • 节点是从左到右递增排列
    • B+Tree
      • 叶子节点具有指针连接,提升区间访问效率
      • 非叶子节点不存储数据,只存储索引并且冗余上一层节点,可以存放更多数据
      • 叶子节点包含所有索引字段
      • 节点是从左到右递增排列
    • Mysql的索引结构采用B+Tree

索引知识补充

  • 索引相当于书的目录,可以提高sql的查询效率。

  • 在InnoDB和MyISAM的引擎下,索引和实际的数据都是存储在磁盘的,只不过进行数据读取的时候会优先把索引加载到内存中。而对于memory引擎,数据跟索引都是放在内存中。

  • 索引底层采用的数据结构B+树。3~4层的B+树足以支持千万级别的数据量存储。因为innodb中,B+树的每个节点默认是16K

B树:类似普通的平衡二叉树,不同的一点是B树允许每个节点有更多的子节点。

在这里插入图片描述

B+树是B-树的变体,也是一种多路搜索树, 它与 B- 树的不同之处在于:

  1. 所有数据存储在叶子节点,非叶子节点并不存储真正的data,在mysql中非叶子节点存储的都是索引
  2. 为所有叶子结点增加了一个双向指针

在这里插入图片描述

使用B+树的好处:

  1. 因为非叶子节点没有存数据,每个节点能索引的范围更大更精确,可以减少树的高度。
  2. B+树的叶子节点两两相连大大增加了区间访问性,可很好的进行范围查询等

为什么MySQL最终选择B+Tree作为索引结构

  • 对于二叉树、红黑树的深度不可控,可能会进行大量的磁盘IO
  • 哈希表主要是适合等值查询的场景,不适合范围查询
  • B-Tree 不支持区间访问以及不能存储更多的索引
  • B+Tree 深度可控以及非叶子节点只存储索引,不存储数据,能存放更多的索引并且叶子节点有指针连接,支持区间访问。

为什么MySQL要⽤B+Tree⽽不⽤跳表

  • B+Tree比跳表的检索效率更高,数据分布也更均匀
  • 跳表的思想类型二路分治实现O(logN),B+Tree通过多路分治实现O(logN)
  • 当数据表的数据足够多的时候,B+Tree的根节点到任意一个叶子节点路径是固定的,而跳表的头节点到目标节点的路径是不固定的,所以检索的value越大,跳表的路径就越深,磁盘IO次数就越多。
  • B+Tree的所有叶子节点构成一个双向循环链表,每一块叶子节点可以存储一条或多条数据,这种可以减少磁盘IO
  • 跳表每一个节点只存储一条数据,对于一条数据的查找比较节省磁盘IO的,但是对于多条数据的查询,跳表的磁盘IO效率比B+Tree低

InnoDB存储引擎也有自己的最小储存单元——页(Page),一个页的大小是16K。

B+Tree 单个叶子节点可以存的记录数 =16k/1k =16.

索引的分类

  • 主键索引就是primary key , 唯一非空
  • 唯一索引就是unique key, 索引值唯一但允许为空
  • 组合索引就是多个字段组合起来的索引,遵循左前缀原则
  • 全文索引就是MylSAM提供的,只能字符类型的才能使用
  • 最左前缀原则就是最左优先

    • 在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。 mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配
      • 比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的
      • 如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
      • =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。
  • 了解索引下推吗?

    • MySQL 5.6引入了索引下推优化。默认开启
    • 有了索引下推优化,可以在减少回表次数
    • 在InnoDB中只针对二级索引有效

MySQL如何为表字段添加索引

  • 普通索引

    • CREATE INDEX <索引的名字> ON tablename (字段名); 
      ALTER TABLE tablename ADD INDEX [索引的名字] (字段名); 
      CREATE TABLE tablename ( [...], INDEX [索引的名字] (字段名) );
      
  • 唯一索引

    • CREATE UNIQUE INDEX <索引的名字> ON tablename (字段名); 
      ALTER TABLE tablename ADD UNIQUE INDEX [索引的名字] (字段名);
      CREATE TABLE tablename ( [...], UNIQUE [索引的名字] (字段名) ;
      
  • 复合索引

    • CREATE INDEX <索引的名字> ON tablename (字段名1,字段名2...);
      ALTER TABLE tablename ADD INDEX [索引的名字] (字段名1,字段名2...);
      CREATE TABLE tablename ( [...], INDEX [索引的名字] (字段名1,字段名2...) );
      
# 增删改查
SELECT * FROM biao1 WHERE id=6;

INSERT INTO biao1(name1,age) VALUES('新增加1','1000');

UPDATE biao1 SET name1="这是修改",age='2000'  WHERE id=20;

DELETE FROM biao1 WHERE id='20',

聚簇索引和⾮聚簇索引

  • 聚簇索引就是叶子节点存储索引和数据, InnoDB属于聚簇索引
  • ⾮聚簇索引就是叶子节点存储索引和磁盘地址,通过磁盘地址找到存储的数据,需要回表操作,所以效率比聚簇索引慢,MylSAM属于非聚簇索引。

聚簇索引 也叫聚集索引。,并不是一种单独的索引类型,而是一种数据存储方式。

二级索引,又称作辅助索引,均属于非聚簇索引

索引什么时候会失效

  • 对于组合索引没有走最左前缀原则会导致索引失效
  • 进行like模糊查询时,%放在索引字段之前,走不了索引下推,导致索引失效,倘若数据量过大,索引下推也会失效
  • 查询条件的类型为字符串时,没有加单引号,可能因为类型不同导致索引失效
  • 对索引列进行计算会导致索引失效
  • 查询条件使用or连接会导致索引失效
  • 判断索引列不等于某个值会导致索引失效

唯一索引比普通索引快吗? 为什么?

  • 查询效率上比较: 效率相差不大
    • 普通索引查询流程:
      • 根据索引找到第一条满足查询条件的记录
      • 找到下一行记录
      • 判断下一行是否满足查询条件
      • 满足重复步骤2,不满足返回满足条件的结果集
    • 唯一索引查询流程:
      • 根据索引找到第一条满足查询条件的记录
      • 返回该行记录
  • 从更新角度看,普通索引可以利用 change buffer ,普通索引更新操作的性能比唯一索引要更好

索引优化原则

  • 代码先上,索引后上
  • 开启慢SQL查询优化,方便分析定位问题
  • 能使用覆盖索引就不要使用全表扫描
  • 不要基于小基数的字段上建立索引
  • 不要对索引字段进行计算
  • 不要用or连接索引字段
  • 频繁增删改的字段不要加索引,会导致频繁的去维护索引树
  • 长字符串可以使用前缀索引,比如前20个字符可以满足90%场景
  • 尽量让组合索引覆盖大部分场景
  • is null 和is not null一般不会走索引
  • 让like走索引下推,如果非必要不要把%放在索引列前
  • where和order by冲突时,优先使用where, 能用where进行过滤就不要使用having
  • 让组合索引、order by(先排序后分组) 和group by(先排序后分组)走最左前缀原则
  • 如果需要获取总行数
    • MylSAM可以直接获取总行数,因为它内部维护了一个计数器
    • 如果使用的是InnoDB,如果需要计算总行数使用count(星),或者count(1),因为count(星)MySQL做了专门的优化,而count(1)可以看似统计常量
      • 倘若字段有索引,查询效率 count(星)=count(1)>count(字段)>count(id), count(字段)效率比count(id)好的原因是二级索引树比主键索引树数据量小,所以走了覆盖所有
      • 倘若没有索引,count(*)=count(1)>count(id)>count(字段),此时没有索引,所以只能走主键索引
  • join要做到小表驱动大表,left join左小右大, right join 反之。 inner join 由MySQL优化器帮忙选择
  • in 和exists 要做到小表驱动大表, in适合子表小于主表的情况,exists反之。
    • in后面跟的是小表,exists后面跟的是大表。
    • MySql 的执行顺序会先执行子查询,再执行主查询 所以 in后面跟的是子查询 会先被执行
    • EXISTS 语法理解为:将主查询的数据放在子查询中做条件验证,根据结果TRUE 和 FALSE 来决定主查询中的数据是否需要保留。EXISTS 子查询只返回TRUE 或 FALSE

主键索引和非主键索引的区别

  1. 主键索引的叶子节点存放着数据,非主键索引的叶子节点存放着主键索引的值(这是在innodb中。如果myisam中,主键索引和普通索引是没有区别的都是直接索引着数据)
  2. 进行主键查询的话可以一次性取到数据,而进行非主键查询的话需要进行回表才能拿到数据。

为什么非主键索引的叶子节点的数据存储的是主键ID

  • 对于InnoDB的二级索引存储的数据是主键ID, 主要是为了保证数据的一致性,因为如果存储的是数据,需要维护主键索引和非主键索引两课树,数据一致性很难保证好,但是存储唯一非空的主键ID,可以通过根据主键ID查询数据。

组合索引在B+树中是如何存储的

**多个列形成一个索引,放入节点中。**进行索引查找的时候先对第一个列进行索引查找,然后拿查找出来的索引数据 从 第二列开始往后逐个匹配。

例:select * from table where b = 13 and c = 16 and d = 4;

在这里插入图片描述

利用第一个索引字段b,找到b=13的索引,然后从找出的索引中继续匹配c=16的索引,最后匹配d=4的索引数据,于是找到该组合索引下的聚簇索引为1,再从聚簇索引树上找到最终数据。可以看到只有第一个索引字段b是排好序的,剩余的字段都是在前一个字段排好序且相等的情况下再进行排序

组合索引也通常用来排序,因为可以省去查出数据后引擎再进行排序的时间。

名词解释

  • **回表:**先根据非聚簇索引找到聚簇索引的值,然后根据聚簇索引找到数据。
    • id主键,name为普通索引
    • select * from table where name = ‘zhangsan’;
  • **索引覆盖:**要查询的字段全都是索引
    • select id,name from table where name = ‘zhangsan’;
  • 最左匹配:针对组合索引,只要查询条件与组合索引从左到右部分字段的顺序相匹配,该次查询就可以使用组合索引进行搜索。
    • 例如现有联合索引(x,y,z)
    • WHERE x=1 AND y=2 AND z=3; 符合最左匹配原则。
    • WHERE x=1 AND y>2 AND z=3; 不符合最左匹配原则,但会使用索引x跟y。
    • WHERE x=1 AND y>2; 符合最左匹配原则。
    • WHERE y>2 AND x=1; 符合最左匹配原则。优化器为了更好的使用索引,会动态调整字段的顺序
    • WHERE y>2 AND z=3; 不符合最左匹配原则,无法使用组合索引
    • WHERE x=1 AND z=3; 不符合最左匹配原则,无法使用组合索引。但会先使用索引x找到x=1的数据,然后再在找到的数据中逐个匹配z=3的数据。
    • WHERE x=1 AND z=3 order by y; 不符合最左匹配原则,因为y不是用于查找。
    • where x=1 and y like‘%k’and z=3 不符合最左匹配原则,但会使用索引x,此时查询的类型是ref
    • where x=1 and y like‘k%k’and z=3 符合最左匹配原则,此时查询的类型是range
    • where x=1 and y like‘kk%’and z=3 符合最左匹配原则,此时查询的类型是range
    • where x=1 and y like‘kk%’and z in (3, 5) 符合最左匹配原则,此时查询的类型是range
  • **索引下推:**利用出现在where中的组合索引字段都筛选完之后才返回到server层。
    • select * from user where name like '张%' and age=18 and ismale=1; 其中 (name, age)是组合索引。
    • 使用索引下推时,会先找到 name以张开头的数据,然后再匹配 age=18的数据,最后将最后匹配到的数据返回到server,在server层中再筛选出 ismale=1的数据
    • 不使用索引下推时,找到 name以张开头 的数据后直接返回到server层,然后再在server层做数据的筛选,这样会将冗余的数据都拷贝到server层。

server层是一个进程。

索引下推是什么

  • 在mysql 5.7版本正式推出索引下推,大致是本身是在mysql的服务层面去对数据进行筛选
  • 索引下推后,改为在mysql引擎层面去对数据筛选,返回到mysql的服务层
  • 这样,可以有效减少服务层从存储引擎接收数据的次数

InnoDB和MylSAM的区别

  • InnoDB支持事务,支持事务的四种隔离级别,MylSAM不支持事务,但是每次查询都是原子的
  • InnoDB支持外键,MySAM不支持外键
  • InnoDB属于聚簇索引,MylSAM属于非聚簇索引
  • InnoDB支持行锁粒度更小,但是可能因为范围而锁表,MylSAM支持表锁,每次操作都会锁表
  • InnoDB不存储总行数,MylSAM存储总行数
  • InnoDB适合增删改场景,MylSAM适合大量查询的场景

为什么建议InnoDB必须设置主键

  • 因为设置了主键,InnoDB会直接使用该主键作为索引列
  • 如果没有设置主键,InnoDB会从数据列中选出值唯一的作为索引列,如果没有找到值唯一的,会维护一个隐藏列作为索引列,而这样比较损耗性能。

为什么推荐使用自增整型作为主键而不是UUID

  • 整型占用磁盘空间更少,查询匹配更快
  • 自增主键与索引节点递增有序吻合,在新增主键的时候,不会因为乱序而导致叶子节点频繁断裂重组而降低性能

执行计划

知识点

执行计划,就是 一条SQL语句在数据库中实际执行的步骤。也就是我们用 EXPLAIN 分析一条SQL语句时展示出来的那些信息。

EXPLAIN 命令是查看 查询优化器 是如何执行查询的,从它的查询结果中可以知道一条SQL语句每一步是如何执行的,都经历了些什么,有没有用到索引,哪些字段用到了什么样的索引,这些信息都是我们SQL优化的依据。

语法:explain select语句

在这里插入图片描述


执行计划中的列

explain 显示的每个列都有不同的含义:

列名含义
id表示查询中执行select子句的顺序。id值大的先执行,若id相等则从上往下优先执行。
select_type查询的类型,主要是用于区分普通查询、联合查询、子查询等复杂的查询。
table表明对应行正在访问的是哪个表。
partitions查询涉及到的分区。
type查询数据时使用的索引类型。
possible_keys查询可以使用的索引。如果使用的是覆盖索引,则不会显示;
key实际使用的索引,如果为NULL,则没有使用索引。
key_len查询中使用的索引的字节数(最大可能长度),并非实际使用长度,理论上长度越短越好。
ref显示该表的索引字段关联的字段。
rows大致估算出找到所需行所需要读取的行数。
filtered返回结果的行数占读取行数的百分比,值越大越好。
Extra额外信息,十分重要。

type:查询数据时使用的索引类型,性能由高到底排列如下:

  • system,表中只有一行记录,比如系统表;
  • const,通过索引一次命中,匹配一行数据;例如 主键置于where列表中:
    • select * from stu where id = 1;
  • eq_ref,唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配;
    • select A.* from A,B where A.id=B.id
  • ref,非唯一性索引扫描,返回匹配某个单独值的所有行,用于=、<或>操作符带索引的列;
    • select * from stu where teacher_id = 123;
  • range,只检索给定范围的行,使用一个索引来选择行,一般用于between、<、>;
    • select * from stu where id between 1 and 10
  • index,只遍历索引树;跟all差不多,只不过index查的字段是索引。
    • select id from stu;
  • all,全表扫描;
    • select * from stu;

extra常见的值如下:

  • using filesort,MySQL会对数据使用一个外部索引排序,而不是按照表内索引顺序进行读取,常见于order by。若出现该值,则应优化SQL语句;
  • using temporary,使用临时表缓存中间结果,常见于order by和group by。若出现该值,则应优化SQL;
  • using index,表示select操作使用了覆盖索引,避免了访问表的数据行;
  • using where,使用了where过滤;
  • using join buffer,使用连接缓存;
  • distinct,发现第一个匹配后,停止搜索更多的行;

MySQL的执⾏计划怎么看/explain

  • 我们可以通过explain关键字去**查看SQL执行的顺序以及分析查询语句的性能瓶颈,**主要看的列有type、key_len、extra
  • type
    • 会显示关联类型,执行效率排序为system>const>eq_ref>ref>range>index>all,system和const基本不进行优化,只要优化到range就可以了
  • key_len
    • 会显示索引列占用的字节数,可以算出具体走了哪些索引列,比如int 占用4字节,tinyint 占用1字节,如果字段允许为空,会单独占用1字节来判断是否为空
  • extra
    • 会显示额外信息
    • Using index 表示使用了普通索引和覆盖索引
    • Using where表示使用where进行过滤
    • Using temporary表示使用临时表,查询效率不高,需要进行优化
    • Using filesort表示使用文件排序,表示order by 没有走索引,需要让它走左前缀原则
      • 文件排序原理就是 MySQL会在内存中,开辟一小块内存sort_buffer, 如果需要排序的数据内存小于sort_buffer,那么就会直接在sort_buffer中排序,如果需要排序的数据内存大于sort_buffer就会创建一个临时文件暂存,然后再从临时文件中把数据加载到sort_buffer进行排序。
      • 对于排序主要分为2种
        • 单路排序
          • 将结果集加入到内存中,然后order 返回结果,占用的内存空间较大
        • 双路排序
          • 将需要排序的字段和主键ID加入到内存中去,然后order再根据主键ID回表查询,然后返回结果占用内存小。
    • Using join buffer表示使⽤NLJ算法,如果有显示Blocked Nested Loop Join表示使⽤BNL算法
      • 这两种算法主要针对join关联的进⾏选择的,如果被驱动表的关联字段有索引,就会选择NLJ算
        法,否则就会选择BNL算法
      • NLJ算法,嵌套循环连接算法,就是每次从驱动表取出⼀⾏加载到join_buffer中,然后通过关
        联字段与被驱动表进⾏⽐较,过滤出符合条件的结果集
      • BNL算法,基于块的循环连接算法,就是每次将驱动表的数据加载到join_buffer中,被驱动表
        每次取出⼀⾏数据与join_buffer中数据进⾏⽐较,过滤出符合条件的结果集
      • 这⾥要注意的是join_buffer默认只有256kb,如果⼀次加载到join_buffer数据过多会分批去进⾏
        处理
      • 为什么被驱动表的关联字段没有索引会选择BNL算法
        • 因为⼤量数据在内存中做数据过滤会⽐磁盘快很多,MySQL会选择BNL算法来优化
      • 对于join关联的优化原则就是⼩表驱动⼤表,⽐如inner join,MySQL的优化器会帮忙选择⼩表
        去驱动⼤表,left/right join需要我们⾃⼰指定,left join⼩表在左、⼤表在右,right join反之
    • 整体上看尽量让查询、排序的结果集使⽤上索引

SQL的执行流程

  • 客户端通过连接器进行权限验证,然后先去判断有没有开启缓存,如果开启了,先从缓存中查询数据返回,如果没有开启就通过词法分析器,去词法分析,然后通过优化器去进行SQL优化,然后再通过执行器去选择存储引擎去进行SQL执行。

MySQL 中的 3 大日志是指哪些?

  • 二进制日志( binlog )和事务日志(包括redo log 和 undo log )
  • binlog
    • binlog 用于记录数据库执行的写入性操作(不包括查询)信息,以二进制的形式保存在磁盘中。
    • binlog使用场景:主从复制数据恢复
      • **主从复制 :**在 Master 端开启 binlog ,然后将 binlog发送到各个 Slave 端, Slave 端重放 binlog 从而达到主从数据一致。
      • **数据恢复 :**通过使用 mysqlbinlog 工具来恢复数据。
  • redo log
    • 只记录事务对数据页做了哪些修改
    • redo log使用场景: 适用于崩溃恢复(crash-safe)
    • redo log的大小是固定的。
    • 保证事务中的持久性
  • undo log
    • undo log主要记录了数据的逻辑变化
      • 比如一条 INSERT 语句,对应一条DELETE 的 undo log
      • 每个 UPDATE 语句,对应一条相反的 UPDATEundo log
      • 这样在发生错误时,就能回滚到事务之前的数据状态。
    • 原子性 底层就是通过 undo log 实现的

MVCC

MVCC机制

  • MVCC就是通过保存数据多个版本来实现并发控制,MVCC在RR(可重复读)和RC(读已提交)级别都实现了,主要是为了提升并发性能,以及对于高并发场景MVCC比行级锁更有效并且开销更小。
  • 它内部维护一个undo日志版本链,在事务第一次查询的时候,会生成一个read_view(一致性视图),当后面进行数据查询的时候,会与read_view按照一定规则匹配,然后返回对应的数据

全称Multi-Version Concurrency Control,即多版本并发控制,主要是为了提高数据库的并发性能。

MVCC

MVCC(Mutil-Version Concurrency Control),就是多版本并发控制。MVCC是一种并发控制的方法,实现对数据库的并发访问。主要适用于Mysql的RC, RR隔离级别。

**好处:**提高数据库并发性能,使用不加锁,非阻塞方式去处理读-写冲突。

MVCC解决了不可重复读。MVCC+临建锁解决了幻读

特点:

  1. 每行数据都存在一个版本,每次数据更新时都会更新版本号。
  2. 修改时Copy出当前版本到当前事务,各个事务之间无干扰。
  3. 保存时比较版本号,如果成功,则覆盖原记录(commit);失败则放弃覆盖(rollback)

当前读和快照读

当前读:读取的一定是数据的最新版本,读取时还要保证其他并发事务不能修改当前数据,会对读取的数据进行加锁

  • select ... lock in share mode(共享锁)、 select ... for updateupdateinsertdelete(排他锁) 这些操作都是当前读

快照读:读取的不一定是数据的最新版本,可能是历史版本的记录。比如:不加锁的select操作。

  • 快照读前提是隔离级别小于串行级别,串行级别下的快照读会退化成当前读;

实现原理

它的实现原理主要是依赖记录中的 3个隐藏字段undologReadView 来通过保存数据在某个时间点的快照。

隐藏字段

每行记录上都会包含几个用户不可见的字段:DB_TEX_IDDB_ROW_IDDB_ROLL_PTR

  • DB_TRX_ID:创建/最后一次修改该记录的事务ID
  • DB_ROW_ID:隐含的自增ID,如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引。
  • DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本。配合undolog使用。
undolog

存放每行数据的版本链。链首是最新的历史记录。

执行流程如下:

【1】有个事务1在persion表插入了一条新记录:name为Jerry, age为24岁,隐式主键是1,事务ID假设为1,回滚指针因为是第一条新记录所以为NULL。

在这里插入图片描述

【2】现在来了一个事务2对该记录的name做出了修改,改为Tom

  • 在事务2修改该行数据时,数据库会先对该行加排他锁。
  • 然后把该行数据拷贝到undolog中作为旧记录,即在undolog中有当前行的拷贝副本
  • 拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务ID为当前事务2的ID, 假设为2。将该行的回滚指针指向undolog中的最新的副本记录。
  • 事务提交后,释放锁

在这里插入图片描述

undolog里面的内容是二进制的,这里为了方便理解写成记录的形式。

并且undolog不仅可以配合MVCC使用,其本身还会作为逻辑日志,保证原子性。

Read View

Read View就是事务进行快照读操作的时候生成的读视图(Read View),在该事务执行快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)

当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,用它来判断当前事务能够看到哪个版本的数据。即可能是当前最新的数据,也有可能是该行记录的undolog里面的某个版本的数据。

Read View可简化成下面三部分:

  • trx_list:Read View生成时系统正活跃的事务ID的集合
  • up_limit_id:记录trx_list列表中事务最小的ID
  • low_limit_id:ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1

如何知道ReadView展示的是哪个版本的数据呢?需要遵循一个可见性算法。

  1. 首先比较DB_TRX_ID < up_limit_id, 如果小于,则当前事务能看到DB_TRX_ID 所在的记录,如果大于等于进入下一个判断
  2. 接下来判断 DB_TRX_ID ≥ low_limit_id , 如果大于等于则代表DB_TRX_ID 所在的记录是在Read View生成后才出现的,那对当前事务肯定不可见。如果小于则进入下一个判断
  3. 判断DB_TRX_ID 是否在活跃事务之中,如果在,则代表Read View生成时刻,这个事务还在活跃,还没有Commit,即修改的数据当前事务也是看不见的;如果不在,则说明,这个事务在Read View生成之前就已经Commit了,即修改的结果当前事务是能看见的

把要被修改的数据的最新记录中的DB_TRX_ID(当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出UndoLog中的DB_TRX_ID再比较,即遍历UndoLog的DB_TRX_ID,直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的内容。


什么时候会进行会形成读视图?每次快照读都会形成一个读视图吗?

答:RC隔离级别下,每次快照读都会生成一个readview;RR隔离级别下,第一次快照读时会生成一个readview,之后的读操作都是使用这个readview,直到当前事务结束。但是,如果在事务的中途执行了当前读(增删改或加锁的读),则会出现幻读,即之前的readview失效。

BufferPool缓存机制

  • 我们对于数据库的增删改查都是**直接操作BufferPool的,**当我们执行一条修改语句的时候,会经过Server调用具体存储引擎,引擎将相关的数据页加载到BufferPool中,然后会记录undo日志,方便事务失败之后进行回滚操作,然后更新BufferPool, 然后记录redo日志,方便MySQL宕机之后,从redo日志中,将BufferPool 中数据进行恢复,然后将修改操作记录到binlog中方便数据归档,然后redo日志记录提交标记,此时redo日志和binlog日志数据一致,其中redo日志采用顺序IO进行记录写入,效率堪比内存,对于数据持久化,MySQL会开启后台线程,定时从BufferPool中刷到磁盘。
  • 对于BufferPool缓存机制,主要采用的是LRU算法,一般将BufferPool设置为物理内存的60%左右即可
  • 为什么MYSQL不直接操作磁盘而是设置BufferPool缓存机制来执行SQL
    • 对于磁盘的随机读写不如直接操作内存,更新内存的性能是极高的,可以抗下高并发
    • 这套机制能保证每个更新请求,都是直接操作BufferPool,然后顺序IO写日志文件,同时,还能保证异常情况的数据一致性

对慢SQL怎么优化

  • 首先,分析SQL语句,看看是否加载了额外的数据,比如额外的列,对于SQL语句进行重写
  • 查看SQL的执行计划,看看索引列有没有命中,没有命中对SQL语句进行调整
  • 对于SQL无法继续进行优化,看看是不是数据量的问题,如果对查询效果要求较高,可以采用垂直分表或者水平分表

慢查询日志

  • 慢查询日志:是一个日志记录。当一个sql的查询时间超过了一个阈值long_query_time 时,该sql语句会被记录到慢查询日志中。long_query_time默认为10秒

  • 查看慢查询日志状态:show variables like ‘%slow_query_log%’; (默认是关闭的)

  • 打开慢查询日志:set global slow_query_log = 1; (只针对当前数据库生效,重启mysql后又自动关闭了)

  • 查看阈值:show variables like ‘%long_query_time%’;

  • 设置阈值:set global long_query_time = n; 需要重新打开窗口登录mysql生效。

线上百万数据如何添加索引

  • 百万数据为什么需要加索引,主要是因为打开页面很长时间看不到数据,加索引主要是为了提升查询的效率
  • 而对于线上百万数据,直接添加索引会导致锁表,所以不建议直接对百万数据加索引,可以这么操作
    • 导出原表数据
    • 创建与表结构一致的新表,先在新表上添加索引
    • 将原表的数据导入新表
    • 修改新表的表名为原表名

百万级别或以上的数据如何删除

因为索引的维护很耗费时间,所以可以先删除索引,然后再删除数据。如果先删数据的话,则每次删一条就要维护一次索引,这样会增加不必要的时间。

MySQL中的锁有哪些

  • 基于锁的属性分为独占锁和共享锁

    • 独占锁(写锁)
      • 当一个事务给数据加上写锁的时候,其他请求不允许再为数据加任何锁,想要进行操作需要等写锁释放之后
      • 独占锁的目的是在修改数据的时候,不允许其他人同时进行读和修改,避免了脏读和脏写的问题
    • 共享锁(读锁)
      • 当一个事务给数据加上读锁的时候,其他事务只能对数据加读锁,而不能加写锁,直到读锁都释放完之后,才允许其他事务加写锁
      • 共享锁的目的:支持并发读,但是读时不允许修改,避免不可重复读的问题
  • 基于锁的粒度可以分为表锁、行锁、记录锁、间隙锁、临建锁

    • 表锁

      • 每次操作都会锁住整张表,粒度大,加锁简单,容易冲突,并发度低,一般用于数据迁移
    • 行锁

      • 每次操作都会锁住表的某行或者多行记录,粒度小,加锁比表锁复杂,不容易冲突,支持更高的并发
      • 行锁是建立在索引的基础上的,没有索引或者索引失效时,InnoDB的行锁变表锁,比如普遍索引数据重复率过高会导致索引失效,比如普通索引数据重复率过高会导致索引失效,行级锁升级为表锁
    • 记录锁

      • 行锁的一种,每次操作会锁住表的某行记录,避免不可重复读以及脏读的问题
    • 间隙锁

      • 行锁的一种,锁住的是两个值之间的空隙,遵循左开右闭原则,为了解决幻读问题
      • 间隙锁在RR级别才会生效,在RC级别会失效
    • 临建锁

      • 行锁的一种,基于记录锁和间隙锁实现的,会锁住查询到记录、记录之间的间隙以及记录相邻的下一个区间,加上临建锁在范围内的数据不能被修改,可以避免不可重复读、脏读、幻读等问题

MySQL如何避免死锁

  • 造成死锁的原因
    • 互斥条件:一个资源只能被一个线程使用
    • 请求和保持条件:一个线程在阻塞等待某个资源的时候,不释放占用资源
    • 不剥夺条件:一个线程已获得的资源,还未完全使用完,不能被其他线程强行剥夺。
    • 循环等待条件:多个线程形成头尾相接的循环等待资源关系
  • 前面三个条件是满足锁的条件,如果想要避免死锁,只要避免线程之间循环等待就行
  • MySQL避免死锁
    • 尽量让表中数据查询通过索引来完成,避免索引失效,导致行锁升级为表锁
    • 合理设计索引、尽量缩小锁的范围
    • 尽量控制事务的大小,减少一次事务锁定的资源数量,缩短锁定资源的时间
    • 如果一条SQL语句涉及事务加锁操作、尽量将其放到整个事务的最后执行
    • 尽可能使用 低级别隔离级别

解释下隔离级别与锁的关系

  • 在RU级别下,读不需要共享锁,这样就不跟被修改数据上的独占锁冲突
  • 在RC级别下,读需要加共享锁,SQL执行完之后才会释放
  • 在RR级别下,读需要加共享锁,要等事务结束后才会释放
  • 在S级别下,会锁住整个范围,等事务结束了才会释放

MySQL主从复制原理

  • 主节点的binlog线程会记录数据修改的操作,然后写入binlog,从节点会通过IO线程拉取主节点的binlog到自己的relay log中,然后执行relay log中的语句进行数据同步

主从复制

主机master数据更新后根据配置和策略自动同步到从机slaver,Master为主,Slave为主。mysql默认采用异步复制的方式,这样从节点就不用一直访问主服务器来更新自己的数据。

原理如下:

在这里插入图片描述

  • master将数据的增删改的sql语句 记录在二进制文件日志binlog
  • slave会在一定时间间隔内对master的binlog进行探测,看其是否发送改变。如果发生改变则使用一个IO线程去获取binlog。同时master为每个从机的IO线程开启一个dump线程,用于传输binlog。
  • slave获取到binlog后会将其内容写到本地的 relay log 中,然后使用一个sql线程 读取relay log中的内容并执行,使本地数据跟 master的数据保持一致。
  • 最后slave的 IO线程 跟 sql线程 进入睡眠状态,等待下一次被唤醒。

为什么要主从同步

  • 读写分离,让数据库支撑更高的并发
  • 数据备份,保证数据的安全

读写分离要怎么做

  • 因为要保证主从节点之间的数据一致性,写操作只能针对主节点,读操作可以主或从节点完成

分表分库

  • 分库节省单个服务器的IO压力,分表降低单表数据量,提升SQL查询效率

  • 分表分库主要是为了解决数据量过大而导致性能降低的问题,可以通过分库或者分表来提升数据库的性能,在阿里规范中有说道:数据量达到500w或者占用磁盘空间达到2G就需要进行分表分库,常见的分库分表组件有MyCat和ShardingSphere,对于数据分片分为垂直分片和水平分片

    • 垂直分片主要是从业务角度将表中数据拆分到不同的库中来解决数据文件过大的问题,但是本质上不能解决数据查询效率慢的问题
    • 水平分片主要是从数据库角度出发,将表中数据拆分到多个表或库中,能解决数据查询效率慢的问题,但是存在很多问题,如分布式事务
  • 对于分片策略主要有取模、按范围、按时间

    • 取模就是对某个值进行取模,数据分配比较均匀,但是扩容不方便
    • 按范围就是指定一个区间进行分片,数据分配不均匀,但是扩容方便
    • 按时间就是将数据在某时间的冷热情况进行分配,方便热数据区分。
  • 分库分表的执行流程为SQL解析->查询优化->SQL路由->SQL改写->SQL执行->结果并归

  • 分库分表会遇到很多问题比如事务一致性问题、跨节点关联问题。跨节点分页以及排序问题、主键避重、公共表处理

    • 事务一致性问题就是原本单库能通过事务机制保证数据一致性,但是多库下会将数据分散到不同库中,存在分布式事务的问题
    • 跨节点关联问题就是多库下会将数据分散到不同的库中就无法进行关联查询了,这时可能就需要考虑单库分页和排序后再汇总分页排序一次,会占用大量内存
    • 主键避重就是单库下可以通过自增ID保证主键唯一,但多计划出现重复,这时可以使用分布式ID去解决,比如雪花算法
    • 公共表处理就是多库下对于一些基本变化不大的表都需要进行数据维护工作

分页

limit (page-1)*pageSize, pageSize;

为什么要使用视图?什么是视图?

视图是一个虚拟表,是动态生成的,它只存放视图的定义,不存放数据。因此可以提高复杂sql语句的复用性和数据的安全性

特点:

  1. 视图的建立和删除不影响基本表
  2. 对视图内容的更新直接影响基本表
  3. 当视图来自多个基本表时,不允许添加和删除数据。

数据库三大范式

通俗易懂解释数据库第一二三范式_Wilbur。。的博客-CSDN博客_数据表范式第一二三范式

  • 第一范式(1NF): 字段是最小的的单元,不可分割

    • 数据库表中的字段都是单一属性的,不可再分
    • (在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库)。
    • 举例:
      • 学生信息组成学生信息表,有姓名、年龄、性别、学号等信息组成。姓名不可拆分吧?所以可以作为该表的一个字段。但我要说这个表要在国外使用呢?人家姓和名要分开,都有特别的意义,所以姓名字段是可拆分的,分为姓字段和名字段。
      • 简单来说,一范式是关系数据库的基础,但字段是否真的不可拆分,根据你的设计目标而定。
  • 第二范式(2NF):满足1NF,表中的字段必须完全依赖于全部主键而非部分主键 (一般我们都会做到)。

    • 第二范式就是要有主键,要求其他字段都依赖于主键
    • (数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖,即符合第二范式)
    • 为什么要有主键?
      • 没有主键就没有唯一性,没有唯一性在集合中就定位不到这行记录,所以要主键
    • 其他字段为什么要依赖于主键?
      • 因为不依赖于主键,就找不到他们。更重要的是,其他字段组成的这行记录和主键表示的是同一个东西,而主键是唯一的,它们只需要依赖于主键,也就成了唯一的。
    • 举例:
      • 学生信息组成学生表,姓名可以做主键么?不能! 因为同名的话,就不唯一了,所以需要学号这样的唯一编码才行。那么其他字段依赖于主键是什么意思?就是“张三”同学的年龄和性别等字段,不能存储别人的年龄性别,必须是他自己的,因为张三的学号信息就决定了,这行记录归张三所有,不能给无关人员使用。
  • 第三范式(3NF):满足2NF,非主键外的所有字段必须互不依赖。

    • 第三范式就是要消除传递依赖,即“消除冗余”(在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合3NF)
    • 简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。
    • 举例:
      • 例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在的员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。

exists 跟 in 的区别

exists返回true或false,它常常和子查询配合使用,用法如下:

select a.* from A a where exists(select 1 from B b where a.id=b.id)
等价于:
for select a.* from A a
	for select 1 from B b where a.id=b.id

exists语句会拿着A表的记录去B表匹配,匹配成功就加入到结果集。exists并不会去缓存子查询的结果集,因为这个结果集并不重要,只需要返回真假即可。


in()语句只会执行一次,它查出B表中的所有id字段并且缓存起来,之后,拿着B表的记录去A表匹配,匹配成功则加入结果集。

select a.* from A a where a.id in (select id from B)
等价于:
for select id from B
	select a.* from A a where a.id=b.id

根据小表驱动大表的原则,最外层表的数据越小,则效率越高。所以,有以下结论:

  1. in()适合B表比A表数据量小的情况

  2. exists()适合B表比A表数据量大的情况

  3. 当A表数据与B表数据一样时,in与exists效率差不多,可任选一个使用.

not in 和not exists

如果字段中没有null值,则两者效率差不多。

如果字段中存在null值,则使用not in时,索引失效,内外表都进行全表扫描;而not exists的子查询依然能用到表上的索引。

Mysql基础架构

  • 优化器:在表里面有多个索引的时候,决定使用哪个索引或者表的连接顺序

  • 执行器:具体执行sql,执行之前会判断一下你对这个表有没有执行查询的权限

在这里插入图片描述

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java-You

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值