MySQL之存储引擎

在文件系统中,MySQL将每个数据库(schema)保存为数据目录下的一个子目录。创建表时,MySQL会在数据库子目录下创建一个和表同名的.frm文件保存表的定义。例如创建一个MyTable的表,MySQL会在MyTable.frm文件中保存该表的定义。因为MySQL使用文件系统的目录和文件来保存数据库和表的定义,大小写敏感性和具体的平台密切相关。Windows中,大小写是不敏感的;但在类Unix中则是敏感的。不同的存储引擎保存数据和索引的方式是不同的,但表的定义是在MySQL服务层统一处理的。


可以使用 SHOW TABLE STATUS命令(在MySQL5.0以后的版本中,也可以查询INFORMATION_SCHEMA中对应的表)显示表的相关信息。如:mysql数据库的user表

 mysql> SHOW TABLE STATUS LIKE 'user' \G;

 *************************** 1. row ***************************
               Name: user
             Engine: MyISAM
         Row_format: Dynamic
               Rows: 6
     Avg_row_length: 59
        Data_length: 356
    Max_data_length: 4294967295
       Index_length: 2048
          Data_free: 0
     Auto_increment: NULL
        Create_time: 2002-01-24 18:07:17
        Update_time: 2002-01-24 21:56:29
         Check_time: NULL
          Collation: utf8_bin
           Checksum: NULL
     Create_options:
            Comment: Users and global privileges
    1 row in set (0.00 sec)

Engine: MyISAM表明这是一个MyISAM表。输出中还有很多其他信息和统计信息。

  • Name:表名。
  • Engine:表的存储引擎类型。旧版本中,该列的名字叫Type。
  • Row_format:行的格式。对于MyISAM表,可选的值为Dynamic、Fixed、Compressed。Dynamic的行长度是可变的,一般包含可变长度的字段,如VARCHAR或BLOB。Fixed的长度则是固定的,只包含固定长度的列,如CHAR和 INTEGER。Compressed的行则只在压缩表中存在。
  • Rows:表的行数。对于MyISAM和其他一些存储引擎,该值是精确的,但对于InnoDB,该值是估计值。
  • Avg_row_length:平均每行包含的字节数。
  • Data_length:表数据的大小(以字节为单位)。
  • Max_data_length:表数据的最大容量,和存储引擎有关。
  • Index_length:索引的大小,以字节为单位。
  • Data_free:对于MyISAM表,表示已分配但目前没有使用的空间。这部分空间包括了之前删除的行,以及后续可以被INSERT利用到的空间。
  • Auto_increment:下一个AUTO_INCREMENT的值。
  • Create_time:表的创建时间。
  • Update_time:表数据的最后修改时间。
  • Check_time:使用CKECK TABLE命令或这myisamchk工具最后一次检查表的时间。
  • Collation:表的默认字符集和字符列排序规则。
  • Checksum:如果启用,保存的是整个表的实时校验和。
  • Create_options:创建表时指定的其他选项。
  • Comment:该列包含了一些其他的额外信息。对应MyISAM表,保存的是表创建时的注释。对应InnoDB表,保存的是InnoDB表空间的剩余空间信息。如果是一个视图,则该列包含"VIEW"的文件字样。

InnoDB存储引擎

InnoDB时MySQL的默认事务性引擎,也是最重要、使用最广泛的存储引擎。它被设计用来处理大量的短期(short-llived)事务,短期事务大部分情况时正常提交的,很少被回滚。InnoDB的性能和自动崩溃恢复特性,使得它在非事务型存储的需求中也很流行。除非有非常特别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎。

InnoDB概览

  • InnoDB的数据存储在表空间(tablespace)中,表空间时有InnoDB管理的一个黑盒子,由一系列的数据文件组成。在MySQL 4.1以后的版本中,InnoDB可以将每个表的数据和索引存放在单独 的数据文件中。也可以使用裸设备作为表空间的存储介质。
  • InnoDB采用MVCC来支持高并发,并且实现了四个标准的隔离级别。其默认级别是REPEATABLE READ(可重复读),并且通过间隙锁(next-key locking)策略防止幻读的出现。间隙锁是的InnoDB不仅仅锁定查询设计的行,还会对索引中的间隙进行锁定,以防止幻影行的插入。
  • InnoDB表是基于聚簇索引创建的。聚簇索引对主键查询有很高的性能。不过它的二级索引(secondary index,非主键索引)中必须包含主键列,所以如果主键列很大的话,其他的所有索引都会很大。因此,若表上的索引较多的话,主键应当尽可能的小。InnoDB的存储格式是平台独立的,也就是说可以将数据和索引文件从Intel平台复制到PowerPC或者Sun SPARC平台。
  • InnoDB内部做了很多优化,包括从磁盘读取数据时采用的可预测性预读,能够自动在内存中创建hash索引以加速读操作的自适应哈希索引(adaptive hash index),以及能够加速插入操作的插入缓冲区(insert buffer)等。
  • 作为事务型的存储引擎,InnoDB通过一些机制和工具支持真正的热备份,Oracle提供的MySQL Enterprise Backup、Percona提供的开源的XtraBackup都可以做到这一点。MySQL的其他存储引擎不支持热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。

MyISAM存储引擎

在MySQL 5.1及以前的版本中,MyISAM是默认的存储引擎。MyISAM提供了大量的特性,包括全文索引,压缩,空间函数(GIS)等,但MyISAM不支持事务和行级锁,而且崩溃后无法安全恢复。对于只读的数据,或者表比较小,可以忍受修改(repair)操作,则依然可以使用MyISAM(应该默认使用InnoDB)。

存储

MyISAM会将表存储在两个文件中:数据文件和索引文件。分布以.MYD和.MYI为扩展名。MyISAM可以包含动态或静态(长度固定)的行。MySQL会根据表的定义来决定采用何种行格式。MyISAM表可以存储的行记录数,一般受限于可用的磁盘空间,或者操作系统中单个文件的最大尺寸。

MyISAM特性

  • 加锁与并发
    MyISAM对整张表加锁,而不是针对行。读取时会对需要读到的所有表加共享锁,写入时则对表加排他锁。取查询的同时,也可以往表中插入新的记录(这被称为并发插入,CONCURRENT INSERT)。
  • 修复
    对于MyISAM表,MySQL可以手工或者自动执行检查和修复操作。执行表的修复可能导致一些数据丢失,而且修复操作是非常慢的。
  • 索引特性
    对应MyISAM表,即使是BLOB和TEXT等长字段,可以基于其前500个字符创建索引。MyISAM也支持全文索引,这是一种基于分词创建的索引,可以支持复杂的查询。
  • 延迟更新索引键(Delayed Key Write)
    创建MyISAM表的时候,如果指定了DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立刻将修改的索引数据写入磁盘,而是写入到内存中的键缓冲区(in-memory key buffer),只有在清理缓冲区或关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提高写入性能,但是在数据库或主机崩溃时会造成索引损坏,需要执行修复操作。延迟更新索引键的特性,可以在全局设置,也可以为单个表设置。
  • MyISAM压缩表
    如果表在创建并导入数据以后,不会再进行修改操作,那么这样的表或许适合采用MyISAM压缩表。

可以使用myisampack 对MyISAM表进行压缩(也叫打包pack)。压缩表是不能进行修改的(除非先将表解除压缩,修改数据,然后再次压缩)。压缩表可以极大地减少磁盘空间占用,因此也可以减少磁盘I/O,从而提升查询性能。压缩表也支持索引,但索引也是只读的。

以现在的硬件能力,对大多数应用场景,读取压缩表数据时的解压带来的开销影响并不大,而减少I/O带来的好处则要大得多。压缩时表中的记录是独立压缩的,所以读取单行的时候不需要去解压整个表(甚至也不解压行所在的整个页面)。

  • MyISAM性能
    MyISAM引擎设计简单,数据以紧密格式存储,所以在某些场景下的性能很好。MyISAM有一些服务器级别的性能扩展限制,比如对索引键缓冲区(key cache)的Mutex锁,MariaDB基于段(segment)的索引键缓冲区机制来避免该问题。但MyISAM最典型的性能问题还是表锁的问题,如果你发现所有的查询都长期处于“Locked”状态,那么毫无疑问表锁就是罪魁祸首。

MySQL内建的其他存储引擎

MySQL还有一些有特殊用途的存储引擎。在新版本中,有些可能因为一些原因已经不再支持;另外还有些会继续支持,但是需要明确地启用后才能使用。

Archive引擎

Archive存储引擎只支持INSERT 和SELECT 操作,在MySQL 5.1之前也不支持索引。

Archive引擎不是一个事务型的引擎,而是一个针对高速插入和压缩做了优化的简单引擎。

Archive引擎会缓存所有的写并利用zlib对插入的行进行压缩,所以比MyISAM表的磁盘I/O更少。但是每次SELECT查询都需要执行全表扫描。所以Archive表适合日志和数据采集类应用,这类应用做数据分析时往往需要全表扫描。或者在一些需要更快速的INSERT 操作的场合下也可以使用。

Blackhole引擎

Blackhole引擎没有实现任何的存储机制,它会丢弃所有插入的数据,不做任何保存。但是服务器会记录Blackhole表的日志,所以可以用于复制数据到备库,或者只是简单地记录到日志。这种特殊的存储引擎可以在一些特殊的复制架构和日志审核时发挥作用。但这种应用方式我们碰到过很多问题,因此并不推荐。

CSV引擎

CSV引擎可以将普通的CSV文件(逗号分割的文件)作为MySQL的表来处理,但这种表不支持索引。CSV引擎可以在数据库运行时拷入或者拷出文件。可以将Excel等电子表格软件中的数据存储为CSV文件,然后复制到MySQL数据目录下,就能在MySQL中打开使用。同样,如果将数据写入到一个CSV引擎表,其他的外部程序也能立即从表的数据文件中读取CSV格式的数据。因此CSV引擎可以作为一种数据交换的机制,非常有用。

Federated引擎

Federated引擎是访问其他MySQL服务器的一个代理,它会创建一个到远程MySQL服务器的客户端连接,并将查询传输到远程服务器执行,然后提取或者发送需要的数据。最初设计该存储引擎是为了和企业级数据库如Microsoft SQL Server和Oracle的类似特性竞争的,可以说更多的是一种市场行为。尽管该引擎看起来提供了一种很好的跨服务器的灵活性,但也经常带来问题,因此默认是禁用的。MariaDB使用了它的一个后续改进版本,叫做FederatedX。

Memory引擎

  • 如果需要快速地访问数据,并且这些数据不会被修改,重启以后丢失也没有关系,那么使用Memory表(以前也叫做HEAP表)是非常有用的。Memory表至少比MyISAM表要快一个数量级,因为所有的数据都保存在内存中,不需要进行磁盘I/O。Memory表的结构在重启以后还会保留,但数据会丢失。
  • Memroy表在很多场景可以发挥好的作用:
    用于查找(lookup)或者映射(mapping)表,例如将邮编和州名映射的表。
    用于缓存周期性聚合数据(periodically aggregated data)的结果。
    用于保存数据分析中产生的中间数据。
  • Memory表支持Hash索引,因此查找操作非常快。虽然Memory表的速度非常快,但还是无法取代传统的基于磁盘的表。Memroy表是表级锁,因此并发写入的性能较低。它不支持BLOB 或TEXT 类型的列,并且每行的长度是固定的,所以即使指定了VARCHAR 列,实际存储时也会转换成CHAR ,这可能导致部分内存的浪费(其中一些限制在Percona版本已经解决)。
  • 如果MySQL在执行查询的过程中需要使用临时表来保存中间结果,内部使用的临时表就是Memory表。如果中间结果太大超出了Memory表的限制,或者含有BLOB 或TEXT 字段,则临时表会转换成MyISAM表。
  • 人们经常混淆Memory表和临时表。临时表是指使用CREATE TEMPORARY TABLE 语句创建的表,它可以使用任何存储引擎,因此和Memory表不是一回事。临时表只在单个连接中可见,当连接断开时,临时表也将不复存在。

NDB集群引擎

2003年,当时的MySQL AB公司从索尼爱立信公司收购了NDB数据库,然后开发了NDB集群存储引擎,作为SQL和NDB原生协议之间的接口。MySQL服务器、NDB集群存储引擎,以及分布式的、share-nothing的、容灾的、高可用的NDB数据库的组合,被称为MySQL集群(MySQL Cluster)。

面向列的存储引擎

MySQL默认是面向行的,每一行的数据都是以前存储的,服务器的查询也是以行为单位处理的。而在面向大数据处理时,面向列的方式可能效率更高。如果不需要整行的数据,面向列的方式可以传输更少的数据。如果每一列都单独存储,那么压缩的效率也会更高。

Infobright是最有名的面向列的存储引擎。在非常大的数据量(数十TB)时,该引擎工作良好。Infobright是为数据分析和数据仓库应用设计的。数据高度压缩,按照块进行排序,每个块都对应有一组元数据。在处理查询时,访问元数据可决定跳过该块,甚至可能只需要元数据即可满足查询的需求。但该引擎不支持索引,不过在这么大的数据量级,即使有索引也很难发挥作用,而且块结构也是一种准索引(quasi-index)。Infobright需要对MySQL服务器做定制,因为一些地方需要修改以适应面向列存储的需要。如果查询无法在存储层使用面向列的模式执行,则需要在服务器层转换成按行处理,这个过程会很慢。Infobright有社区版和商业版两个版本。

选择合适的引擎

  • 大部分情况下,InnoDB都是正确的选择,所以Oracle在MySQL 5.5版本时将InnoDB作为默认的存储引擎了。对应如何选择存储引擎,可以简单的归纳为一句话,除非需要用到某些InnoDB不具备的特性,并且没有其他办法可以替代,否则都应该优先选择InnoDB引擎。例如,如果要用到全文索引,建议优先考虑InnoDB加上Sphinx的组合,而不是使用支持全文索引的MyISAM。如果不在乎可扩展能力和并发能力,也不在乎崩溃后的数据丢失问题,却对InnoDB的空间占用过多比较敏感,这种情况下选择MyISAM比较合适。
  • 除非万不得已,否则建议不要混合使用多种存储引擎,否则可能带来一系列复杂的问题,以及一些潜在的bug和边界问题。存储引擎层和服务器层的交互已经比较复杂,更不用说混合多个存储引擎了。至少,混合存储对一致性备份和服务器参数配置都带来了一些困难。
  • 如果应用需要不同的存储引擎,请先考虑如下几个因素:
  1. 事务
    如果应用需要事务支持,那么InnoDB(或者XtraDB)是目前最稳定并且经过验证的选择。如果不需要事务,并且主要是SELECT 和INSERT 操作,那么MyISAM是不错的选择。一般日志型的应用比较符合这一特性。
  2. 备份
    备份的需求也会营销存储引擎的选择。如果可以定期的关闭服务器来执行备份,那么备份的因素可以忽略。反之,如果需要支持在线热备份,InnoDB就是基本的需求。
  3. 崩溃恢复
    数据量比较大的书画,系统崩溃后如何快速地恢复是一个需要考虑的问题。相对而言,MyISAM奔溃后发生损坏的概率比InnoDB高的多,而且恢复速度也很慢。因此,即使不需要事务支持,很多人选择InnoDB引擎,这是一个比较重要的因素。
  4. 特有的特性
    最后,有些应用可能依赖一些存储引擎所独有的特性或者优化,比如很多应用依赖聚簇索引的优化。另外,MySQL中也只有MyISAM支持地理空间搜索。如果一个存储引擎拥有一些关键的特性,同时却又缺乏一些必要的特性,那么有时候不得不做折中的考虑,或者在架构设计上做一些取舍。某些存储引擎无法直接支持的特性,有时候通过变通也可以满足需求。

日志型应用

假设你需要实时地记录一台中心电话交换机的每一通电话的日志到MySQL中,或者通过Apache的mod_log_sql 模块将网站的所有访问信息直接记录到表中。这一类应用的插入速度有很高的要求,数据库不能成为瓶颈。MyISAM或者Archive存储引擎对这类应用比较合适,因为它们开销低,而且插入速度非常快。

如果需要对记录的日志做分析报表,则事情就会变得有趣了。生成报表的SQL很有可能会导致插入效率明显降低,这时候该怎么办?

  1. 一种解决办法,是利用MySQL内置的复制方案将数据复制一份到备库,然后在备库上执行比较消耗时间和CPU的查询。这样主库只用于高效的插入工作,而备库上执行的查询也无须担心影响到日志的插入性能。当然也可以在系统负载较低的时候执行报表查询操作,但应用在不断变化,如果依赖这个策略可能以后会导致问题。
  2. 另外一种方法,在日志记录表的名字中包含年和月的信息,比如web_logs_2012_01 或者web_logs_2012_jan 。这样可以在已经没有插入操作的历史表上做频繁的查询操作,而不会干扰到最新的当前表上的插入操作。

只读或者大部分情况下只读的表

有些表的数据用于编制类目或分列清单(如工作岗位、 竞拍、不动产等),这种是典型的读多写少的业务。如果不介意MyISAM的崩溃恢复问题,选择MyISAM引擎是合适的。有些存储引擎不会保证将数据安全的写入到磁盘中,而许多用户并不清除这样有多大的风险(MyISAM只是将数据写入到内存中,等待操作系统定期将数据刷出到磁盘上)。

一个值得推荐的方式,是在性能测试环境模拟真实的环境,运行应用,然后拔下电源模拟崩溃测试。对崩溃恢复的第一手测试经验是无价之宝,可以避免真的碰到崩溃时手足无措。

不要轻易相信“MyISAM比InnoDB快”之类的经验之谈,这个结论往往不是绝对的。在很多场景中,InnoDB的速度都可以让MyISAM望尘莫及,尤其是使用到聚簇所有,或者需要访问的数据都可以放入到内存中的引用。如数据大小,IO请求量,主键还是二级索引都会影响到存储引擎的性能。
当设计上述类型的应用时,建议采用InnoDB。MyISAM引擎在一开始可能没有任何问题,但随着应用压力的上升,则可能迅速恶化。各种锁争用、崩溃后的数据丢失等问题都会随之而来。

订单处理

如果涉及订单处理,那么支持事务就是必要选项。半完成的订单是无法用来吸引客户的。另外一个重要的考虑点是存储引擎对外键的支持情况。InnoDB是订单处理类应用的最佳选择。

电子公告牌和主题讨论论坛

当前有成百上千的基于PHP或者Perl的免费系统可以支持主题讨论。其中大部分的数据库操作效率都不高,因为它们大多倾向于在一次请求中执行尽可能多的查询语句。主题讨论区一般都有更新计数器,并且会为各个主题计算访问统计信息。多数应用只设计了几张表来保存所有的数据,所以核心表的读写压力非常大。为保证这些核心表的数据一致性,锁称为资源争用的主要因素。

尽管有这些设计缺陷,但大多数应用在中低负载时可以工作得很好。如果Web站点的规模迅速扩展,流量随之猛增,则数据库访问可能变得非常慢。此时一个典型的解决方案是更改为支持更高读写的存储引擎,但有时用户会发现这么做反而导致系统变得更慢了。用户可能没有意识到这是由于某些特殊查询的缘故,典型的如:

mysql> SELECT COUNT(*) FROM table;

问题在于,不是所有的存储引擎运行上述查询都非常快,对应MyISAM确实很快,但其他的可能就不行。每种存储引擎都能找出类似的对自己有利的例子。

CD-ROM应用

如果要发布一个基于CD-ROM或者DVD-ROM并且使用MySQL数据文件的应用,可以考虑使用MyISAM表或者MyISAM压缩表,这样表之间可以隔离并且可以在不同介质上相互拷贝。MyISAM压缩表比未压缩的表要节约很多空间,但压缩表是只读的。在某些应用中这可能是个大问题。但如果数据放到只读介质的场景下,压缩表的只读特性就不是问题,就没有理由不采用压缩表了。

大数据量

什么样的数据量算大?我们创建或者管理的很多InnoDB数据库的数据量在3~5TB之间,或者更大,这是单台机器上的量,不是一个分片(shard)的量。这些系统运行得还不错,要做到这一点需要合理地选择硬件,做好物理设计,并为服务器的I/O瓶颈做好规划。在这样的数据量下,如果采用MyISAM,崩溃后的恢复就是一个噩梦。

如果数据量继续增长到10TB以上的级别,可能就需要建立数据仓库。Infobright是MySQL数据仓库最成功的解决方案。也有一些大数据库不适合Infobright,却可能适合TokuDB。

转换表的引擎

有很多种方法可以将表的存储引擎转换成另外一种引擎。每种方法都有其优点和缺点。

ALTER TABLE

将表从一个引擎修改另一个引擎最简单的方法是使用ALTER TABLE语句。如下将mytable的引擎修改为InnoDB:

mysql> ALTER TABLE mytable ENGINE=InnoDB;
  • 上述语法可以使用任何存储引擎。但却需要执行很长时间。MySQL会按行将数据从原表复制到一张新表中。在复制期间会消耗掉系统所有的IO能力,同时原表上会加上读锁。所以,在繁忙的表上执行此操作要特别小心。一个替代方案是采用接下来将讨论的导出与导入的方法,手工进行表的复制。
  • 如果转换表的存储引擎,将会失去和原引擎相关的所有特性。例如,如果将一张InnoDB表转换为MyISAM,然后再转换回InnoDB,原InnoDB表上所有的外键将丢失。

导出与导入

可以使用mysqldump工具将数据导出到文件,然后修改文件中CREATE TABLE语句的存储引擎选项。注意修改表名,因为同一个数据库不能存在相同的表名,即使它们使用不同的存储引擎。同时要注意mysqldump 默认会自动在CREATE TABLE 语句前加上DROP TABLE 语句,不注意这一点可能会导致数据丢失。

创建与查询(CREATE和SELECT)

我们可以先创建一个新的存储引擎的表,然后利用INSERT…,SELECT语法来到数据:

mysql> CREATE TABLE innodb_table LIKE myisam_table;
mysql> ALTER TABLE innodb_table ENGINE=InnoDB;
mysql> INSERT INTO innodb_table SELECT * FROM myisam_table;

数据量不大的话,这样做工作的很好。如果数据量很大,可以考虑做分批处理,针对每一段数据执行事务提交操作,避免大事务产生过多的undo。假设有主键字段id,重复运行以下语句将数据导入到新表:

mysql> START TRANSACTION;
mysql> INSERT INTO innodb_table SELECT * FROM myisam_table WHERE id BETWEEN x AND y;
mysql> COMMIT;

这样操作完以后,新表是原表的一个全量赋值,原表还在,如果需要可以删除原表。如果有必要,可以在执行的过程中对原表加锁,以确保新表和原表的数据一致。

Percona Toolkit提供了一个pt-online-schema-change 的工具(基于Facebook的在线schema变更技术),可以比较简单、方便地执行上述过程,避免手工操作可能导致的失误和烦琐。

总结

  • MySQL拥有分层的架构。上层是服务器层的服务和查询执行引擎,下层则是存储引擎。虽然有很多不同作用的插件API,但存储引擎API还是最重要的。如果能够立即MySQL在存储引擎和服务层直接处理查询时如果API来回交互,就能抓住MySQL的核心基础架构的精髓。
  • MySQL最初基于ISAM构建(后来被MyISAM取代),其后续添加了更多的存储引擎和事务支持。MySQL有一些怪异的行为是由于历史遗留导致的。例如,在执行ALTER TABLE 时,MySQL提交事务的方式是由于存储引擎的架构直接导致的,并且数据字典也保存在.frm 文件中(这并不是说InnoDB会导致ALTER变成非事务型的。对于InnoDB来说,所有的操作都是事务)。
  • 当然,存储引擎API的架构也有一些缺点。有时候选择多并非好事,而在MySQL 5.0和MySQL 5.1中有太多的存储引擎可以选择。InnoDB对于95%以上的用户来说都是最佳选择,所以其他的存储引擎可能只是让事情变得复杂难搞,当然也不可否认某些情况下某些存储引擎能更好地满足需求。
  • Oracle一开始收购了InnoDB,之后又收购了MySQL,在同一个屋檐下对于两者都是有利的。InnoDB和MySQL服务器之间可以更快地协同发展。MySQL依然基于GPL协议开放全部源代码,社区和客户都可以获得坚固而稳定的数据库,MySQL正在变得越来越可扩展和有用。

参考<高性能SQL>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值