InnoDB、MyISAM 这样的存储引擎都是把表存储在磁盘上的,而操作系统用来管理磁盘的机制被称为 文件系统。用专业一点的表述就是:像 InnoDB、MyISAM 这样的存储引擎都是把表存储在文件系统上的。当想读取数据时,这些存储引擎会从文件系统中把数据读出来返回;当想写入数据时,这些存储引擎会把数据写回文件系统。本章介绍 InnoDB 和 MyISAM 这两个存储引擎的数据如何在文件系统中存储。
MySQL 服务器程序在启动时会到文件系统的某个目录下加载一些文件,之后运行过程中产生的数据也会存储到这个目录下的某些文件中,这个目录就称为 数据目录。下面详细介绍这个目录下具体有哪些重要的内容。
之前只接触过 MySQL 的安装目录(安装 MySQL 时可以自己指定)。安装目录下重要的 bin 目录存储了许多控制客户端程序和服务器程序的命令(许多可执行文件,如 mysql、mysqld、mysqld_safe 等几十个)。而 数据目录 用来存储 MySQL 运行过程中产生的数据,一定要与安装目录区分开。
数据目录 对应着一个系统变量 datadir。在使用客户端与服务器建立连接之后查看这个系统变量的值即可:
mysql> SHOW VARIABLES LIKE 'datadir';
+---------------+-----------------------+
| Variable_name | Value |
+---------------+-----------------------+
| datadir | /usr/local/var/mysql/ |
+---------------+-----------------------+
1 row in set (0.00 sec)从结果可以看出,在示例计算机上 MySQL 的数据目录是 /usr/local/var/mysql/,实际路径请以自己机器上的为准。
MySQL 运行过程中会产生哪些数据?当然包括用户创建的数据库、表、视图和触发器等信息。除了这些用户数据,为了程序更好地运行,MySQL 也会创建一些其他额外数据。下面详细介绍 数据目录 下的内容。
每当使用 CREATE DATABASE 数据库名 语句创建一个数据库时,在文件系统上实际发生的事很简单:每个数据库都对应数据目录下的一个子目录(或者说一个文件夹)。每当新建一个数据库时,MySQL 会做两件事:
数据目录 下创建一个和数据库名同名的子目录(或者说文件夹);db.opt 的文件,该文件中包含这个数据库的各种属性,例如数据库的字符集和比较规则。注:这是 MySQL 5.7 及更早版本的行为。MySQL 8.0 起不再创建
db.opt,数据库属性存入 InnoDB 数据字典(8.0.46 实测:新建数据库子目录为空)。
例如查看示例计算机上当前有哪些数据库:
mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| charset_demo_db |
| dahaizi |
| mysql |
| performance_schema |
| sys |
| xiaohaizi |
+--------------------+
7 rows in set (0.00 sec)可以看到示例计算机上当前有 7 个数据库,其中 charset_demo_db、dahaizi 和 xiaohaizi 是自定义的,其余 4 个是 MySQL 自带的系统数据库。再看一下数据目录下的内容:
.
├── auto.cnf
├── ca-key.pem
├── ca.pem
├── charset_demo_db
├── client-cert.pem
├── client-key.pem
├── dahaizi
├── ib_buffer_pool
├── ib_logfile0
├── ib_logfile1
├── ibdata1
├── ibtmp1
├── mysql
├── performance_schema
├── private_key.pem
├── public_key.pem
├── server-cert.pem
├── server-key.pem
├── sys
├── xiaohaizideMacBook-Pro.local.err
├── xiaohaizideMacBook-Pro.local.pid
└── xiaohaizi
6 directories, 16 files上面的清单是 MySQL 5.7 时代的目录结构。MySQL 8.0 有以下差异(8.0.46 实测):不再有 db.opt、ib_logfile0/1(8.0.30 起 redo 日志改存于 #innodb_redo 目录下的 #ib_redoN 文件);默认初始化 undo_001、undo_002 两个独立 undo 表空间;新增双写文件 #ib_16384_0.dblwr、#ib_16384_1.dblwr 与临时表空间目录 #innodb_temp;mysql 系统库的数据字典与系统表存于 mysql.ibd;二进制日志 binlog.000001、binlog.index 默认开启。
仔细看可以发现,除了 information_schema 这个系统数据库外,其他数据库在 数据目录 下都有对应的子目录。information_schema 比较特殊,MySQL 对它的实现进行了特殊处理,没有使用相应的数据库目录。
数据是以记录的形式插入到表中的,每个表的信息可以分为两种:
表结构 指该表的名称、表里有多少列、每个列的数据类型、有哪些约束条件和索引、用的是什么字符集和比较规则等信息,这些信息都体现在建表语句中。在 MySQL 5.7 及更早版本中,InnoDB 和 MyISAM 这两种存储引擎都会在 数据目录 下对应的数据库子目录下创建一个专门用于描述表结构的文件,文件名格式是 表名.frm。
注:MySQL 8.0 起彻底移除了
.frm文件(表结构存入 InnoDB 数据字典表,支持原子 DDL)。实测 8.0.46:InnoDB 表的序列化字典信息(SDI)存储在对应.ibd文件内部;MyISAM 表则生成独立的表名_序号.sdi文件。
例如在 dahaizi 数据库下创建一个名为 test 的表:
mysql> USE dahaizi;
Database changed
mysql> CREATE TABLE test (
-> c1 INT
-> );
Query OK, 0 rows affected (0.03 sec)在 MySQL 5.7 及更早版本中,数据库 dahaizi 对应的子目录下就会多出一个名为 test.frm 的描述表结构的文件(.frm 文件以二进制格式存储,直接打开会是乱码)。如前所述,MySQL 8.0 起不再有 .frm 文件。
描述表结构的文件已了解,那表中的数据存到什么文件中?在这个问题上,不同存储引擎产生了分歧。下面分别看 InnoDB 和 MyISAM 用什么文件保存表中数据。
前面重点介绍过 InnoDB 的实现原理,此时应熟悉下面几点:
InnoDB 使用 页 作为基本单位管理存储空间,默认的 页 大小为 16KB;InnoDB 存储引擎来说,每个索引对应一棵 B+ 树,B+ 树的每个节点都是一个数据页,数据页之间不要求物理连续,因为数据页之间有 双向链表 维护这些页的顺序;InnoDB 的聚簇索引的叶子节点存储了完整的用户记录,也就是所谓的 索引即数据,数据即索引。为了更好管理这些页,InnoDB 提出了一个 表空间(或 文件空间,英文名 table space / file space)的概念。表空间是一个抽象概念,可以对应文件系统上一个或多个真实文件(不同表空间对应的文件数量可能不同)。每一个 表空间 可以被划分为很多 页,表数据就存放在某个 表空间 下的某些页里。InnoDB 将表空间划分为几种不同类型,逐一介绍。
系统表空间 可以对应文件系统上一个或多个实际文件。默认情况下,InnoDB 会在 数据目录 下创建一个名为 ibdata1、大小为 12M 的文件,这个文件就是 系统表空间 在文件系统上的表示。这个文件是所谓的 自扩展文件,当不够用时会自动增加文件大小。
如果想让 系统表空间 对应文件系统上多个实际文件,或者想修改 ibdata1 这个文件名,可以在 MySQL 启动时配置对应的文件路径及大小,例如修改配置文件:
[server]
innodb_data_file_path=data1:512M;data2:512M:autoextend这样 MySQL 启动之后会创建两个 512M 大小的文件作为 系统表空间,其中的 autoextend 表示这两个文件不够用时自动扩展 data2 文件的大小。
也可以把 系统表空间 对应的文件路径不配置到 数据目录 下,甚至配置到单独的磁盘分区上,涉及的启动参数是 innodb_data_file_path 和 innodb_data_home_dir。具体配置逻辑较复杂,这里不展开,需要时可查官方文档。
需要注意:在一个 MySQL 服务器中,系统表空间只有一份。从 MySQL 5.5.7 到 MySQL 5.6.6 之间的各个版本中,表中的数据都会被默认存储到这个 系统表空间。
在 MySQL 5.6.6 及之后的版本中,InnoDB 不会默认把各个表的数据存储到系统表空间中,而是为每一个表建立一个独立表空间,即创建多少个表就有多少个独立表空间。使用 独立表空间 存储表数据,会在该表所属数据库对应的子目录下创建一个表示该 独立表空间 的文件,文件名与表名相同,只是添加了 .ibd 扩展名,完整文件名是 表名.ibd。
例如使用 独立表空间 存储 xiaohaizi 数据库下的 test 表,在 xiaohaizi 目录下会为 test 表创建这两个文件:
test.frm # 仅 MySQL 5.7 及更早版本;8.0 起无 .frm
test.ibd其中 test.ibd 文件用来存储 test 表中的数据和索引。也可以自己指定使用 系统表空间 还是 独立表空间 存储数据,这个功能由启动参数 innodb_file_per_table 控制。例如想刻意将表数据都存储到 系统表空间,可以在启动 MySQL 服务器时这样配置:
[server]
innodb_file_per_table=0当 innodb_file_per_table 的值为 0 时代表使用系统表空间;值为 1 时代表使用独立表空间。不过 innodb_file_per_table 参数只对新建的表起作用,对已经分配表空间的表不起作用。如果想把已经存在于系统表空间中的表转移到独立表空间,可以使用下面的语法:
ALTER TABLE 表名 TABLESPACE [=] innodb_file_per_table;或者把已经存在于独立表空间的表转移到系统表空间:
ALTER TABLE 表名 TABLESPACE [=] innodb_system;其中中括号括起来的 = 可有可无。例如想把 test 表从独立表空间移动到系统表空间:
ALTER TABLE test TABLESPACE innodb_system;随着 MySQL 的发展,除了上述两种经典表空间外,还新提出了一些不同类型的表空间,比如通用表空间(general tablespace)、undo 表空间(undo tablespace)、临时表空间(temporary tablespace)等,具体内容这里不展开,用到时再介绍。
介绍完 InnoDB 的系统表空间和独立表空间,现在看 MyISAM。与 InnoDB 的索引和数据是一个东西不同,MyISAM 的索引全部都是 二级索引,该存储引擎的数据和索引分开存放,所以在文件系统中也使用不同的文件存储数据文件和索引文件。与 InnoDB 不同的是,MyISAM 没有所谓的 表空间 一说,表数据都存放到对应的数据库子目录下。假如 test 表使用 MyISAM 存储引擎,在它所在数据库对应的 xiaohaizi 目录下会为 test 表创建这三个文件:
test.frm # 仅 MySQL 5.7 及更早版本;8.0 起为 test_序号.sdi
test.MYD
test.MYI其中 test.MYD 代表表的数据文件(即插入的用户记录),test.MYI 代表表的索引文件(为该表创建的索引都会放到这个文件中)。
MySQL 中的视图其实是虚拟的表,是某个查询语句的别名。存储视图时不需要存储真实数据,只需要把它的结构存储起来。和表一样,在 MySQL 5.7 及更早版本中,描述视图结构的文件也会被存储到所属数据库对应的子目录下,即一个 视图名.frm 文件。MySQL 8.0 起视图定义存入数据字典,数据目录中不再生成视图文件(8.0.46 实测:创建视图后数据库子目录无新文件)。
除了用户自己存储的数据外,数据目录 下还包括一些为了让程序更好运行的额外文件,主要包括这几类:
服务器进程文件
每运行一个 MySQL 服务器程序,都意味着启动一个进程。MySQL 服务器会把自己的进程 ID 写入到一个文件中。
服务器日志文件
服务器运行过程中会产生各种日志,比如常规查询日志、错误日志、二进制日志、redo 日志等,这些日志各有用途,后文会详细介绍,这里先了解即可。
默认/自动生成的 SSL 和 RSA 证书和密钥文件
主要是为客户端和服务器安全通信而创建的一些文件。
因为 MySQL 的数据都存在文件系统中,就不得不受到文件系统的一些制约,这在数据库和表的命名、表的大小和性能方面体现得比较明显,例如:
数据库名称和表名称不得超过文件系统所允许的最大长度
每个数据库都对应 数据目录 的一个子目录,数据库名称就是这个子目录的名称;每个表都会在数据库子目录下产生一个和表名同名的 .frm 文件,如果是 InnoDB 的独立表空间或使用 MyISAM 引擎,还会有其他文件的名称与表名一致。这些目录或文件名的长度都受限于文件系统所允许的长度。
特殊字符的问题
为避免数据库名和表名出现某些特殊字符而造成文件系统不支持的情况,MySQL 会把数据库名和表名中所有除数字和拉丁字母以外的字符在文件名里都映射成 @+编码值 的形式作为文件名。例如创建的表名为 'test?',由于 ? 不属于数字或拉丁字母,会被映射成编码值,所以这个表对应的 .frm 文件名就变成了 test@003f.frm。
文件长度受文件系统最大长度限制
对 InnoDB 的独立表空间来说,每个表的数据都会存储到一个与表名同名的 .ibd 文件中;对 MyISAM 存储引擎来说,数据和索引会分别存放到与表同名的 .MYD 和 .MYI 文件中。这些文件会随表中记录的增加而增大,其大小受限于文件系统支持的最大文件大小。
文件描述符限制
如果同时访问的表的数量非常多,可能受到文件系统的文件描述符限制的影响。
前面提到了 MySQL 的几个系统数据库,这几个数据库包含了 MySQL 服务器运行过程中所需的一些信息以及运行状态信息,下面简单了解。
mysql
这个数据库非常核心,它存储了 MySQL 的用户账户和权限信息、一些存储过程和事件的定义信息、一些运行过程中产生的日志信息、一些帮助信息以及时区信息等。
information_schema
这个数据库保存着 MySQL 服务器维护的所有其他数据库的信息,比如有哪些表、哪些视图、哪些触发器、哪些列、哪些索引等。这些信息并不是真实的用户数据,而是一些描述性信息,有时也称为元数据。
performance_schema
这个数据库主要保存 MySQL 服务器运行过程中的一些状态信息,算是对 MySQL 服务器的性能监控,包括统计最近执行了哪些语句、在执行过程的每个阶段花费了多长时间、内存的使用情况等信息。
sys
这个数据库主要通过视图的形式把 information_schema 和 performance_schema 结合起来,让程序员更方便地了解 MySQL 服务器的性能信息。
这里只是对四个系统数据库做简介。如果详细介绍这几个系统库的使用,篇幅会很长,这里因为介绍数据目录时遇到,为保持内容完整性提及一下,具体使用方式参考官方文档。
InnoDB、MyISAM 这样的存储引擎,会把数据存储到文件系统上。mysqld --verbose --help | grep datadirSHOW VARIABLES LIKE 'datadir';表名.frm 文件;8.0 起 .frm 被移除,表结构存入 InnoDB 数据字典;InnoDB 和 MyISAM,表数据有不同的存储方式。InnoDB 存储引擎,使用 表空间 来存储表中的数据,表空间分两种类型:
InnoDB 将所有的表数据都存储到这个系统表空间内,它是一个抽象概念,实际可以对应文件系统中若干个真实文件;innodb_file_per_table 参数设置为 1。每个表的独立表空间对应的文件系统文件是数据库子目录下与表名同名的 .ibd 文件。MySQL 中的数据实际存储在文件系统上,会受到文件系统的一些制约:
@+编码值 的形式作为文件名;