mysql数据库学习基础知识整理

主要摘自咕咕评测机:https://blog.csdn.net/doublekillyeye

一、mysql数据库引擎:

 

 总结:innoDB和myisam主要区别

1.myisam不支持事务,innoDB支持事务

2.innoDB支持外键

3.myisam只有表锁,而innodb有表锁、页锁、行级锁(默认行级)

4.innodb支持mvcc

二、ACID原则

原子性:数据库数据要么都成功,要么全部失败。InnoDB引擎通过undolog保证rollback的时候能找到之前的数据(也就是前面提到的mvcc)

一致性:数据库数据都是一致的,从一个状态转移到另一个状态,不会出现中间的状态。通过故障恢复(crash recovery)和double write buffer机制来保证数据库数据的一致性。

隔离性:隔离性指多个事务可以同时对数据进行修改而不互相影响。(默认是RR(可重复读)隔离级别)

永久性:事务提交后被持久化到永久存储.InnoDB通过redolog保证已经commit的数据一定不会丢失,永久保存在数据库中。

三、并发带来的数据库问题

第一类数据丢失:两个事务操作同一数据,在一个事务提交后另一个事务回滚了导致这个事务提交的数据丢失了。

脏读:一个事务可以读取另外一个事务在未提交事务之前的变更的数据。(读取到修改但未提交的数据)

第二类数据丢失:两个事务操作同一数据,一个事务提交后另外一个事务也提交了,导致先提交的事务的数据被覆盖。

不可重复度:也就是一个事务多次读取相同行数据得到不同的结果。(读到别事务已经提交的数据)

幻读:事务对同一个表前后查询的数据行数不同(和不可重复读角度不同,不可重复读角度在于同一个数据的不同)(读取到新增且已提交的数据)

四、数据库隔离级别(解决并发问题)

读未提交RU:可以读取未提交的数据,未提交的数据称为脏数据,所以又称脏读。此时:幻读,不可重复读和脏读均允许;(此时两种数据丢失也不能解决)

读已提交RC:只能读取已经提交的数据;此时:允许幻读和不可重复读,但不允许脏读,所以RC隔离级别要求解决脏读;(此时可以解决第一类更新)

可重复读RR:同一个事务中多次执行同一个select,读取到的数据没有发生改变;此时:允许幻读,但不允许不可重复读和脏读,所以RR隔离级别要求解决不可重复读;(同时也能解决第二类数据丢失)(MySQL InnoDB 存储引擎的默认支持的隔离级别

串行化:幻读,不可重复读和脏读都不允许,所以serializable要求解决幻读;( 分布式事务下一般会使用到

即分别解决(未解决以上任意一种问题)(第一类、脏读)(第二类、不可重复读)(幻读)

  • 不可重复读的重点是修改:同样的条件的select, 你读取过的数据, 再次读取出来发现值不一样了
  • 幻读的重点在于新增或者删除:同样的条件的select, 第1次和第2次读出来的记录数不一样

五、锁机制(建立在Innodb,myISAM,BDB上讨论)

表级锁:开销小,加锁快,但是粒度大冲突多,并发读最低;myisam和bdb,innodb都支持,分为共享锁 和 排他锁;不会出现死锁(一次性获得所需的全部锁,要么全部满足,要么全部等待)。

页级锁:页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁。

行级锁:只有innodb支持,行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁

六、锁分类

1)表级锁

  1. 共享锁(S锁):如果一个事务T对一个数据A加上共享锁后,其他事务只能对数据A再次加共享锁,不能加入排他锁。加上共享锁后,只能读取数据,不能修改数据。
  2. 排他锁(X锁):如果一个事务T对一个数据A加上排他锁后,其他事务不能对数据A进行任何类型的封锁。加上排他锁后,既能读取数据,也能修改数据。
  3. 意向共享锁(IS):表示事务准备给数据行加上共享锁,在一个数据行加入共享锁之前必须获得该表的意向共享锁(IS)。
  4. 意向排他锁(IX): 表示事务准备给数据行加上排他锁,在一个数据行加上排他锁之前必须获得该表的意向排他锁(IX)。

2)行级锁

InnoDB的行级锁是基于索引实现的,如果查询语句未命中任何索引,那么InnoDB会使用表级锁.。

使用锁的时候,如果表没有定义任何索引,那么InnoDB会创建一个隐藏的聚簇索引并使用这个索引来加记录锁。

此外,不同于MyISAM总是一次性获得所需的全部锁,InnoDB的锁是逐步获得的,当两个事务都需要获得对方持有的锁,导致双方都在等待,这就产生了死锁。 发生死锁后,InnoDB一般都可以检测到,并使一个事务释放锁回退,另一个则可以获取锁完成事务,我们可以采取以上方式避免死锁:

  通过表级锁来减少死锁产生的概率;
  多个程序尽量约定以相同的顺序访问表(这也是解决并发理论中哲学家就餐问题的一种思路);
  同一个事务尽可能做到一次锁定所需要的所有资源。

 InnoDB行锁模式兼容性列表

请求锁模式

   是否兼容

当前锁模式

XIXSIS
X冲突冲突冲突冲突
IX冲突兼容冲突兼容
S冲突冲突兼容兼容
IS冲突兼容兼容兼容

间隙锁行级,使用范围条件时;对范围内不存在的记录加锁,一是为了防止幻读、二是为了满足回复和负责的需要。当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁!

MyISAM中是不会产生死锁的,因为MyISAM总是一次性获得所需的全部锁,要么全部满足,要么全部等待。而在InnoDB中,锁是逐步获得的,就造成了死锁的可能。

在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定 这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。 在UPDATE、DELETE操作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key locking。

当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。

发生死锁后,InnoDB一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。

锁竞争

一个进程请求某个 MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL如何处理呢?答案是写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前!这是因为MySQL认为写请求一般比读请求要重要。这也正是MyISAM表不太适合于有大量更新操作和查询操作应用的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。这种情况有时可能会变得非常糟糕!幸好我们可以通过一些设置来调节MyISAM 的调度行为。
但是可以进行设置。

只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

七、多版本控制

在innodb中,主要是通过readview技术来实现,对于查询到的每条数据记录,都会用readview来判断一下当前这行是否可以被当前事务看到,如果可以,则输出,否则就利用undolog来构建历史版本,再进行判断,直到记录构建到最老的版本或者可见性条件满足。

最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行。

MVCC只在 RC 和 RR两个隔离级别下工作。其他两个隔离级别和MVCC不兼容,RU总是读取最新的记录行,而不是符合当前事务版本的记录行;Serializable 则会对所有读取的记录行都加锁。

RR通过行级锁+MVCC实现 ,正常读的时候不加锁,写的时候加锁。而 MVCC 的实现依赖:隐藏字段(三个)、Read View、Undo log

InnoDB存储引擎在每行数据的后面添加了三个隐藏字段

 1. DB_TRX_ID(6字节):表示最近一次对本记录行作修改(insert | update)的事务ID。至于delete操作,InnoDB认为是一个update操作,不过会更新一个另外的删除位,将行表示为deleted。并非真正删除。

        2. DB_ROLL_PTR(7字节):回滚指针,指向当前记录行的undo log信息

        3. DB_ROW_ID(6字节):随着新行插入而单调递增的行ID。理解:当表没有主键或唯一非空索引时,innodb就会使用这个行ID自动产生聚簇索引。如果表有主键或唯一非空索引,聚簇索引就不会包含这个行ID了。这个DB_ROW_ID跟MVCC关系不大。
Read View 结构(读视图)

Read View主要是用来做可见性判断的, 里面保存了“对本事务不可见的其他活跃事务”。

① low_limit_id目前出现过的最大的事务ID+1,即下一个将被分配的事务ID(还未分配的最小事务ID,并不是活跃事务列表中最大的事务ID)

 up_limit_id活跃事务列表trx_ids中最小的事务ID,如果trx_ids为空,则up_limit_id 为 low_limit_id。

③ trx_ids:Read View创建时其他未提交的活跃事务ID列表。意思就是创建Read View时,将当前未提交事务ID记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。不包括当前事务自己和已提交的事务

 ④ creator_trx_id:当前创建事务的ID,是一个递增的编号

Undo log

 Undo log中存储的是老版本数据,当一个事务需要读取记录行时,如果当前记录行不可见,可以顺着undo log链找到满足其可见性条件的记录行版本。
        大多数对数据的变更操作包括 insert/update/delete,在InnoDB里,undo log分为如下两类:
        ①insert undo log : 事务对insert新记录时产生的undo log, 只在事务回滚时需要, 并且在事务提交后就可以立即丢弃。
        ②update undo log : 事务对记录进行delete和update操作时产生的undo log,不仅在事务回滚时需要,快照读也需要,只有当数据库所使用的快照中不涉及该日志记录,对应的回滚日志才会被purge线程删除。

Purge线程:
    为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下旧记录的deleted_bit,并不真正将旧记录删除。
    为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。
    purge线程自己也维护了一个read view,如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。

摘自:https://blog.csdn.net/Waves___/article/details/105295060#1.2%E3%80%81Read%20View%20%E7%BB%93%E6%9E%84

比较算法

 1. 如果 trx_id < up_limit_id, 那么表明“最新修改该行的事务”在“当前事务”创建快照之前就提交了,所以该记录行的值对当前事务是可见的。跳到步骤5。

        2. 如果 trx_id >= low_limit_id, 那么表明“最新修改该行的事务”在“当前事务”创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤4。

        3. 如果 up_limit_id <= trx_id < low_limit_id, 表明“最新修改该行的事务”在“当前事务”创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表trx_ids进行查找(源码中是用的二分查找,因为是有序的):

            (1) 如果在活跃事务列表trx_ids中能找到 id 为 trx_id 的事务,表明在“当前事务”创建快照前,“该记录行的值”被“id为trx_id的事务”修改了,但没有提交;或者在“当前事务”创建快照后,“该记录行的值”被“id为trx_id的事务”修改了(不管有无提交);这些情况下,这个记录行的值对当前事务都是不可见的,跳到步骤4;

            (2)在活跃事务列表中找不到,则表明“id为trx_id的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见,跳到步骤5。

        4. 在该记录行的 DB_ROLL_PTR 指针所指向的undo log回滚段中,取出最新的的旧事务号DB_TRX_ID, 将它赋给trx_id,然后跳到步骤1重新开始判断。

        5. 将该可见行的值返回。

  • 当前读
    像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

  • 快照读
    不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

总结:RC与RR的主要区别在于RC会在每次查询前都进行readview,所以会使得事务读到别事务已经提交的数据,而RR只会在首次使用select前进行一次readview然后在整个事务中使用。

RR可以解决一部分幻读(普通select)但是对于当前读即select ... for update等语句,这时候,事务A是当前读,每次语句执行的时候都是获取的最新数据。也就是说,在只有MVCC时,A先执行 select ... where nid between 1 and 10 … for update;然后事务B再执行  insert … nid = 5 …;然后 A 再执行 select ... where nid between 1 and 10 … for update,就会发现,多了一条B insert进去的记录。这就产生幻读了,所以单独靠MVCC并不能完全防止幻读。(采用正常读的时候不加锁,写的时候加锁。)

 只有普通select语句才会创建快照,select ... lock in share mode,select ... for update不会,update、delete、insert语句也不会,因为它们都是 当前读,会对访问的数据加锁。

八、树(平衡二叉树,红黑树,b树,b+树、字典树)

平衡二叉树:一般是用平衡因子差值决定并通过旋转来实现,左右子树树高差不超过1,那么和红黑树比较它是严格的平衡二叉树,平衡条件非常严格(树高差只有1),只要插入或删除不满足上面的条件就要通过旋转来保持平衡。由于旋转是非常耗费时间的。我们可以推出AVL树适合用于插入删除次数比较少,但查找多的情况。

b树与b+树:

B树:(B-树)
根节点:[1,m-1]个关键字
其他节点:[m/2向上上取整-1, m-1]个关键字

每个结点关键字从左到右递增
每个结点的左孩子都比它小,右孩子都比它大.

每个叶子节点都在同一层。

B+树

和B树基本等价的.
除此之外B+树还有以下的要求:
(只有叶子结点存储数据)
内部节点用做索引

它们特点是一样的,是多路查找树,一般用于数据库系统中,因为他们分支多所以树的层数少,在数据操作都是在内存中操作的所以在mysql中数据需要先通过IO把数据从磁盘读取到内存中而这个操作是非常耗时的所有多路树可以使得过程的IO次数减少,而mysql中一般采用b+树作为索引的数据结构,因为每次IO读取的数据是有限的而b树中节点都是存放数据的,而b+树只有叶节点存放数据,所以在达到数据的过程中如何在层数较少就能找到数据那b树的速度是比b+树快的,但是当层数大时反而会造成很大的IO开销,而且效率不稳定,也就是平均io次数会较b+树多,而b+树的非页节点只保存索引,索引b+树一次IO能得到更多的索引,减少了IO的开销。

1 b+树的数据都集中在叶子节点。分支节点 只负责索引。  b树的分支节点也有数据 。 b+树的层高 会小于 B树 平均的Io次数会远大于 B+树

2b+树更擅长范围查询。叶子节点 数据是按顺序放置的双向链表。  b树范围查询只能中序遍历。

3索引节点没有数据。比较小。b+树可以把索引完全加载至内存中。

4.除IO次数外还有一个突出优势遍历更加方便(同上2):B+树的叶子结点都是相链的,因此对整棵树的遍历只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

总结:为什么MySQL选择B+树做索引

1、 B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。

2、B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

3、B+树更便于遍历:由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。

4、B+树更适合基于范围的查询:B树在提高了IO性能的同时并没有解决元素遍历的我效率低下的问题,正是为了解决这个问题,B+树应用而生。B+树只需要去遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作或者说效率太低。(B+树由于所有数据都在叶子结点不用跨层,同时由于有链表结构,只需要找到首尾,通过链表就能把所有数据取出来了)

红黑树
(1)根节点是黑色
(2)叶子节点都是黑色的null节点
(3)红色节点的孩子必须时黑色
(4)从任意节点到叶子节点的路径中的黑色节点都是相同的

数据库索引为什么不用红黑树呢?

AVL 树和红黑树这些二叉树结构的数据结构可以达到最高的查询效率这是毋庸置疑的。

但操作系统读写磁盘的基本单位是扇区,而文件系统的基本单位是簇(Cluster)。

也就是说,磁盘读写有一个最少内容的限制,即使我们只需要这个簇上的一个字节的内容,我们也要把一整个簇上的内容读完。

红黑树并不能填满一个簇上的所有内容,多余的内容被浪费,还非常耗时(io次数较b+数多)

而且红黑树因为要保证红色节点的节点必需是黑节点而且路径上的黑节点数必需相等所以最长链和最短链差了一倍,导致和效率非常不平均

同理如果使用AVL树(红黑树也是一种AVL树)

虽然较红黑树查询效率更稳定,但是比较还是二叉树,而且对树的平衡因子更严格所以在维护插入删除时需要更多的时间

磁盘IO代价主要花费在查找所需的柱面上,树的深度过大会造成磁盘IO频繁读写。根据磁盘查找存取的次数往往由树的高度所决定,所以,只要我们通过某种较好的树结构减少树的结构尽量减少树的高度,B树可以有多个子女,从几十到上千,可以降低树的高度。

9)字典树

字典树:又称前缀树,相同的前缀会在同一个链上。

九、索引

b+树索引和hash索引的差异

(1)HASH索引只用于使用 = 或 <=> 操作符的等式比较。如果一定要使用范围查询 的话,只能使用BTREE索引。

(2)优化器不能使用 Hash 索引来加速 order by 操作。

(3)使用 Hash 索引时 MySQL 不能确定在两个值之间大约有多少行。如果将一 个MyISAM表改为的 Hash 索引 memory 表,

会影响一些查询的执行效率。

(4)Hash索引只能使用整个关键字来搜索一行。

(5)如果哈希冲突会采用链地址法处理冲突,即如果多个列的哈希值相同,索引会以链表的方式存放多个记录指针到同一个哈希条目中去。而在查找时也需要遍历桶内的所有元素。

什么时候索引失效

1.有or必全有索引;
2.复合索引未用左列字段;
3.like以%开头;
4.需要类型转换;
5.where中索引列有运算;
6.where中索引列使用了函数;
7.如果mysql觉得全表扫描更快时(数据少);

索引类型

1.聚簇索引(聚集索引):聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。

 

聚集索引的优点

聚集索引的查询速度非常的快,因为整个B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。

聚集索引的缺点

  1. 依赖于有序的数据 :因为B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或UUID这种又长又难比较的数据,插入或查找的速度肯定比较慢。
  2. 更新代价大 : 如果对索引列的数据被修改时,那么对应的索引也将会被修改, 而且况聚集索引的叶子节点还存放着数据,修改代价肯定是较大的, 所以对于主键索引来说,主键一般都是不可被修改的。

2.非聚簇索引(非聚集索引):(除innodb的聚簇索引外都是)myisam的全都是。

特别的聚集索引和非聚集索引是相对innodb和myisam而讲的。

3.主键索引(Primary Key)

数据表的主键列使用的就是主键索引。

一张数据表有只能有一个主键,并且主键不能为null,不能重复。

在mysql的InnoDB的表中,当没有显示的指定表的主键时,InnoDB会自动先检查表中是否有唯一索引的字段,如果有,则选择该字段为默认的主键,否则InnoDB将会自动创建一个6Byte的自增主键。

4.二级索引(辅助索引,二级索引属于非聚集索引。)

二级索引又称为辅助索引,是因为二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。

唯一索引,普通索引,前缀索引等索引属于二级索引。

  1. 唯一索引(Unique Key) :唯一索引也是一种约束。**唯一索引的属性列不能出现重复的数据,但是允许数据为NULL,一张表允许创建多个唯一索引。**建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
  2. 普通索引(Index) :普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和NULL。
  3. 前缀索引(Prefix) :前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小, 因为只取前几个字符。
  4. 全文索引(Full Text) :全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6之前只有MYISAM引擎支持全文索引,5.6之后InnoDB也支持了全文索引。

非聚集索引的缺点

  1. 跟聚集索引一样,非聚集索引也依赖于有序的数据
  2. 可能会二次查询(回表) :这应该是非聚集索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。

非聚集索引一定回表查询吗(覆盖索引)?

非聚集索引不一定回表查询。

试想一种情况,用户准备使用SQL查询用户名,而用户名字段正好建立了索引。

 SELECT name FROM table WHERE username='guang19';

那么这个索引的key本身就是name,查到对应的name直接返回就行了,无需回表查询。

即使是MYISAM也是这样,虽然MYISAM的主键索引确实需要回表, 因为它的主键索引的叶子节点存放的是指针。但是如果SQL查的就是主键呢?

SELECT id FROM table WHERE id=1;

主键索引本身的key就是主键,查到返回就行了。这种情况就称之为覆盖索引了。

覆盖索引:

覆盖索引即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了, 而无需回表查询。

如主键索引,如果一条SQL需要查询主键,那么正好根据主键索引就可以查到主键。

再如普通索引,如果一条SQL需要查询name,name字段正好有索引, 那么直接根据这个索引就可以查到数据,也无需回表。

最左前缀原则

假设创建的联合索引由三个字段组成:

ALTER TABLE table ADD INDEX index_name (num,name,age)

那么当查询的条件有为:num / (num AND name) / (num AND name AND age)时,索引才生效。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值