MySQL-基础4-使用InnoDB引擎的表

目录

1. 索引组织表

2. InnoDB逻辑存储结构

2.1 表空间

2.2 段

2.3 区

2.4 页

2.5 行

3. InnoDB行记录格式

3.1 Compact行记录格式

3.2 Redundant行记录格式

3.3 行溢出数据

3.4 Compressed和Dynamic行记录格式

3.5 CHAR的行结构存储

4. Named File Formats机制

5. 约束

5.1 数据完整性

5.2 约束的创建与查找

5.3 约束和索引的区别

5.4 对错误数据的约束

5.5 ENUM和SET约束

5.6 触发器与约束

5.7 外键约束

6. 视图

6.1 视图作用

6.2 物化视图

7. 分区表

7.1 分区概念

7.2 分区类型:range分区

7.3 分区类型:list分区

7.4 分区类型:hash分区

7.5 分区类型:key分区

7.6 分区类型:columns分区

7.7 子分区

7.8 分区中的null值

7.9 在表和分区间交换数据



 

1. 索引组织表

在InnoDB存储引擎中,表中数据都是根据主键顺序来组织存放的,这种存储方式的表称为:索引组织表index organized table。因此,在InnoDB存储引擎表中,每张表都会有主键primary key,如果开发人员在创建表时没有显式地定义主键,则InnoDB存储引擎会按照如下规则创建主键:

①判断表中是否有非空的唯一索引,如果有,则该列即为主键;

②如果没有符合上述的列,则InnoDB自动创建一个6字节大小的指针;

注意:当表中有多个非空唯一索引时,InnoDB选择建表时第一个定义的非空唯一索引为主键,这里的指的是第一个定义索引的顺序,而不是建表时列的顺序。

2. InnoDB逻辑存储结构

从逻辑存储结构来看,所有数据都被放在一个空间中,这个空间叫做表空间tablespace,而表空间则是由段segment组成,段则是由区extent组成,而区又是由页page组成,而页又是由行Row组成,下面挨个介绍各个组成部分:

2.1 表空间

从逻辑结构层面来看,表空间tablespace是最高层,所有数据都存放在表空间中,默认情况下InnoDB有一个共享表空间ibdata1,所有数据都存放在这里,除非用户启用innodb_file_per_table来为每张表内的数据单独存放一个表空间;

只不过即便开启了innodb_file_per_table参数,每张表的表空间也仅仅是存放数据、索引、插入缓冲Bitmap页,至于其他数据,例如回滚信息、插入缓冲索引页、系统事务信息、二次写缓冲等还是继续存放到ibdata1中,这就意味着,不管你是否开启innodb_file_per_table,随着时间流逝,ibdata1占用空间还是会不断增长;

2.2 段

表空间是由各种段组成,常见的段有:数据段、索引段、回滚段等等。

考虑到采用的索引情况,数据即索引,索引即数据,数据段就是B+树的叶子节点,索引段即为B+树的非索引节点,在InnoDB存储引擎中,对段的管理都是由引擎自身完成;

2.3 区

区是由连续页组成的空间,在任何情况下每个区的大小都是1MB,为了保证区中页的连续性,InnoDB一次性从磁盘申请4-5个区,默认情况下,InnoDB存储页大小为16KB,即一个区中一共有64个连续的页;(1024/16=64)

在InnoDB1.0.x引入压缩页,通过key_block_size来设置页的大小,可以设置为2K/4K/8K,对应的每个区的页数量就是512/256/128;

从1.2.x又新增了innodb_page_size,该参数可以将默认页大小设置为4K/8K,但页中数据库不是压缩的,对应区中页的数量就是253/128,无论页大小怎么变化, 区就是1MB。

注意:当用户启用innodb_file_per_table后,创建的表默认是96KB,但是一个区是1MB,这是为什么呢?

在每个段开始时,先用32个页大小的碎片页(16KB*32=512KB)来存放数据,当使用完之后才去申请64个连续页,因为作为InnoDB来讲,其并不知道这个表到底占用多少存储空间,因此,先给你几个空间用用,如果确实放的数据量大了,再去给你分配更大的空间;

2.4 页

页page在其他资料里也叫做块block;页是InnoDB磁盘管理的最小单位,默认页大小为16KB,从InnoDB1.2.x开始,通过innodb_page_size可以将页设置为4K/8K/16K,设置完成后则所有表中页的大小为innodb_page_size,且不可以对其再次进行修改,除非通过mysqldump导入和导出来产生新的数据库,常见的页类型有:

数据页、undo页、系统页、事务数据页、插入缓冲位图页、插入缓冲空闲列表页、未压缩的二进制大对象页、压缩的二进制大对象页;

2.5 行

InnoDB存储引擎是面向列存储的,也就是说数据是按照行来进行存放的;每个页最多允许存放16KB/2 - 200行记录,即7992行记录;

3. InnoDB行记录格式

既然以行的形式存储记录,那我们看一下是怎么存储的:

在InnoDB 1.0.x之前,有Compact和Redundant这2种格式来存放行记录,在MySQL5.1中,默认采用Compact行格式,用户可以通过show table status like ‘table_name’来查看,其中的row_format就是当前所使用的行记录格式类型;

3.1 Compact行记录格式

Compact行记录格式是在MySQL 5.0中引入的,目标就是高效的存储数据,即一个页中存放的行数据越多,其性能就越高,其存储格式如下:

变长字段长度列表

Null标志位

记录头信息

列1数据

列N数据.....

变长字段长度列表:非NULL变长字段长度列表,并且是按照列的顺序逆序放置的,其长度如下:

①若列的长度小于255个字节,则用1字节表示;

②若列的长度大于255个字节,则用2字节表示;

变长字段的长度最大不可以超过2字节,这是因为MySQL的varchar最大长度限制为65535;

NULL标志位:指示该行数据中是否有NULL值,有则用1表示,占1字节;

记录头信息:固定占用5字节,一共40位,里面存储了不同的信息,例如:标记该行是否已被删除(占1位)、该记录拥有的记录数(占4位)、索引堆中该条记录的排序记录(占13位)、记录类型用来标记自己是B+树节点还是普通(占3位)、页中下一条记录的相对位置(占16位)等等;

列N数据:实际存储的,每个列的数据;这里特别注意的是NULL不占用该部分任何空间,即NULL除了占用NULL标志位,实际存储不占用任何空间,另外就是每行数据除去用户定义的列之外,还有2个隐藏列:事务ID列(6个字节)、回滚指针列(7个字节),最后就是如果该表没有自定义主键,那么InnoDB还会自己给加上一个6字节的rowid列;

3.2 Redundant行记录格式

该格式是MySQL5.0之前InnoDB的存储格式,所以,这个Redundant单纯是为了兼容之前版本而用的,其组成如下:

字段长度偏移列表

记录头信息

列1数据

列N数据.....

字段长度偏移列表:按照列的顺序逆序放置的,其长度如下:

①若列的长度小于255个字节,则用1字节表示;

②若列的长度大于255个字节,则用2字节表示;

记录头信息:占用6字节,48位,同样的,每一位都有固定的含义,例如:该行是否已被删除(1位)、该记录拥有的记录数(4位)、索引堆中该条记录的索引号(13位)、记录中列的数量(10)、偏移列表为1字节还是2字节(1位)、页中下一条记录的相对位置(16位)等等;

3.3 行溢出数据

InnoDB存储引擎可以把一条记录中某些数据存储在真正的数据页面之外,InnoDB存储引擎表是索引组织的,即B+ Tree的结构,这样每个页中至少应该有2条行记录,否则失去了B+ Tree的意义变成链表了,因此,如果某一条记录占据空间特别大,大到页中只能放下这一条记录,那么InnoDB存储引擎就自动把这行数据存放到溢出页中

3.4 Compressed和Dynamic行记录格式

InnoDB 1.0.x版本引入了新的文件格式,之前的Compact和Redundant称为Antelope文件格式,而新引入的文件格式叫做Barracuda文件格式,该文件格式拥有2种新的行记录格式:Compressed、Dynamic;

这2种记录格式对于存放在BLOB类型中的数据,直接采用了完全的行溢出方式,且Compressed行记录格式另外的功能是存储在其中的行数据会以zlib算法进行压缩,因此,对于BLOB、TEXT、VARCHAR这类大长度类型的数据能够进行非常有效的存储

3.5 CHAR的行结构存储

在多字节字符接的情况下,char和varchar的实际存储基本没有什么区别,因为从MySQL 4.1开始,char(N)中 N指的是字符的长度,而不是之前的字节长度了,因此不同的字符集情况下,char类型列内部存储的可能是不定长的数据;

4. Named File Formats机制

随着InnoDB引擎发展,各种新引入的页数据结构来支持新的功能特性,为了使得这些新的页数据结构与之前的页数据结构兼容,从InnoDB 1.0.x开始,InnoDB存储引擎通过Named File Formats机制来解决页结构兼容性问题;

InnoDB将1.0.x之前的文件格式叫做Antelope,这个版本中的文件格式叫做Barracuda,新的文件格式总是包含之前版本的页格式,未来还将引入新的文件格式,而我们可以通过innodb_file_format来指定文件格式,可以通过show variables like ‘innodb_file_format’\G来查看;

Innodb_file_format_check用来检查当前InnoDB对不同文件格式的支持度,默认值为on,如果出现不支持的文件格式,则打印到错误日志文件中;(目前InnoDB存储引擎里至少有26种不同的页数据格式,至于能否与不同版本的InnoDB兼容,就靠这个属性了)_

5. 约束

5.1 数据完整性

一般来说,数据完整性有以下3种形式:

实体完整性保证表中有一个主键:在InnoDB存储引擎表中,可以通过定义primary key或者unique key约束来保证实体的完整性,甚至还可以通过编写一个触发器来保证数据完整性;

域完整性保证数据每列的值满足特定的条件:在InnoDB存储引擎表中,域完整性可以通过以下几种途径来保证:

途径1:选择合适的数据类型来确保一个数据值满足特定条件;

途径2:外键foreign key约束;

途径3:编写触发器;

途径4:还可以用default约束作为强制域完整性的一个方面;

参照完整性保证2张表之间的关系:InnoDB支持外键,因此允许用户定义外键来强制参照完整性,也可以通过编写触发器来强制执行;

具体结合InnoDB存储引擎而言,其提供了如下几种约束:

主键约束primary key、唯一性约束unique key、外键约束foreign key、默认值约束default、非空约束not null;

5.2 约束的创建与查找

约束创建可以采用以下2种方式:

①表建立时就进行约束定义;

②利用alter table命令来创建约束;

具体语法,可以上网搜一下;

5.3 约束和索引的区别

约束是一个逻辑概念,来保证数据的完整性;

索引是一个数据结构,既有逻辑上的概念,在数据库中也代表着物理存储的方式;

5.4 对错误数据的约束

在某些默认设置下,MySQL允许非法/不正确的数据插入/更新,又或者在数据库内部将其转化为一个合法的值,例如向not null的字段插入一个null值之后,MySQL数据库会将其更改为0之后再进行插入,因此,MySQL并没有对数据的正确性进行约束;

如果用户想通过约束来对数据库的非法数据的插入/更新操作进行严格限制,则必须修改sql_mode参数;

当sql_mode=strict_trans_tables时,MySQL数据库会对输入值的合法性进行约束,不同错误会有提示不同的错误内容,至于sql_mode其他取值,可以网上搜一下;

5.5 ENUM和SET约束

MySQL不支持传统的check约束,但通过enum和set可以解决部分此类约束需求;

这里即便你违反了enum和set,MySQL也只是“警告warning”级别的信息,你需要修改sql_mode,从而让“警告warning”级别的信息变成“错误error”级别的信息;

5.6 触发器与约束

触发器的作用是在执行insert、delete、update命令前/后,自动调用SQL命令或存储过程,从MySQL 5.1开始触发器逐渐稳定、成熟;

通过create trigger命令来创建触发器,只有具备Super权限的MySQL数据库用户才可以执行该create trigger命令;

一个表最多创建6个触发器,分别对应insert-before/afler、update-before/afler、delete-before/afler,而其中的before/after分别代表触发器发生的时间,即在每行操作的之前发生还是之后发生;只不过目前MySQL只支持for each row的触发方式,即按每行记录进行触发;

5.7 外键约束

用户可以执行create table时就添加外键,也可以在表创建之后通过alter table来添加;

外键定义时的on delete和on update表示在对父表进行delete和update操作时,对子表所做的操作,而可定义的子表操作有:

①cascade:表示当父表发生delete或update操作时,对相应的子表中的数据也进行delete或update操作;

②set null:表示当父表发生delete或update操作时,相应的子表中数据被更新为null值,前提是子表中对应列允许为null值;

③no action:表示当父表发生delete或update操作时,抛出错误,不允许这类操作发生;

④restrict:表示当父表发生delete或update操作时,抛出错误,不允许这类操作发生,如果定义外键时没有指定on delete或on update时,restrict就是默认的外键设置;

注意:

①MySQL对外键约束都是立刻检查,而不是像其他数据库等SQL语句运行完毕之后再去检查;

②InnoDB在外键建立时会自动的对该列添加一个索引;

最后,外键约束在数据导入时,会非常麻烦,耗费大量时间,因为MySQL外键是即时检查,所以,对导入的每一行都会进行外键检查,为此,可以这样处理导入时的外键约束:

set foreign_key_checks = 0;

导入数据完毕

set foreign_key_checks = 1;

6. 视图

在MySQL数据库中,视图view是一个命名的虚表,由一个SQL查询来定义,可以当做表来用,与持久表不同的是,视图中的数据没有实际的物理存储;(通俗理解为视图是存在于内存中的,持久表是存在于磁盘和内存中的)

6.1 视图作用

视图主要用途之一就是被用作一个抽象装置,特别是对于一些应用程序,这部分程序不需要基表的全部字段,只需要部分字段,因此只需要根据所需字段定义视图来获取数据或者更新数据,因此,从某种程序来讲视图也算是起到一个安全层的作用,毕竟隔离了应用程序与基表;

MySQL 5.0开始支持视图,虽然视图是基于基表的一个内存虚拟表,但用户还是可以对某些视图进行update操作,通过视图来更新基表,像这种可以更新的视图叫做可更新视图updatable view,视图定义中的with check option就是针对可更新视图的,即更新值是否需要检查,对于不满足视图定义条件的,则抛出一个异常,不允许视图中数据更新;

通过show tables来查看所有的基表和视图,如果只想看基表则可以在information_schema架构下的table表来查询,搜索table_type = ‘base table’的表,而information_schema架构下的views表则存放了视图的详细信息;

6.2 物化视图

物化视图指的是把位于内存中的视图,放到磁盘上,物化视图可以用于预先计算并保存多表的链接join或者聚集group by等耗时较多的SQL操作结果,这样在执行复杂查询的时候,就可以避免进行耗时的操作,从而快速得到结果,而MySQL本身不支持物化视图;

7. 分区表

7.1 分区概念

分区功能并不是在存储引擎层面完成的,但也并不是所有的存储引擎都支持,因此,有些引擎支持分区,而有些引擎则不支持分区;

MySQL 5.1版本新增了对分区的支持,分区的过程就是把一个表或者索引分解为多个更小、更容易管理的部分,从逻辑上讲,用户看到的只有一个表或一个索引,但物理层面这个表或索引则是由数十个物理分区组成,每个分区都是独立的对象,可以独自处理,也可以作为一个更大对象的一部分进行处理;

MySQL支持水平分区(同一个表中不同行记录分配到不同的物理文件中),并不支持垂直分区(同一个表中不同列记录分配到不同的物理文件中),此外,MySQL分区是局部分区索引,即一个分区中既存放了数据又存放了索引(全局分区指数据存放到各个分区中,但所有数据的索引存放到一个对象中);

可以通过show variables like ‘%partition%%’\G来查看当前数据库是否启用了分区功能;

MySQL数据库支持以下几种类型的分区:

①range分区:行数据基于属于一个给定连续区间的列值来被放入分区,MySQL 5.5开始支持range columns分区;

②list分区:和range分区类似,只不过是list分区面向的是离散的值,MySQL 5.5开始支持list columns分区;

③hash分区:根据用户自定义的表达式的返回值来进行分区,返回值不能为负数;

④key分区:根据MySQL提供的哈希函数来进行分区;

注意事项:

①不论创建何种类型的分区,如果表中存在主键或唯一索引时,分区列必须是唯一索引的一个组成部分;唯一索引可以允许null值的,而分区列只要是唯一索引的一个组成部分,不需要整个唯一索引列都是分区列,例如unique key(col1,col2,col3)的话,则分区列只能从col1和col2以及col3来选择

②如果建表时没有指定主键/唯一索引,那么可以指定任何一个列为分区列;

7.2 分区类型:range分区

create table t1( id INT )

ENGINE=INNODB

PARTITION BY RANGE(id)(

PARTITION p0 VALUES LESS THAN (10),

PARTITION p1 VALUES LESS THAN (20)

)

这里当id小于10的时候插入p0分区,当id大于等于10小于20时插入p1分区;

再查看表在磁盘上的物理文件,启用分区之后,表文件是由建立分区时的各个分区的ibd文件组成,文件名格式为:表名#P#分区id.ibd,例如:t1#P#p0.ibd、t1#P#p1.ibd;

分区表t1是根据列id的值范围来存放到不同的物理文件中,可以通过information_schema架构下的partitions表来查看每个分区的详细信息;

当插入一个不在分区中定义的值时,MySQL数据库会抛出一个异常,因此可以在分区中添加一个maxvalue值的分区来解决这类情况:

alter table t1 add partition(partition p2 values less than maxvalue);

结合实际项目开发,range分区主要用于日期列的分区,根据月份、年份来存放对应的记录,当我们要删除对应年份的记录时,不再需要delete语句,而是直接alter table xxx drop partition 分区名即可,直接drop掉对应的分区;

注意事项:

create table t1( date datetime )

ENGINE=INNODB

PARTITION BY RANGE(year(date))(

PARTITION p2008 VALUES LESS THAN (2009),

PARTITION p2009 VALUES LESS THAN (2010)

)

查询2008年数据-sql1:

select * from t1 where date >= ‘2008-01-01’ and date <= ‘2008-12-31’

查询2008年数据-sql2:

select * from t1 where date >= ‘2008-01-01’ and date < ‘2009-01-01’

 

①对于启用分区后,应该根据分区的特定来编写性能最优的SQL语句,在编写select查询的时候,必须把分区列作为查询条件

②date >= ‘2008-01-01’ and date <= ‘2008-12-31’则是告诉MySQL仅仅查询P2008这一个分区;而date >= ‘2008-01-01’ and date < ‘2009-01-01’则是告诉MySQL要分别查询P2008和P2009这2个分区,因此编写SQL的时候,尽量不要夸分区查询

③PARTITION BY RANGE(year(date)),这里对range分区查询优化器,其只对year()/to_days()/to_seconds()/unix_timestamp()这类函数进行优化选择,其他函数在range(XXXXX)没有效果;

7.3 分区类型:list分区

list分区和range分区非常相似,只是分区列的值是离散的,而非连续的:

create table t1(id int)

partition by list(id)(

partition p0 values in (1,2,3,4,5),

partition p1 values in (6,7,8,9,10)

);

这里与range区别:

range用的是values less than,而list用的是values in;

7.4 分区类型:hash分区

Hash分区目的是把数据均匀分散到预先定义好的各个分区中,从而保证各个分区中数据量大致是一样的;

相比较range和list在定义时需要人工明确指定,hash分区保存则是MySQL自动完成这些工作,而需要开发者做的只是把要进行hash分区的列编写一个列值或者表达式,以及指定被分区的表将要被分割成的分区数量;

parttition by hash(expr)中expr是一个返回一个整数的表达式,然后后面添加partitions num,其中的num是一个非负的整数来表示将要被分割成分区的数量,如果没有partitions,则默认分数数量为1;

create table t1(id int)

partition by hash(id)

partiton 10;

(个人猜测hash返回一个整数,然后用整数除以10取余,余数就是要放的分区id)

这里是hash分区,MySQL还提供了linear hash的分区;

7.5 分区类型:key分区

Key分区与hash分区类似,不同点在于hash分区使用用户自定义的函数来进行分区,key分区使用MySQL数据库提供的函数来进行分区;

create table t1(id int)

partition by key(id)

partiton 10;

7.6 分区类型:columns分区

range/list/hash/key分区的条件是:数据必须是整数,如果不是整数,那么需要通过函数将其转化为整数,例如:year()/to_days()/month()等函数;

从MySQL 5.5开始支持columns分区,该分区可以直接使用非整型的数据进行分区,分区根据类型直接比较而得到,不需要转化为整数,另外 range columns分区还支持对多个列的值进行分区;

Columns分区支持以下数据类型:

①所有整型类型,例如int,smallint,tinyint,bigint;

②日期类型,如date和datetime;

③字符串类型,如char,varchar,binary,varbinary;

按照日期类型来分区:

create table t1(birthday datetime)engine=innodb

partition by range columns(birthday)(

partition p0 values less than (‘2009-01-01’),

partition p1 values less than (‘2010-01-01’)

)

按照字符串类型来分区:

create table t1(name varchar(1000))

partition by list columns(name)(

partition p0 values in (‘zhagnsan’,‘lisi),

partition p1 values in (‘wangwu’,‘maliu)

)

针对range columns,还可以使用多个列进行分区:

create table t1(

age int,

name varchar(10)

)engine=innodb

partition by range columns(age,name)(

partition p0 values less than(20,’lisi’),

partition p1 values less than(80,‘wangwu’)

);

这里可以看出,columns分区的2个分支range column和list column分别可以替代range和list分区;

7.7 子分区

子分区指的是在分区的基础上再进行分区,有时候这种分区又叫做复合分区,MySQL只允许在range和list分区上再进行hash或key的子分区

create table t1(id int,birth date)engine=innodb

partition by range(year(birth))

subpartition by hash(to_days(birth))

subpartitions 2(

 partition p0 values less than (1990),

 partition p1 values less than (2000),

 partition p2 values less than (maxvalue)

);

这里先根据partition by range(year(birth))进行range分区,然后再通过subpartition by hash(to_days(b))进行hash分区,subpartitions 2这里的意思是:按照range分成p0、p1、p2一共3个分区,然后每个分区继续按照hash继续分成2个子分区;

我们还可以通过subpartition语法来为每个子分区指定名字,对上面t1表改造如下:

create table t1(id int,birth date)engine=innodb

partition by range(year(birth))

subpartition by hash(to_days(b))(

 partition p0 values less than (1990)(subpartition s0,subpartition s1),

 partition p1 values less than (1990)(subpartition s2,subpartition s3),

 partition p2 values less than (1990)(subpartition s4,subpartition s5)

);

子分区的建立需要注意下面几个问题:

①每个子分区的数量必须相同;

②要在一个分区表的任何分区上使用subpartiton来明确定义任何子分区,那么就必须定义所有的子分区;(通俗来讲,就是要么不定义,要么都定义)

③每个subpartition子句必须包括子分区的一个名字;

④子分区的名字必须是唯一的;

7.8 分区中的null值

MySQL允许对null值做分区,MySQL分区的时候,总是把null值看做小于任何一个非null值,跟处理null值的order by操作是一样的,因此对不同的分区类型,MySQL对null值的处理也各不相同,例如:

①对于range分区,向分区列插入null值,则MySQL会把该值放入最左边的分区,可以理解为最先定义的partition;

②对于list分区,向分区列插入null值,则必须显式地指出哪个分区中将放入null值,否则会报错,即在values in(null)中显式指明null存放的分区;

③对于hash和key分区,任何分区函数都会将含有null值的记录返回为0;

7.9 在表和分区间交换数据

MySQL 5.6开始支持alter table ... exchange partition语法,该语法允许分区/子分区中的数据与另一个非分区表中的数据进行交换;如果非分区表中的数据为空,则将分区中的数据移动到非分区表中,如果分区表中的数据为空,则将非分区表中的数据移动到分区中;(这里是移动,相当于是剪切操作,而不是复制操作)

要想使用alter table exchange partition语法,需要满足下面几个条件:

①要交换的表需要和分区表有着相同的表结构,且表不能是分区表;

②在非分区表中的数据必须在交换的分区定义内;

③被交换的表中不能含有外键,或者其他表含有对该表的外键引用;

④用户除了alter、insert、create权限外,还需要drop权限;

这里还有2个细节:

细节1:使用该语法时,不会触发交换表和被交换表上的触发器;

细节2:auto_increment列会被重置;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值