存储引擎在mysql体系架构中位于第三层, 负责mysql中的数据存储和提取,根据mysql提供的文件访问层抽象接口定制的一种文件访问机制.
使用show engines命令可以查看当前数据库支持的引擎信息.
从截图可看到, mysql 默认的存储引擎是InnoDB,支持事务,行锁,外键,支持分布式事务(XA),支持保存点(回滚).
但是mysql5.5版本之前默认采用的是MyISAM存储引擎.
也可以通过以下语句查看默认的存储引擎:
show variables like '%default_storage_engine%';
mysql 自动存储引擎介绍
InnoDB存储引擎
InnoDB是从5.5版本开始的mysql默认事务型存储引擎, 它被设计用来处理大量的短期(short-lived)事务. 可以确保事务的完整提交(Commit)和回滚(Rollback).InnoDB 不仅缓存索引还要缓存真实数据,对内存要求比较高,而且内存大小对性能有决定性的影响.其数据文件结构:
(1)表名.frm 存储表结构(mysql8 开始,合并在表名.ibd中) ;
(2)表名.ibd 存储数据和索引
InnoDB 存储引擎的特点是行锁设计、支持外键,默认读取操作不会产生锁。
MyISAM存储引擎
MySIAM 是5.5版本之前的默认存储引擎, 提供了大量的特性,包括全文索引,压缩,空间函数(GIS)等,但是MyISAM不支持事务和行级锁,其最大的缺陷是崩溃后无法安全恢复,其优点是访问的速度快,对事务完整性没有要求或者以SELECT、INSERT为主的应用。MyISAM只缓存索引,不缓存真实数据。
数据文件格式:
(1)表名.frm存储表结构
(2)表名.MYD存储数据
(3)表名.MYI存储索引
Archive 存储引擎
Archive档案存储引擎只支持INSERT和SELECT 操作,只适合日志和数据采集类应用。 根据测试结论来看,Archive表比MyISAM表要小大约75%,比支持事务处理的InnoDB表小大约83%。
Blockhole存储引擎
BlackHole引擎没有实现任何存储机制,它会丢弃所有插入的数据,不会任何保存。但是服务器会记录Blackhole表的日志,所以可以用于复制数据到备份库,或者简单地记录到日志。但是在实际应用中会碰到很多问题,不推荐。
CSV 存储引擎
CSV存储引擎可以将普通的csv文件作为MYSQL的表来处理,但不支持索引,可以作为一种数据交换的机制是非常有用的。CSV存储的数据直接可以在操作系统中使用文本编辑器或者excel读取。
Memory存储引擎
如果需要快速地访问数据,并且这些数据不会被修改, 重启以后丢失也没有关系,那么就很适合使用Memory存储引擎, Memory表至少比MyISAM 表要快一个数量级。
Federated存储引擎
Federated 引擎是访问其他MYSQL服务器的的一个代理(跨库关联查询),尽管该引擎看起来提供了一种很好的跨服务器的灵活性,但是问题也不少, 默认情况下是禁用的。
InnoDB和MyISAMD对比
事务和外键
InnoDB 支持事务和外键,具有安全性和完整性,适合大量insert或update操作;
myISAM 不支持事务和外键,它提供高速存储和检索,适合大量的select 查询操作。
锁机制
InnoDB 支持行级锁,锁定指定记录,基于索引来加锁实现;
MyISAM 支持表级锁,锁定整张表。
索引结构
InnoDB 使用聚集索引(聚族索引),索引和记录在一起存储,即缓存索引,也缓存记录;
MyISAM 使用非聚集索引,索引和记录分开。
并发处理能力
InnoDB 读写阻塞可以与隔离级别有关,可以采用多版本并发控制(MVCC)来支持高并发;
MyISAM使用表锁,会导致写操作并发率低,读之间并不阻塞,读写阻塞。
存储文件
InnoDB 表对应两个文件,.frm 表结构文件,.Ibd数据文件。InnoDB表最大支持64TB.
MyISAM 对应三个文件,.frm 表结构文件,.MYD表数据结构,.MYI索引文件。从Mysql5.0开始默认限制是256TB。
使用场景
MyISAM:
- 不需要事务支持;
- 并发相对较低;
- 以数据查询(读)为主,数据修改相对较少;
- 数据一致性要求不高。
InnoDB
- 需要事务支持(具有较好的事务特性);
- 行级锁定对高并发有很好的适应能力;
- 数据更新较为频繁的场景;
- 数据一致性要求高;
- 硬件设备内存较大,可以利用InnoDB比较好的缓存能力来提高内存利用率,较少磁盘IO。
InnoDB存储结构
从Mysql5.5 版本开始默认使用InnoDB 作为引擎,它擅长处理事务,具有自动崩溃恢复的特性,在日常开发中使用非常广泛,官方的InnoDB引擎架构图如下:
主要分为内存结构和磁盘结构,
内存结构(In-Memory Structures)
内存结构主要包括Buffer Pool、Change Buffer、Adaptive Hash Index 和Log Buffer 四大组件。
Buffer Pool
缓冲池,简称BP,以page页为单位,默认大小16K,BP的底层采用链表数据结构管理page。在InnoDB访问表记录和索引时会在Page页中缓存,以后使用可以减少磁盘IO操作,提升效率。
Buffer Pool 是主内存的一个区域, 主要用于缓存热点数据和索引页;
Buffer Pool 可以看做一个list ,采用中点插入的改进型LRU(最近少使用页面淘汰算法)管理存储区域。当需要空间将new page 添加到buffer pool时,最近最久未使用的page 会被淘汰,new page会从list 的中点进行插入,并把list分割成两个sublist:
- 在头部,存储的是最近访问的new page;
- 在尾部,存储的是最近访问较少的old page;
改型的LRU算法将常用页面保留在new sublist,最近最少使用的页面会保存在old sublist,包括即将被淘汰的page,
默认情况下, 改进型LRU算法按以下方式进行操作:
- 3/8 的buffer pool 用于old sublist;
- list 的中间点是new sublist 的tail 和old sublist 的head的临界点;
- 当InnoDB 读取一个page插入到buffer pool时,初始位置是list的中点(old sublist 的head),可以读取页面,是因为它是用户启动的操作(例如SQL 查询)所必需的,例如SQL 查询,InnoDB自动执行的异步随机预读操作(MYSQL 5.6引入)
- 在old sublist 列表中访问一个页面会使其年轻,并将page移到new sublist的头部。如果由于用户的启动操作需要而读取页面,则会立即进行首次访问,并且页面将变为年轻页面。如果由于预读操作而读取了页面,则第一次访问不会立即发生,并且有可能在页面被淘汰之前都不会被访问。
- 随着数据库的运行,缓冲池中未被访问的页会通过向列表尾部移动来“老化”。新子列表和旧子列表中的页面会随着其他页面成为新页面而老化。旧子列表中的页面也会随着页面在中点插入而老化。最终,未使用的页面到达旧子列表的尾部并被淘汰。
Log Buffer
日志缓冲区,用来保存要写入磁盘上log文件(Redo/Undo)的数据,日志缓冲区的内容定期刷新到磁盘log文件中。日志缓冲区满时会自动将其刷新到磁盘,当遇到BLOG或者多行更新的大事务操作时,增加日志缓冲区可以节省磁盘IO。
innodb_flush_log_at_trx_commit 控制如何将日志缓冲区的内容写入并刷新到磁盘;
innodb_flush_log_at_timeout 变量控制日志刷新频率
Adaptive Hash Index
自适应哈希索引,用于优化对BP数据的查询。InnoDB 存储引擎会监控对表索引的查找,如果观察到建立哈希索引可以带来速度的提升,则建立哈希索引,所以称之自适应。InnoDB存储引擎会自动根据访问的频率和模式来为某些页建立索引。
Change Buffer
Change Buffer是一种特殊的数据结构,简称CB,当辅助索引页不在缓冲池中时,它会缓存对这些页所做的更改。缓冲更改(可能由Insert、Update或delete 等DML操作 产生),然后通过其他读取操作将页加载到缓冲池中时合并。
change buffer 中缓存的数据类型有innodb_change_buffering 控制。Change buffer 默认情况下栈缓冲池总大小的25%,最大值可设置50%,通过innodb_change_buffer_max_size可设置 。如果Change Buffer 占用了缓冲池共享内存空间太多,会导致页面比预期更快的从缓冲池中淘汰。innodb_change_buffer_max_size变量是动态的,它允许在不重新启动服务器的情况下修改设置。
当更新一条记录时,该记录在BufferPool存在,直接在BufferPool修改,一次内存操作。如果该记录在BufferPool不存在(没有命中),会直接在ChangeBuffer进行一次内存操作,不用再去磁盘查询数据,避免一次磁盘IO。当下次查询记录时,会先进性磁盘读取,然后再从ChangeBuffer中读取信息合并,最终载入BufferPool中。
如果在索引设置唯一性,在进行修改时,InnoDB必须要做唯一性校验,因此必须查询磁盘做一次IO操作,会直接将记录查询到BufferPool中,然后在缓冲池修改,不会在
ChangeBuffer操作。如果二级索引或主键中包含降序索引,则不支持change cache;
磁盘结构(On-Disk Structures)
InnoDB 磁盘主要包含Tables、indexes、TableSpaces、Doublewrite Buffer、Redo log、Undo logs
TableSpaces
InnoDB存储引擎的所有数据都被逻辑地存放一个空间中,称为表空间(tablespace)。表空间由段(segment)、区(extent)、页(page)、行(row)组成,用于存储多个ibd数据文件、存储表的记录和索引。ibd数据文件包含多个段。如下图所示InnoDB的逻辑存储结构:
段(Segment):用于管理多个Extent,分为数据段(Leaf node segment)、索引段(Non-leaf node segment)、回滚段(Rollback segment)。一个表至少会有两个segment,一个管理数
据,一个管理索引。每多创建一个索引,会多两个segment。默认的extent大小为1M,即64个16KB的Page。
区(Extent):一个区固定包含64个连续的页,大小为1M。当表空间不足,需要分配新的页资源,不会一页一页分,直接分配一个区。
页(Page):用于存储多个Row行记录,大小为16K。包含很多种页类型,比如数据页,undo页,系统页,事务数据页,大的BLOB对象页。Page是文件最基本的单位,无论何种类型的page,都是由page header,page trailer和page body组成。一个page 默认是16KB.
行(Row):包含了记录的字段值,事务ID(Trx id)、滚动指针(Roll pointer)、字段指针(Field pointers)等信息。
表空间又分为系统表空间、独立表空间、通用表空间、临时表空间、Undo表空间等多种类型;
系统表空间(The System Tablespace)
系统表空间是双写缓冲区和更改缓冲区的存储区域。如果在系统表空间中创建表,而不是在每个表文件或常规表空间中创建表,则它也可能包含表和索引数据。在以MySQL8.0之前系统表空间包含InnoDB数据字典。在MySQL 8.0中,InnoDB将元数据存储在MySQL数据字典中。
系统表空间可以有一个或多个数据文件。缺省情况下,将在数据目录中创建一个名为 ibdata1 的单个系统表空间数据文件。系统表空间数据文件的大小和数量由innodb_data_file_path启动选项定义。
增加系统表空间大小的最简单方法是将其配置为自动扩展:
innodb_data_file_path=ibdata1:10M:autoextend
autoextend指定该属性后,数据文件将根据需要的空间自动增加8MB的大小。
独立表空间( File-Per-Table Tablespaces)
每个独立表空间包含单个InnoDB表的数据和索引, 并存储在文件系统中自己的数据文件中。innodb_file_per_table选项默认开启,表将被创建于表空间中。否则,
innodb将被创建于系统表空间中。每个表文件表空间由一个.ibd数据文件代表,该文件
默认被创建于数据库目录中。表空间的表文件支持动态(dynamic)和压缩(commpressed)行格式。
通用表空间(General Tablespaces)
通用表空间为通过create tablespace语法创建的共享表空间。通用表空间可以创建于
mysql数据目录外的其他表空间,其可以容纳多张表,且其支持所有的行格式。
CREATE TABLESPACE ts1 ADD DATAFILE ts1.ibd Engine=InnoDB; //创建表空
间ts1
CREATE TABLE t1 (c1 INT PRIMARY KEY) TABLESPACE ts1; //将表添加到ts1
表空间
撤销表空间(Undo Tablespaces)
撤销表空间由一个或多个包含Undo日志文件组成。在MySQL 5.7版本之前Undo占用的
是System Tablespace共享区,从5.7开始将Undo从System Tablespace分离了出来。
InnoDB使用的undo表空间由innodb_undo_tablespaces配置选项控制,默认为0。参
数值为0表示使用系统表空间ibdata1;大于0表示使用undo表空间undo_001、
undo_002等。
临时表空间(Temporary Tablespaces)
分为session temporary tablespaces 和global temporary tablespace两种。session
temporary tablespaces 存储的是用户创建的临时表和磁盘内部的临时表。global
temporary tablespace储存用户临时表的回滚段(rollback segments )。mysql服务
器正常关闭或异常终止时,临时表空间将被移除,每次启动时会被重新创建。
Doublewrite Buffer(双写缓冲区)
doublewrite缓冲区是系统表空间中的存储区域,在其中InnoDB写入从缓冲池中刷新的页面,然后再将它们写入数据文件中的适当位置。仅在刷新页面并将其写入doublewrite缓冲区之后,才将页面InnoDB写入其正确位置。如果在页面写入过程中发生操作系统,存储子系统或mysqld进程崩溃,则InnoDB可以在崩溃恢复期间从doublewrite缓冲区中找到页面的良好副本。
尽管数据总是被写入两次,但双写缓冲区不需要两倍的I / O开销或两倍的I / O操作。只需一次fsync()调用操作系统,就可以将数据作为大的连续块写入双写缓冲区。
在大多数情况下,默认情况下会启用双写缓冲区。要禁用双写缓冲区,请设置innodb_doublewrite=0。
Redo log(重做日志)
重做日志是一种基于磁盘的数据结构,Redo Log 是指事务中修改的任何数据,将最新的数据备份存储的位置;以恢复操作为目的,在数据库发生意外时重现操作。
随着事务操作的执行,就会生成Redo log,在事务提交时会将产生Redo Log 写入Log Buffer,并不是随着事务的提交就立刻写入磁盘文件。等事务操作的脏页写入到磁盘之后,Redo Log的使命也就完成了,Redo Log占用的空间就可以重用。
-
Redo Log 工作原理:
防止在发生故障的时间点,尚有脏页未写入标的IBD 文件中,在重启MYSQL 服务的时候,根据Redo Log进行重做,从而达到事务持久化特定。
-
Redo Log 写入机制
Redo Log 文件内容是以顺序循环的方式写入文件,写满时则回溯到第一个文件进行覆盖。
write pos 是当前记录的位置,一边写一边后移,写到最后一个文件末尾后就回到 0 号文件开头;
checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件;
write pos 和 checkpoint 之间还空着的部分,可以用来记录新的操作。如果 write pos 追上checkpoint,表示写满,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint
推进一下。
- Redo Log相关配置参数
每个InnoDB存储引擎至少有1个重做日志文件组(group),每个文件组至少有2个重做日志文
件,默认为ib_logfile0和ib_logfile1。可以通过下面一组参数控制Redo Log存储:
show variables like '%innodb_log%';
Redo Buffer 持久化到 Redo Log 的策略,可通过 Innodb_flush_log_at_trx_commit 设置:
- 0:每秒提交 Redo buffer ->OS cache -> flush cache to disk,可能丢失一秒内的事务数据。由后台Master线程每隔 1秒执行一次操作。
- 1(默认值):每次事务提交执行 Redo Buffer -> OS cache -> flush cache to disk,最安全,性能最差的方式。
- 2:每次事务提交执行 Redo Buffer -> OS cache,然后由后台Master线程再每隔1秒执行OS cache -> flush cache to disk 的操作。
一般建议选择取值2,因为 MySQL 挂了数据没有损失,整个服务器挂了才会损失1秒的事务提交数
据。
更改redo log 文件的数量或大小的步骤:
第一步: 停止mysql 服务器,并确保它关闭且没有错误
第二步:编辑my.cnf.innodb_log_file_size 日志文件大小设置;innodb_log_files_in_group 日志文件数据设置;
第三步:重启mysql服务。
Undo logs(撤消日志)
撤消日志是与单个读写事务关联的撤销日志记录的集合。以撤销操作为目的,返回指定某个状态的操作。
数据库事务开始之前,会将要修改的记录存放到 Undo 日志里,当事务回滚时或者数
据库崩溃时,可以利用 Undo 日志,撤销未提交事务对数据库产生的影响。
Undo Log在事务开始前产生;事务在提交时,并不会立刻删除undo log,innodb会将该事务对应的undo log放入到删除列表中,后面会通过后台线程purge thread进行回收处理。Undo Log属于逻辑日志,记录一个变化过程。例如执行一个delete,undolog会记录一个insert;执行一个update,undolog会记录一个相反的update。
undo log采用段的方式管理和记录。在innodb数据文件中包含一种rollback segment回滚段,内部包含1024个undo log segment。可以通过下面一组参数来控制Undo log存储。
show variables like '%innodb_undo%';
撤销日志存在于系统表空间、撤消表空间和临时表空间。驻留在全局临时表空间中的撤消日志用于修改用户定义的临时表中的数据的事务。这些撤消日志不会重做记录,因为崩溃恢复不需要它们。它们仅在服务器运行时用于回滚。这种类型的撤消日志通过避免重做日志I / O来提高性能。
每个撤销表空间和全局临时表空间分别支持最多128个回滚段。Innodb_rollback_segments可以修改回滚段的数量。
回滚段支持的事务数取决于回滚段中的撤销槽数以及每个事务所需的撤销日志数。回滚段中的撤消槽数因InnoDB页面大小而异。
InnoDB 页面大小 | 回滚段中撤销槽个数=InnoDB页面大小/16 |
---|---|
4096(4KB) | 256 |
8192(8KB) | 512 |
16384(16KB) | 1024 |
32768(32KB) | 2048 |
65536(64KB) | 4096 |
一个事务最多可以分配四个撤消日志,以下每种操作类型都可以分配一个:
- INSERT 用户定义表上的操作
- update 和delete 用户定义表上的操作
- INSERT 用户定义的临时表上的操作
- update 和delete 用户定义的临时表上的操作
撤销日志根据需要分配, 从分配的撤消表空间回滚段中分配了对常规表执行操作的事务的撤消日志。从已分配的全局临时表空间回滚段中分配了对临时表执行操作的事务的撤消日志。分配给事务的撤消日志在其持续时间内一直与事务相关。
如果每个事务执行任一种或一种或操作,并发读-写事务的数目即能够支持的是:INSERT、UPDATE、DELETE、InnoDB
(innodb_page_size / 16) * innodb_rollback_segments * number of undo tablespaces
如果每个交易执行和一个或操作,即并发读-写事务的数目是能够支持的是:INSERT、UPDATE、DELETE、InnoDB
(innodb_page_size / 16 / 2) * innodb_rollback_segments * number of undo tablespaces
如果每个事务INSERT在临时表上执行操作,InnoDB则可以支持的并发读写事务数为:
(innodb_page_size / 16) * innodb_rollback_segments
如果每个交易执行和一个或操作上的临时表,并发读写的事务的数目是能够支持的是:INSERT、UPDATE、DELETE、InnoDB
(innodb_page_size / 16 / 2) * innodb_rollback_segments
InnoDB线程模型
InnoDB 存储引擎是多线程的模型, 因此其后台有多个不同的后台线程,负责处理不同的任务。
IO Thread
在InnoDB存储引擎中大量使用了AIO(Async IO)来处理写IO请求,这样可以极大提高数据库的性能。在InnoDB1.0版本之前共有4个IO Thread,分别是write,read,insert buffer和log thread,后来版本将read thread和write thread分别增大到了4个,一共有10个。可以使用innodb_read_io_threads和innodb_write_io_threads参数进行设置IO thread 。
- Read thread: 负责读取操作,将数据从磁盘加载到缓存page页,有4个;
- Write thread:负责写操作,将缓存脏页刷新到磁盘,有4个;
- Log thread: 负责将日志缓冲区内容刷新到磁盘,有1个;
- Insert buffer thread: 负责将写缓冲内容刷新到磁盘,有1个;
Purge Thread
事务提交之后,其使用的undo日志将不再需要,因此需要Purge Thread回收已经分配的undo
页。
从InnoDB 1.1版本开始,purge操作可以独立到单独的线程中进行,以此来减轻Master Thread的工作,从而提高CPU的使用率以及提升存储引擎的性能。
查看Purge thread 个数
show variables like '%innodb_purge_threads%';
从上图可看到InnoDB有4个Purge Thread, 从InnoDB 1.2以后,InnoDB 是支持多个Purge Thread的, 目的是为了进一步加快undo页的回收,同时由于Purge Thread需要离散地读取undo页,这样也能更进一步利用磁盘的随机读取性能。
Page Cleaner Thread
作用是将脏数据刷新到磁盘,脏数据刷盘后相应的redo log也就可以覆盖,即可以同步数据,又能
达到redo log循环使用的目的。会调用write thread线程处理。
show variables like '%innodb_page_cleaners%';
Master Thread
Mater thread 是InnoDB 的主线程,负责调度其他各个线程,优先级最高,作用是将缓冲池中的数据异步刷新到磁盘,保证数据的一致性。包含: 脏页的刷新(page cleaner thread)、undo页回收(purge thread)、redo日志刷新(log thread)、合并写缓冲等, 内部有两个主处理,分贝时每隔1秒和每隔10秒处理。
每1秒处理:
- 刷新日志缓冲区,刷到磁盘;
- 合并写缓冲区数据,根据IO 读写压力来决定是否操作;
- 刷新脏页数据到磁盘,根据脏页比例达到75%才操作(innodb_max_dirty_pages_pctinnodb_io_capacity)
每10秒处理:
- 刷新脏数据到磁盘
- 合并写缓冲区数据
- 刷新日志缓冲区
- 删除无用的undo页
InnoDB数据文件
File文件格式(File-Format)
在早期的InnoDB版本中,文件格式只有一种,随着InnoDB引擎的发展,出现了新文件格式,用于支持新的功能。目前InnoDB只支持两种文件格式:Antelope 和 Barracuda。
- Antelope: 先前未命名的,最原始的InnoDB文件格式,它支持两种行格式:COMPACT和
REDUNDANT,MySQL 5.6及其以前版本默认格式为Antelope。 - Barracuda: 新的文件格式。它支持InnoDB的所有行格式,包括新的行格式:COMPRESSED
和 DYNAMIC。
通过innodb_file_format 配置参数可以设置InnoDB文件格式,之前默认值为Antelope,5.7版本
开始改为Barracuda。
Row行格式(Row_format)
表的行格式决定了它的行是如何物理存储的,这反过来又会影响查询和DML操作的性能。如果在
单个page页中容纳更多行,查询和索引查找可以更快地工作,缓冲池中所需的内存更少,写入更
新时所需的I/O更少。
InnoDB存储引擎支持四种行格式:REDUNDANT、COMPACT、DYNAMIC和COMPRESSED。
- REDUNDANT 行格式
使用REDUNDANT行格式,表会将变长列值的前768字节存储在B树节点的索引记录中,其余的存储在溢出页上。对于大于等于786字节的固定长度字段InnoDB会转换为变长字段,以便能够在页外存储。 - COMPACT 行格式
与REDUNDANT行格式相比,COMPACT行格式减少了约20%的行存储空间,但代价是增加了某些操作的CPU使用量。如果系统负载是受缓存命中率和磁盘速度限制,那么COMPACT格式可能更快。如果系统负载受到CPU速度的限制,那么COMPACT格式可能会慢一些。 - DYNAMIC 行格式
使用DYNAMIC行格式,InnoDB会将表中长可变长度的列值完全存储在页外,而索引记录只包含指向溢出页的20字节指针。大于或等于768字节的固定长度字段编码为可变长度字段。DYNAMIC行格式支持大索引前缀,最多可以为3072字节,可通过innodb_large_prefix参数控制。 - COMPRESSED 行格式
COMPRESSED行格式提供与DYNAMIC行格式相同的存储特性和功能,但增加了对表和索引数据压缩的支持。
DYNAMIC和COMPRESSED新格式引入的功能有:数据压缩、增强型长列数据的页外存储和大索引前缀。
每个表的数据分成若干页来存储,每个页中采用B树结构存储;如果某些字段信息过长,无法存储在B树节点中,这时候会被单独分配空间,此时被称为溢出页,该字段被称为页外列。
在创建表和索引时,文件格式都被用于每个InnoDB表数据文件(其名称与*.ibd匹配)。修改文件
格式的方法是重新创建表及其索引,最简单方法是对要修改的每个表使用以下命令:
ALTER TABLE 表名 ROW_FORMAT=格式类型;