Day07-数据库服务事务机制


1、数据库存储引擎结构 (磁盘结构部分–表空间–一大波名词知识–底层原理知识)
2、数据库事务机制概念
3、数据库事务机制特性 (A-原子特性 C-一致性 I-隔离性 D-持久性)
4、数据库事务生命周期
5、数据库事务提交方式
6、数据库事务隔离级别 (RC RR 脏读 不可重复读 幻读)
7、数据库事务工作流程 (A-原子特性 C-一致性 I-隔离性 D-持久性) 详述
8、数据库存储行锁模式 (表锁)

入门门槛问题:
01 数据库索引原理问题 - Btree 索引树构建(聚簇 辅助)
02 数据库存储引擎问题 - 存储引擎结构
03 数据库事务处理特性 - 隔离机制

1、数据库存储引擎结构

磁盘结构部分:
类型三:undo表空间
作用:可以将事务变化前的数据页信息进行保存,便于进行数据的回滚操作
文件:undo_00x
操作:

mysql> select @@innodb_undo_tablespaces;
+---------------------------+
| @@innodb_undo_tablespaces |
+---------------------------+
|                         2 |
+---------------------------+
1 row in set (0.00 sec)
-- 确认是否打开独立undo模式,并设置undo表空间文件个数(3-5个),(用于轮询)

mysql> select @@innodb_max_undo_log_size;
+----------------------------+
| @@innodb_max_undo_log_size |
+----------------------------+
|                 1073741824 |
+----------------------------+
1 row in set (0.00 sec)
-- 表示undo日志信息的大小,默认1G

mysql> select @@innodb_undo_log_truncate;
+----------------------------+
| @@innodb_undo_log_truncate |
+----------------------------+
|                          1 |
+----------------------------+
1 row in set (0.00 sec)
-- 表示开启undo自动回收的机制(undo purge)
-- 是否对原有历史undo日志数据进行释放

mysql> select @@innodb_purge_rseg_truncate_frequency;
+----------------------------------------+
| @@innodb_purge_rseg_truncate_frequency |
+----------------------------------------+
|                                    128 |
+----------------------------------------+
1 row in set (0.00 sec)
-- 触发自动回收的条件,单位是检测次数
-- 在释放undo日志数据前,做多少次数据量确认

官方参数使用说明(important):
The number of undo tablespaces can only be configured when initializing a MySQL instence and is fixed for the life of the instance;
undo表空间的数量只能在初始化MySQL实例时配置,并且在实例生命周期内是固定的

PS:undo有些配置信息,需要在初始化操作时进行预先调整;

类型四:temp表空间
作用:用于存储临时数据信息,比如:排序的数据信息 分组的数据信息 连表查询的数据信息
文件:ibtmp1

类型五:redo事务日志 ****
作用:用于存储数据操作记录信息(实现方法 利用了WAL技术 write ahead log)
文件:ib_logfileX
配置:

mysql> show variables like '%innodb_log_file%';
+---------------------------+----------+
| Variable_name             | Value    |
+---------------------------+----------+
| innodb_log_file_size      | 50331648 |
| innodb_log_files_in_group | 2        |
+---------------------------+----------+
2 rows in set (0.07 sec)
-- 在实际生产环境中,建议大小为512M~4G,应用组数为2~4组(写入数据过程轮询写入)

redo事务日志配置操作方法:

# 编写数据库配置文件信息
vim /etc/my.cnf
[mysqld]
innodb_log_file_size=100M
innodb_log_files_in_group=3

# 确认配置信息是否已经生效
[root@xiaoQ-01 data]# /etc/init.d/mysqld restart
[root@xiaoQ-01 data]# ll /data/3306/data/ib_log*
-rw-r----- 1 mysql mysql 104857600 11月 15 15:12 /data/3306/data/ib_logfile0
-rw-r----- 1 mysql mysql 104857600 11月 15 15:12 /data/3306/data/ib_logfile1
-rw-r----- 1 mysql mysql 104857600 11月 15 15:12 /data/3306/data/ib_logfile2

类型六:ib_buffer_pool预热文件
作用:ib_buffer_pool预热文件可以用于缓冲和缓存,可以存储"热"数据页(经常查询或修改的数据页),从而减少物理IO消耗;
从数据库5.7版本开始,数据库正常关闭后,内存中存储的数据页缓冲或缓存信息均会失效,重新启动后还会消耗IO获取相应数据页信息;
为了可以尽量减少磁盘IO的消耗,可以将内存中的热数据页信息存储在ib_buffer_pool文件中;
数据库服务再次启动后,会直接读取ib_buffer_pool文件中信息,并将读取的信息加载到内存中,最终减少随机IO数量;

说明:存在ib_buffer_pool预热文件后,有可能在数据库服务关闭时比较耗费一些时间,但实际环境数据库服务关闭情况较少;

文件:ib_buffer_pool

类型七:Doublewrite Buffer(DWB)文件
作用:可以保证数据页信息存储时,不会出现坏页(或者在出现坏页时,可以修复坏页)
DWB文件主要作用是:mysql process crash in the middle of a page write(在数据库服务存储时,数据页写了一半);
数据库Innodb可以找到一个好的数据页副本从Doublewrite Buffer文件中,主要是避免数据信息出现损坏;
MySQL数据库最小IO存储单元是page(16kB),OS系统中最小的IO存储单元是block(4kB),OS也可以称为存储子系统;
会出现一个问题:数据库系统与操作系统的存储关系问题,在数据库中写入一个数据页时,在文件系统层面可能只是写入了2个block;
文件:#ib_16384_0.dblwr
​ #ib_16384_1.dblwr

内存结构部分:
类型一:AHI
作用:用于存储热点数据页的内存地址信息,可以快速调取数据页中数据,减少总索引树的IO消耗
简单理解:索引的索引

类型二:change buffer
作用:将辅助索引树中,变化的索引数据页信息进行临时存储,避免辅助索引树结构频繁发生变化,导致业务访问索引树受到影响

类型三:Buffer Pool
作用:包含了AHI内存数据信息 + change buffer内存数据信息 + 磁盘加载数据页信息和索引页信息
配置:

# Buffer Pool配置参数信息查看:
mysql> select @@innodb_buffer_pool_size;
+---------------------------+
| @@innodb_buffer_pool_size |
+---------------------------+
|                 134217728 |
+---------------------------+
1 row in set (0.00 sec)
-- buffer pool默认内存空间大小为128M,生产建议大小可以设置为物理内存总量的50%~80%

# Buffer Pool配置参数修改方法:
# 配置信息临时调整
mysql > set global innodb_buffer_pool_size=268435456;
-- 配置调整后,重新登录mysql数据库生效

# 配置信息永久调整
[root@xiaoQ-01 ~]# vim /etc/my.cnf
[mysqld]
innodb_buffer_pool_size=256M
-- 配置调整后,重新启动mysql数据库生效

类型四:Log Buffer
作用:Log Buffer内存存储区域主要用来缓冲 redo log日志信息;
配置:

# Log Buffer配置参数信息查看:
mysql> select @@innodb_log_buffer_size;
+--------------------------+
| @@innodb_log_buffer_size |
+--------------------------+
|                 16777216 |
+--------------------------+
1 row in set (0.00 sec)
-- log_buffer默认内存空间大小为16M,生产建议大小可以设置为innodb_log_file_size文件大小的 1-N倍(后续说明)

# Log Buffer配置参数修改方法:
# 配置信息临时调整
mysql > set global innodb_log_buffer_size=33554432;
-- 配置调整后,重新登录mysql数据库生效

# 配置信息永久调整
[root@xiaoQ-01 ~]# vim /etc/my.cnf
[mysqld]
innodb_log_buffer_size=32M
-- 配置调整后,重新启动mysql数据库生效

数据库引擎架构梳理

1668600173769

2、数据库事务机制概念

事务(Transaction)可以更通俗的理解为交易,所以事务会伴随着交易类的业务类型出现的概念(工作模式);
现实生活中存在很多的交易行为,比如:物换物的等价交换、货币换物的等价交换、虚拟货币换物(虚拟物品)的等价交换;
因此就需要考虑如何保证现实生活中交易过程的和谐,一般会有法律、道德等方面规则进行约束;
而在数据库服务中为了保证线上交易的"和谐",便加入了"事务"工作机制(保证交易行为安全性)

3、数据库事务机制特性

A – 原子性:(Atomicity)
表示在事务生命周期中,所有的操作行为必须都成功,要不都失败。

C – 一致性:(Consistency)
一致性表示一个事务发生前、中、后,数据都最终保持一致,即读和写都要保证一致性;

I – 隔离性:(Isolation)
隔离性表示一个事务操作数据行的时候,不会受到其他事务的影响,主要利用锁机制来保证隔离性;

D – 持久性:(Durability)
持久性表示一旦事务进行了提交,即可永久生效(落盘)
可以在数据库服务异常时,将内存中未落盘的数据进行恢复

4、数据库事务生命周期

begin   DML;DML;DML;DML;  commit   --  完整的生命周期
begin   DML;DML;DML;DML;  rollback  --  完整的生命周期

在运用事务机制完成相关工作任务时,对于事务使用是存在生命周期概念的,标准显示的事务生命周期控制语句有:

# 开启事务机制
begin;
start transaction;
# 提交事务任务
commit;
# 回滚事务操作
rollback;

说明:事务生命周期中,只能使用DML语句,其中包括:select、update、delete、insert;DDL语句会隐式进行提交

事务的生命周期操作演示:

# 进行测试数据库查询数据
mysql> use world;
mysql> select * from city limit 10;

# 进行测试数据库数据撤销修改
mysql> begin;
mysql> update city set population=10 where id=1;
mysql> update city set population=10 where id=2;
-- 由于是采用事务进行的修改,所以只是在内存层面进行的修改,并没有对磁盘上的数据进行修改;
mysql> select * from city limit 10;
-- 由于是采用事务进行的修改,此时看到的数据信息只是内存层面的修改信息
mysql> rollback;
-- 由于是采用事务进行的撤销,会读取undo文件信息,将事务操作撤回到事务开始前的状态
mysql> select * from city limit 10;
-- 由于是采用事务进行的修改,当撤销操作执行完,看到数据信息还是原来的;

# 进行测试数据库数据永久修改
mysql> begin;
mysql> update city set population=10 where id=1;
mysql> update city set population=10 where id=2;
-- 由于是采用事务进行的修改,所以只是在内存层面进行的修改,并没有对磁盘上的数据进行修改;
mysql> select * from city limit 10;
-- 由于是采用事务进行的修改,此时看到的数据信息只是内存层面的修改信息
mysql> commit;
-- 由于是采用事务进行的提交,会加载redo文件信息,将事务内存层面的修改同步到磁盘中(完成了D特性)
mysql> select * from city limit 10;
-- 由于是采用事务进行的修改,当执行操作执行完,看到数据信息将永久保存下载;

5、数据库事务提交方式

方式一:自动提交方式(人操作数据库)
事务自动提交表示在没有显示的使用begin语句的时候,执行DML操作语句时,会在DML操作语句前自动添加begin
并在DML操作语句执行后自动添加commit
在生产环境中,若处于频繁事务业务场景中,建议关闭autocommit自动提交功能,或者每次事务执行的时候;
都进行显示的执行begincommit
事务自动提交方式参数信息:

mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            1 |
+--------------+
1 row in set (0.00 sec)
-- 此时设置为1,表示事务会自动提交;

事务自动提交方式参数修改:

# 临时关闭事务自动提交功能
mysql> set global autocommit=0;
-- 配置调整后,重新登录mysql数据库生效

# 永久关闭事务自动提交功能
[root@xiaoQ-01 ~]# vim /etc/my.cnf
[mysqld]
autocommit=0
-- 配置调整后,重新启动mysql数据库生效

事务自动提交方式设置方式优点缺点说明:

序号参数配置优势
情况01autocommit=0
关闭事务自动提交
可以编写多个关联的DML,进行一次性提交操作,若出现异常可以回滚
符合原子特性
劣势
可能出现多个关联的DML,只是完成了部分操作,这时就可能等待状态
基于隔离特性,操作的数据表或数据行就会进入锁定状态
参数配置优势
情况02autocommit=1
开启事务自动提交
可以出现多个关联的DML,逐行操作自动提交,就可以不用处于锁等待状态
劣势
可能出现多个关联的DML,,每执行一条就进行提交,会造成多个语句执行不符合原子性

方式二:手动提交方式(程序连接数据库)

mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            0 |
+--------------+
1 row in set (0.00 sec)
-- 此时设置为0,表示事务会手动提交;

特殊情况:
隐式提交方式:在没有执行commit时,也会自动提交

序号语句类型涉及命令
01DDL语句类型alter、create、drop
02DCL语句类型grant、revoke、set password
03锁定语句类型lock tables、unlock tables
04其他语句类型truncate table、load data infile、select for update

隐式自动回滚情况分析:

  • 情况一:在事务操作过程中,会话窗口自动关闭了,会进行隐式自动回滚;
  • 情况二:在事务操作过程中,数据库服务被停止了,会进行隐式自动回滚;
  • 情况三:在事务操作过程中,出现事务冲突死锁了,会进行隐式自动回滚;

6、数据库事务隔离级别 (RC RR 脏读 不可重复读 幻读)

数据隔离机制作用:避免多个事务操作冲突性;

隔离级别:
级别一:RU(READ-UNCOMMITTED 表示读未提交) 低
一个事务还没提交时,它做的变更就能被别的事务看到。

事务A-begin 事务B-begin
update a=10 - > 20 select a ? = 20
问题:脏读 不可重复读 幻读???

级别二:RC(READ-COMMITTED 表示读已提交) 中
一个事务提交之后,它做的变更才会被其他事务看到。

事务A-begin 事务B-begin
select a=10
update a=10 - > 20 select a = 10
commit select a = 20

问题:不可重复读 幻读???
​ 行级锁,所以无法解决幻读问题

级别三:RR(REPEATABLE-READ 表示可重复读) 高
一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。
当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。

事务A-begin 事务B-begin
select a=10 select a = 10
update a=10 - > 20 select a = 10
commit select a = 10
select a = 20 commit
​ select a = 20

问题:解决不可重复读 – RR
​ 幻读 – 间隙锁,所以解决了幻读问题

级别四:SR(SERIALIZABLE 可串行化) 严格
顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。
当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
简单说明:多个并行事务,分别执行

事务A-begin 事务B-begin
select a=10 select a = 10
update a=10 - > 20 L-行锁 update a=10 - > 30 禁止
​ update a=20 - > 30 禁止
表锁
commit

基于以上级别利用操作理解作用特性:
1)如何修改级别:

mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ         |
+-------------------------+
1 row in set (0.00 sec)

# 临时修改:
set global transaction_isolation='READ-UNCOMMITTED';   -- 改为RU
set global transaction_isolation='READ-COMMITTED';     -- 改为RC
set global transaction_isolation='REPEATABLE-READ';    -- 改为RR
set global transaction_isolation='SERIALIZABLE';       -- 改为SR

# 永久修改
vim /etc/my.cnf
[mysqld]
transaction_isolation='xxx'
-- 重启服务程序

2)隔离级别不同出现的问题解释
问题一:脏读 – 读的隔离性问题
内存中修改的数据信息,不同的事物都可以看到;内存中操作的数据页-脏页

问题二:不可重复读 – 读的隔离性问题
在一个事务生命周期内,查询相同的数据信息,有不同的结果
select * from t1 where id=10; --> a=10
select * from t1 where id=10; --> a=20

问题三:幻读 – 写的隔离性问题
多个事务并行处理时,其中一个事务做了数据信息调整,另一个事务也做了相同数据信息调整;
会导致上一个事务做的操作,出现异常结果;
薪资表-A事务 薪资表-B事务
update 薪资表 set 人员薪资=20W where 人员薪资<20W insert into 薪资表 values(张三,10W);
commit commit
select * from 薪资表 where 人员薪资<20W
张三 薪资=10W

3)不同级别的操作演示:
准备环境数据:

# 创建表:
mysql> use oldboy
mysql> create table t1 (
    id int not null primary key auto_increment,
    a int not null,
    b varchar(20) not null,
    c varchar(20) not null
) charset=utf8mb4 engine=innodb;

# 创建数据
mysql> begin;
mysql> insert into t1(a,b,c)
values
(5,'a','aa'),
(7,'c','ab'),
(10,'d','ae'),
(13,'g','ag'),
(14,'h','at'),
(16,'i','au'),
(20,'j','av'),
(22,'k','aw'),
(25,'l','ax'),
(27,'o','ay'),
(31,'p','az'),
(50,'x','aze'),
(60,'y','azb');
mysql> commit;
-- 确认两个SQL会话窗口,即不同的事务查看的数据是否一致的;

隔离级别设置:RU

set global transaction_isolation='READ-UNCOMMITTED';   -- 改为RU

mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-UNCOMMITTED        |
+-------------------------+
1 row in set (0.00 sec)

开启两个事务窗口 A B

体会什么是脏读情况:

# A事务的操作:
mysql> begin;
mysql> update t1 set a=10 where id=1;

mysql> select * from t1 where id=1;
+----+----+---+----+
| id | a  | b | c  |
+----+----+---+----+
|  1 | 10 | a | aa |
+----+----+---+----+
1 row in set (0.00 sec)

# B事务的操作:
mysql> select * from t1 where id=1;
+----+----+---+----+
| id | a  | b | c  |
+----+----+---+----+
|  1 | 5  | a | aa |
+----+----+---+----+
1 row in set (0.00 sec)
-- 在A会话窗口没提交的事务修改,被B会话窗口查询到了
mysql> select * from t1 where id=1;
+----+----+---+----+
| id | a  | b | c  |
+----+----+---+----+
|  1 | 10 | a | aa |
+----+----+---+----+
1 row in set (0.00 sec)
-- 在A会话窗口进行回滚后,在B窗口查询的数据又恢复了

体会什么是不可重复读:

mysql> update t1 set a=10 where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update t1 set a=15 where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from t1 where id=2;
+----+----+---+----+
| id | a  | b | c  |
+----+----+---+----+
|  2 | 10 | c | ab |
+----+----+---+----+
1 row in set (0.00 sec)

mysql> select * from t1 where id=2;
+----+----+---+----+
| id | a  | b | c  |
+----+----+---+----+
|  2 | 15 | c | ab |
+----+----+---+----+
1 row in set (0.00 sec)

体会什么是幻读:

mysql> alter table t1 add index idx(a);
# 事务A操作
mysql> begin;
mysql> update t1 set a=20 where a<20;
Query OK, 6 rows affected (0.00 sec)
Rows matched: 6  Changed: 6  Warnings: 0

mysql> select * from t1 where a<20;
Empty set (0.01 sec)

mysql> commit;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from t1 where a<20;
+----+----+---+----+
| id | a  | b | c  |
+----+----+---+----+
| 14 | 15 | o | zz |
+----+----+---+----+
1 row in set (0.00 sec)

# 事务B操作
mysql> begin;
mysql> insert into t1 values(null,15,'o','zz');
Query OK, 1 row affected (0.00 sec)

隔离级别设置:RC (互联网应用)
set global transaction_isolation=‘READ-COMMITTED’; – 改为RC

解决了脏读问题,但是无法解决不可重复读和幻读问题;

支付 – 账户钱 – 改动 10000 9000 – rollback — 确认 commit – 待付款 – 9000

# 事务A操作
mysql> update t1 set a=10 where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from t1 where id=1;
+----+----+---+----+
| id | a  | b | c  |
+----+----+---+----+
|  1 | 10 | a | aa |
+----+----+---+----+
1 row in set (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.01 sec)

# 事务B操作
mysql> select * from t1 where id=1;
+----+----+---+----+
| id | a  | b | c  |
+----+----+---+----+
|  1 | 20 | a | aa |
+----+----+---+----+
1 row in set (0.00 sec)

mysql> select * from t1 where id=1;
+----+----+---+----+
| id | a  | b | c  |
+----+----+---+----+
|  1 | 10 | a | aa |
+----+----+---+----+
1 row in set (0.00 sec)

隔离级别设置:RR (金融行业)
set global transaction_isolation=‘REPEATABLE-READ’; – 改为RR next key lock
解决了脏读问题,不可重复读问题,幻读问题

解决幻读 存在间隙锁 默认其他级别的锁是行锁:

行级锁 + 间隙锁 gap lock == next key lock

区域间隙锁 < 左闭右开(可用临界值) ; 区域间隙锁 > 左开右闭(不可用临界值)

7、数据库事务工作流程

1)名词解释
参考文档:

  • redo log - Disk
    表示重做日志,当出现异常情况,内存中数据直接写入磁盘失败时,可以通过重启数据库服务,读取此文件修复数据信息;
    文件存储表项为:ib_logfile0~N 默认48M,轮询使用

  • redo log buffer - mem
    表示重做日志生成缓冲区,相当于redo log的内存区域。redo log文件与redo log buffer是有IO关系的;
    事务修改提交后:redo log buffer -> redo log,表示写入数据到redo log;
    事务操作恢复时:redo log -> redo log buffer,表示读取数据从redo log;

  • tablespace file - disk
    表示存储表数据行和索引等信息的文件,含有表空间所有数据文件;ibd

  • Innodb buffer pool - mem
    表示数据缓冲区,主要用于缓冲事务要处理的数据和索引信息,tablespace文件与buffer pool是有IO关系的;

  • LSN
    表示日志序列号,在buffer pool中有数据页信息的变化就会记录到redo log buffer中,主要记录变化了多少字节量;
    利用LSN记录相应数据页的变化量(LSN+变化字节量),也可以理解为记录的是日志量的变化;
    MySQL每次数据库启动,都会比较磁盘数据页和redolog的LSN,必须要求两者一致,数据库才能正常启动;

  • WAL(Write Ahead Log)
    表示redo日志生成记录优先于数据页写入到磁盘的过程,并且是支持预写入机制(group commit)的;

  • Dirty page
    表示在内存进行修改的数据页,在redo buffer中会记录数据页的数据量的变化,此时在数据页还未最终写入到磁盘中时;
    就称之为脏页,所以一般所谓的脏读就是读取脏页的数据页信息;

  • CheckPoint
    表示为检查点,就是将脏页刷写到磁盘的动作;

  • DB_TRX_ID(6字节)
    表示为事务ID号,InnoDB会为每一个事务生成一个事务号(由事务管理器管理TM),伴随着整个事务生命周期
    其中事务ID号码信息,在redo和undo日志文件中都会有相应的标识;

  • DB_ROLL_PTR(7字节)
    表示回滚指针,在rollback时会使用undo日志回滚已修改的数据,DB_ROLL_PTR会指向此次事务的回滚业务点;
    从而找到undo上的相应的日志信息;

数据库名词解释官方参考:https://dev.mysql.com/doc/refman/8.0/en/glossary.html

2)事务特性详述

  • 事务都具有D - 持久性 (redo log、CR 机制)
  • 事务都具有A - 原子性 (undo log、redo log、CR 机制)

存储引擎序号号码信息查看:

mysql> show engine innodb status\G
Log sequence number                   105377511
-- redo buffer中的SN号码信息
Log flushed up to                           105377511
-- redo buffer刷新到磁盘上的SN号码信息
Last checkpoint at                         105377511
-- 磁盘数据页的SN号码信息
  • 事务都具有C - 一致性 (undo log、redo log、CR 机制、DWR 机制)

学生问题:
01 如何知道多个undo日志文件,正在使用哪一个?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值