与其他数据库不同,MySQL有一个存储引擎的概念,根据不同的需求可以选择最优的存储引擎。

MySQL存储引擎概述

插件式存储引擎是MySQL最重要的特性之一,用户可以根据应用的需要选择如何存储、索引数据、是否使用事务等。MySQL默认支持多种存储引擎,以适用于不同领域的数据库应用需要,用户可以通过选择不同的存储引擎提高应用的效率,提供灵活的存储,用户甚至可以根据自己的需求定制和使用自己的存储引擎,以实现最大程度的可定制性。

MySQL5.7支持的存储引擎包括:InnoDBMyISAMMEMORYCSVBLACKHOLEARCHIVEMERGE(MRG_MyISAM)FEDERATEDEXAMPLENDB 等,其中 InooDBNDB 提供事务安全表,其他存储引擎都是非事务安全表。

创建新表时如果不指定存储引擎,那么系统就会使用默认存储引擎,MySQL5.5 之前默认存储引擎是 MyISAM,5.5版本之后就改为了 InnoDB

修改默认存储引擎

如果要修改默认的存储引擎,可以在参数文件中设置 default_storage_engine,查看当前的默认存储引擎,可以使用如下命令:

  1. mysql> show variables like 'default_storage_engine';
  2. +------------------------+--------+
  3. | Variable_name | Value |
  4. +------------------------+--------+
  5. | default_storage_engine | InnoDB |
  6. +------------------------+--------+
  7. 1 row in set, 1 warning (0.00 sec)

可以通过以下方法查看当前数据库支持的存储引擎:

mysql> show engines \G
*************************** 1. row ***************************
      Engine: InnoDB
     Support: DEFAULT
     Comment: Supports transactions, row-level locking, and foreign keys
Transactions: YES
          XA: YES
  Savepoints: YES
*************************** 2. row ***************************
      Engine: MRG_MYISAM
     Support: YES
     Comment: Collection of identical MyISAM tables
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 3. row ***************************
      Engine: MEMORY
     Support: YES
     Comment: Hash based, stored in memory, useful for temporary tables
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 4. row ***************************
      Engine: BLACKHOLE
     Support: YES
     Comment: /dev/null storage engine (anything you write to it disappears)
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 5. row ***************************
      Engine: MyISAM
     Support: YES
     Comment: MyISAM storage engine
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 6. row ***************************
      Engine: CSV
     Support: YES
     Comment: CSV storage engine
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 7. row ***************************
      Engine: ARCHIVE
     Support: YES
     Comment: Archive storage engine
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 8. row ***************************
      Engine: PERFORMANCE_SCHEMA
     Support: YES
     Comment: Performance Schema
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 9. row ***************************
      Engine: FEDERATED
     Support: NO
     Comment: Federated MySQL storage engine
Transactions: NULL
          XA: NULL
  Savepoints: NULL
9 rows in set (0.00 sec)

其中 Support 不同值的含义分别是:

  • DEFAULT 支持并启用,并且为默认引擎
  • YES 支持并启用
  • NO 不支持
  • DISABLED 支持,但数据库启动时被禁用

创建表时指定存储引擎

创建新表时可以通过 ENGINE 关键字设置新建表的存储引擎,例如在下面的例子中,ai表是存储引擎是MyISAM 而 country 的存储引擎是 InnoDB:

mysql> CREATE TABLE ai (
    -> i bigint(20) not null AUTO_INCREMENT,
    -> PRIMARY KEY(i)
    -> ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE country(
    -> country_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> country VARCHAR(50) NOT NULL,
    -> last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    -> PRIMARY KEY(country_id)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.02 sec)

修改已存在表的存储引擎

可以使用 ALTER TABLE 语句,将已经存在的表修改为其他存储引擎:

mysql> show create table ai \G;
*************************** 1. row ***************************
       Table: ai
Create Table: CREATE TABLE `ai` (
  `i` bigint(20) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`i`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

ERROR:
No query specified

mysql> alter table ai engine = innodb;
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show create table ai \G;
*************************** 1. row ***************************
       Table: ai
Create Table: CREATE TABLE `ai` (
  `i` bigint(20) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`i`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

修改后,表ai的存储引擎是InnoDB,可以使用InnoDB存储引擎的相关特性。

注意:修改表的存储引擎需要锁表并复制数据,对于线上环境的表进行这个操作非常危险,除非你非常了解可能造成的影响,否则在线上环境请考虑使用其他方式,例如:percona的 OSC工具

各存储引擎特性

重点介绍集中常用的存储引擎,并对比各个存储引擎之间的区别:

MySQL 常用存储引擎对比

特点 MyISAM Memory InnoDB Archive NDB
B树索引 支持 支持 支持
备份/时间点恢复 支持 支持 支持 支持 支持
支持集群 支持
聚簇索引 支持
数据压缩 支持 支持 支持
数据缓存 N/A 支持 支持
数据加密 支持 支持 支持 支持 支持
支持外键 支持 支持
全文索引 支持 支持
地理坐标数据类型 支持 支持
地理坐标索引 支持 支持
哈希索引 支持 支持
索引缓存 支持 N/A 支持 支持
锁粒度 表级 表级 行级 行级 行级
MVCC多版本控制 支持
支持复制 支持 有限支持 支持 支持 支持
存储限制 256TB RAM 64TB None 384EB
T树索引 支持
支持事务 支持 支持
统计信息 支持 支持 支持 支持 支持

下面重点介绍四种存储引擎:MyISAM、MEMORY、InnoDB、Archive。

MyISAM 引擎

MyISAM引擎是MySQL5.5之前版本默认的存储引擎。MyISAM既不支持事务,也不支持外键,在5.5之前的版本中,MyISAM在某些场景中相对InnoDB的访问速度有明显的优势,对事务完整性没有要求或者以 SELECTINSERT 为主的应用都可以使用这个引擎来创建表,在MySQL5.6之后,MyISAM已经用的越来越少了。

每个MyISAM引擎的表在磁盘上存储成为三个文件,其文件名和表名相同,但扩展名不同:

  • .frm 存储表定义
  • .MYD MYData 存储数据
  • .MYI MYIndex 存储索引

数据文件和索引可以放在不同的目录,平均分布IO,以获取更快的速度。

要指定索引和数据文件的路径,需要在创建表的时候通过 DATA DIRECTORYINDEX DIRECTORY 语句指定,也就是说不同MyISAM表的索引文件和数据文件可以放在不同的路径下,文件路径需要是绝对路径,并且具有访问权限。

MyISAM类型的表有可能会损坏,原因可能是多种多样的,损坏后的表可能不能被访问,会提示需要修复或返回错误的结果。MyISAM类型的表提供修复的工具,可以用 CHECK TABLE 来检查 MyISAM 表的健康,并用 REPAIR TABLE 语句修复一个损坏的MyISAM表。表损坏可能会导致数据库异常重新启动,需要尽快修复并尽可能地确认损坏的原因。

MyISAM的表还支持3种不同的存储格式,分别如下:

  • 静态(固定长度)表
  • 动态表
  • 压缩表

压缩表

其中,静态表是默认的存储格式。静态表的字段都是非变长字段,每一个记录的长度都固定的。这个存储方式的特点的优点是存储非常迅速,容易缓存,出现故障容易恢复;缺点是占用的空间通常比动态表多。静态表在存储数据时会按照列的宽度定义补足空格,但是在应用访问的时候并不会得到这些空格,这些空格在返回之前已经去掉了。需要注意的是,如果保存的内容后面本来就带有空格,那么返回结果的时候也会被去掉。

mysql> CREATE TABLE Myisam_char (name char(10)) engine=myisam;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO Myisam_char values('abcde'),('abcde  '),('  abcde'),('  abcde  ');
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> SELECT  name,length(name) FROM Myisam_char;
+---------+--------------+
| name    | length(name) |
+---------+--------------+
| abcde   |            5 |
| abcde   |            5 |
|   abcde |            7 |
|   abcde |            7 |
+---------+--------------+
4 rows in set (0.00 sec)

可以看到插入记录后面的空格被去掉了,而前面的空格被保留了。

动态表

动态表中包含变长字段,记录不是固定长度的。这样存储的优点是占用空间相对较少,但频繁的更新或删除记录会产生碎片,需要定期执行 OPTIMIZE TABLE 语句或 myisamchk-r 命令来改善性能,并且在出现故障时恢复相对比较困难。

压缩表

压缩表由 myisampack 工具创建,占用非常小的磁盘空间。因为每条记录都是被单独压缩的,所以只有非常小的访问开支。

InnoDB引擎

InnoDB 引擎是 MySQL5.5 之后的默认存储引擎,提供了具有提交、回滚和崩溃恢复能力的事务安全保障,同时提供了更小的锁粒度和更强的并发能力,拥有自己独立的缓存和日志,在 MySQL5.6 和 5.7 版本中性能有较大的提升。

相比较 MyISAM 引擎,InnoDB 会占用更多的磁盘空间以保留数据和索引。但在多数场景下,InnoDB 都是更好选择,也是为何MySQL将默认存储引擎改成 InnoDB 的原因,在更是在 MySQL8.0 中 将所有的系统表也改为 InnoDB 存储引擎。

下面将介绍 IoonDB 存储引擎的表在使用过程中不同于其他存储引擎的特点,以及如何更好的使用 InnoDB 存储引擎。

自动增长列

InnoDB 表的自动增长列是可以手动插入的,但如果插入的值是空,则实际插入的值将是自动增长后的值。

下面定义表 autoincre_demo,其中列 i 使用自动增长列,对该表插入记录,然后查看自动增长列的处理情况:

mysql> CREATE TABLE autoincre_demo
    -> (i SMALLINT NOT NULL AUTO_INCREMENT,
    -> name VARCHAR(10), PRIMARY KEY(i)
    -> )ENGINE=InnoDB;
Query OK, 0 rows affected (0.03 sec)

mysql> INSERT INTO autoincre_demo VALUES(NULL,'1'),(2,'2'),(4,'3'),(NULL,'4');
Query OK, 4 rows affected (0.01 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM autoincre_demo;
+---+------+
| i | name |
+---+------+
| 1 | 1    |
| 2 | 2    |
| 4 | 3    |
| 5 | 4    |
+---+------+
4 rows in set (0.00 sec)

可以通过ALTER TABLE [table_name] AUTO_INCREMENT = n; 来强制自动增长的初始值(也就是下次新增从那个数字开始,之后再次插入依然还是在上次的基础上+1):

mysql> ALTER TABLE autoincre_demo AUTO_INCREMENT=10;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> INSERT INTO autoincre_demo VALUES(NULL,'5');
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM autoincre_demo;
+----+------+
| i  | name |
+----+------+
|  1 | 1    |
|  2 | 2    |
|  4 | 3    |
|  5 | 4    |
| 10 | 5    |
+----+------+
5 rows in set (0.00 sec)

注意: 在MySQL8.0 之前的版本中,对于InnoDB这个引擎,这个值是存储在内存当中的,如果数据库重启,这个值就会丢失,数据库会自动将 AUTO_INCREMENT 重置为当前存储的最大值+1,自增列记录的值和预期不一致,从而导致数据冲突。

在 MySQL8.0 中这个变量将保存到REDO LOG中,每一次计数器发生改变都会更新 REDO LOG。如果数据库发生重启,InnoDB会根据REDO LOG中的计数器信息来初始化其内存值。

可以使用 LAST_INSERT_ID() 来查询当前线程最后插入记录使用的值,如果插入多条记录,LAST_INSERT_ID() 只返回第一条数据的自动增长值,如果是手动指定了自动增长列的值,则 LAST_INSERT_ID() 的值不会更新:

mysql> SELECT LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
|                0 |
+------------------+
1 row in set (0.00 sec)

mysql> INSERT INTO autoincre_demo VALUES(1,'1');
Query OK, 1 row affected (0.01 sec)

mysql> SELECT LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
|                0 |
+------------------+
1 row in set (0.00 sec)

mysql> INSERT INTO autoincre_demo(name) VALUES('2'),('3');
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> SELECT LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
|                2 |
+------------------+
1 row in set (0.00 sec)
  • 表中没有数据时 LAST_INSERT_ID() 的值是 0
  • 手动指定自动增长列时,LAST_INSERT_ID() 也是 0
  • 当自动插入多条数据时,LAST_INSERT_ID() 的值是第一条数据的自增值

对于 InnoDB 表,自动增长列必须被索引,如果是组合索引,也必须是组合索引的第一列,但对于 MyISAM 表,自动增长列可以是组合索引的其他列看,这样插入记录后,自动增长列是按照组合索引前面几列进行排序后递增的。

例如:创建一个新的 MyISAM 表 myisam_demo ,自动增长列 d1 作为组合索引的第二列,对该表插入一些记录后,自动增长列是按照组合索引的第一列 d2 进行排序后递增的:

mysql> CREATE TABLE myisam_demo
    -> (d1 SMALLINT NOT NULL AUTO_INCREMENT,
    -> d2 SMALLINT NOT NULL,
    -> name VARCHAR(10),
    -> INDEX(d2,d1)
    -> )ENGINE=MyISAM;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO myisam_demo(d2,name) VALUES(2,'2'),(3,'3'),(4,'4'),(2,'2'),(3,'3'),(4,'4');
Query OK, 6 rows affected (0.00 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM myisam_demo;
+----+----+------+
| d1 | d2 | name |
+----+----+------+
|  1 |  2 | 2    |
|  1 |  3 | 3    |
|  1 |  4 | 4    |
|  2 |  2 | 2    |
|  2 |  3 | 3    |
|  2 |  4 | 4    |
+----+----+------+
6 rows in set (0.00 sec)

外键约束

MySQL 支持外键的常用存储引擎只有 InnoDB ,在创建外键的时候,要求父表必须有对应的索引,子表在创建外键的时候也会自动创建对应的索引。

下面的样例数据库中有两张表,country 是父表,country_id 为主键索引,city 是子表,country_id 字段为外键,对应 country 表的主键 country_id。

mysql> CREATE TABLE country (
    -> country_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> country VARCHAR(50) NOT NULL,
    -> last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    -> PRIMARY KEY (country_id)
    -> )ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.03 sec)

mysql> CREATE TABLE city(
    -> city_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> city VARCHAR(50) NOT NULL,
    -> country_id SMALLINT UNSIGNED NOT NULL,
    -> last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    -> PRIMARY KEY (city_id),
    -> KEY idx_fx_country_id (country_id),
    -> CONSTRAINT fa_city_country FOREIGN KEY (country_id) REFERENCES country (country_id) ON DELETE RESTRICT ON UPDATE
CASCADE
    -> )ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.03 sec)

创建索引时,可以指定在删除、更新父表时对子表进行相应的操作,包括 RESTRICTCASCADESET NULLNO ACTION

  • 其中 RESTRICTNO ACTION 相同,作用是限制子表有关联记录时父表不能更新;
  • CASCADE 表示父表在更新和删除时,更新或删除子表对应记录。
  • SET NULL 表示父表在更新或删除的时候,子表对应的字段被 SET NULL

选择后两种方式的时候要谨慎,可能会因为错误的操作导致数据丢失。

上面创建的表,子表外键指定的是:ON DELETE RESTRICT ON UPDATE CASCADE 方式,所以在主表删除记录的时候,如果子表有记录,则不允许删除;主表在更新记录时,如果子表有对应记录,则子表对应更新:

mysql> INSERT INTO country VALUES(1,afghanitan,'2020-12-10 05:15:34');
ERROR 1054 (42S22): Unknown column 'afghanitan' in 'field list'
mysql> INSERT INTO country VALUES(1,'afghanitan','2020-12-10 05:15:34');
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO city VALUES(2,'kabul',1,'2020-12-13 12:00:12');
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM country WHERE country_id = 1;
+------------+------------+---------------------+
| country_id | country    | last_update         |
+------------+------------+---------------------+
|          1 | afghanitan | 2020-12-10 05:15:34 |
+------------+------------+---------------------+
1 row in set (0.00 sec)

mysql> SELECT * FROM city WHERE country_id = 1;
+---------+-------+------------+---------------------+
| city_id | city  | country_id | last_update         |
+---------+-------+------------+---------------------+
|       2 | kabul |          1 | 2020-12-13 12:00:12 |
+---------+-------+------------+---------------------+
1 row in set (0.00 sec)

-- 删除失败 因为子表有关联外键
mysql> DELETE FROM country where country_id = 1;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`test`.`city`, CONSTRAINT `fa_city_country` FOREIGN KEY (`country_id`) REFERENCES `country` (`country_id`) ON UPDATE CASCADE)

mysql> UPDATE country SET country_id = 200 WHERE country_id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM country WHERE country = 'afghanitan';
+------------+------------+---------------------+
| country_id | country    | last_update         |
+------------+------------+---------------------+
|        200 | afghanitan | 2020-12-16 22:22:23 |
+------------+------------+---------------------+
1 row in set (0.00 sec)

-- 父表更新后 子表的关联字段自动跟着更新
mysql> SELECT * FROM city WHERE city_id = 2;
+---------+-------+------------+---------------------+
| city_id | city  | country_id | last_update         |
+---------+-------+------------+---------------------+
|       2 | kabul |        200 | 2020-12-13 12:00:12 |
+---------+-------+------------+---------------------+
1 row in set (0.00 sec)

当某个表被其他表创建了外键参照,那么该表对应的索引或主键都禁止被删除。

当导入多个表的数据时,如果需要忽略之前的导入顺序,可以暂时关闭外键的检查;同样,在执行 LOAD DATEALTER TABLE 操作时,可以通过暂时关闭外键约束来加快执行的速度,执行完成后再开启:

关闭外键约束的命令是:SET FOREIGN_KEY_CHECKS = 0;

开启外键约束的命令是:SET FOREIGN_KEY_CHECKS = 1;

对应 InnoDB 类型的表,外键的信息可以通过 SHOW CREATE TABLESHOW TABLE STATUS 命令显示:

mysql> SHOW TABLE STATUS LIKE 'city' \G
*************************** 1. row ***************************
           Name: city
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 1
 Avg_row_length: 16384
    Data_length: 16384
Max_data_length: 0
   Index_length: 16384
      Data_free: 0
 Auto_increment: 3
    Create_time: 2020-12-15 23:28:12
    Update_time: 2020-12-16 22:22:23
     Check_time: NULL
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)

mysql> SHOW CREATE TABLE city \G
*************************** 1. row ***************************
       Table: city
Create Table: CREATE TABLE `city` (
  `city_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `city` varchar(50) NOT NULL,
  `country_id` smallint(5) unsigned NOT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`city_id`),
  KEY `idx_fx_country_id` (`country_id`),
  CONSTRAINT `fa_city_country` FOREIGN KEY (`country_id`) REFERENCES `country` (`country_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

注意:外键需要注意的细节较多,一旦使用不当,可能会带来性能下降或数据不一致的问题,尤其是在 OLTP类型(联机事务处理,个人理解:高可用,对数据有强一致性要求,例如电商、金融这类)的应用中,需要谨慎使用外键。

主键和索引

不同于其他存储引擎,InnoDB 的数据文件本身就是以聚簇索引的形式保存的,这个聚簇索引也被叫做主索引,并且也是 InnoDB 表的主键。InnoDB 的每行数据都是保存在主索引的叶子节点上,所以每个 InnoDB 表都必须包含主键,如果没有显式指定主键,那么 InnoDB 存储引擎会自动创建一个长度为 6 个字节的 LONG 类型的隐藏字段作为主键。

考虑到聚簇索引的特点对于查询优化的效果,所有 InnoDB 表都应该显式的指定主键,一般来说,主键都应该按照以下原则来选择:

  • 满足唯一和非空约束
  • 优先考虑使用经常被当作查询条件的字段或自增字段
  • 字段值基本不会被修改
  • 使用尽可能短的字段

在 InnoDB 表上,除了主键之外的索引都叫做辅助索引或二级索引,二级索引会指向主索引,并通过主索引获取最终数据。因此,主键设置是否合理,都会对索引的效率产生影响。

存储方式

InnoDB 存储表和索引的方式有以下两种:

共享表空间存储,这种方式创建的表的表结构保存在 .frm 文件中,数据和索引保存在 innodb_data_home_dir he innodb_data_file_path 定义的表空间中,可以是多个文件。

多表空间存储,这种方式创建的表的表结构仍然保存在 .frm 文件中,但是每个表的数据和索引单独保存在 .ibd 中。如果是一个分区表,则每个分区对应的 .idb 文件是 “表名+分区名”,可以在创建分区时指定每个分区的数据文件位置,以此将此表的 IO 平均分配在多个磁盘上。

使用共享表空间时随着数据不断增长,表空间的管理维护将变得越来越困难,所以一般都建议多表空间。多表空间存储在 MySQL5.7 中默认设置为 ON,即新建的表都是按照多表空间的方式创建。如果修改此参数为 OFF,则新创建的表都会改为共享表空间存储,但已经创建的多表空间的表仍然保存原来的访问方式。

在一些老版本的表中,很多都是共享表空间,可以通过以下命令修改为多表空间:

mysql> SET GLOBAL innodb_file_per_table=1;
Query OK, 0 rows affected (0.00 sec)

mysql> ALTER TABLE city ENGINE=InnoDB;
Query OK, 0 rows affected (0.08 sec)
Records: 0  Duplicates: 0  Warnings: 0

多表空间的数据文件没有大小限制,既不需要设置初始大小,也不需要设置文件的最大限制,扩展大小等参数。

对于使用了多表空间特性的表,可以比较方便的进行单表操作的备份和恢复操作,但是直接复制.ibd 文件不行,因为没有共享表空间的字典信息,直接复制的 .ibd 文件和 .frm 文件恢复时是不能被正常识别的,但可以通过以下命令:

ALTER TABLE tel_name DISCARD TABLESPACE;
ALTER TABLE tel_name IMPORT TABLESPACE;

注意: 即使在多表空间的存储方式下,共享表空间仍然是必须的,InnoDB 会把内部数据词典和在线重做日志,放在这个文件中。

MEMORY 引擎

MEMORY 存储引擎使用存在于内存中的内容来创建表。每个 MEMORY 表实际只对应一个磁盘文件 .frm。MEMORY 类型的表访问速度非常快,因为它的数据是放在内存中的,并且默认使用HASH做索引,但是一旦服务关闭,表中的数据就会丢掉。

mysql> CREATE TABLE tab_memory ENGINE=MEMORY
    ->  SELECT phone,sheng,shi,type
    ->  FROM phone limit 600;
Query OK, 600 rows affected (0.01 sec)
Records: 600  Duplicates: 0  Warnings: 0

mysql> SELECT COUNT(*) FROM tab_memory;
+----------+
| COUNT(*) |
+----------+
|      600 |
+----------+
1 row in set (0.00 sec)

mysql> SHOW TABLE STATUS like 'tab_memory' \G;
*************************** 1. row ***************************
           Name: tab_memory
         Engine: MEMORY
        Version: 10
     Row_format: Fixed
           Rows: 600
 Avg_row_length: 189
    Data_length: 1044672
Max_data_length: 66060225
   Index_length: 0
      Data_free: 0
 Auto_increment: NULL
    Create_time: 2020-12-20 01:51:28
    Update_time: NULL
     Check_time: NULL
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)

给 MEMORY 表创建索引时,可以指定使用 HASH 索引还是 BTREE 索引:

-- mysql> CREATE INDEX mem_hash USING BTREE on tab_memory (phone);
mysql> CREATE INDEX mem_hash USING HASH on tab_memory (phone);
Query OK, 600 rows affected (0.01 sec)
Records: 600  Duplicates: 0  Warnings: 0

mysql> SHOW INDEX FROM tab_memory \G;
*************************** 1. row ***************************
        Table: tab_memory
   Non_unique: 1
     Key_name: mem_hash
 Seq_in_index: 1
  Column_name: phone
    Collation: NULL
  Cardinality: 300
     Sub_part: NULL
       Packed: NULL
         Null:
   Index_type: HASH
      Comment:
Index_comment:
1 row in set (0.00 sec)

在启动MySQL 服务的时候使用 --init-file 选项,把 INSERT INTO … SELECT 或 LOAD DATA INFILE 这样的语句放入这个文件中,就可以在服务启动时从持久稳固的数据源装载表。

服务器需要足够的内存来维持所有在同一时间使用的 MEMORY 表,当不再需要 MEMORY 表的内容之时,要释放被MEMORY 表使用的内存,应当执行 DELETE FROMTRUNCATE TABLE,或整个删除表(使用 DROP TABLE 操作)。

每个 MEMORY 表都可以方式的数据量大小,受到 max_heap_table_size 系统变量的约束,这个系统变量的初始值是16MB,可以根据需求加大。在定义 MEMORY 表的时候,可以通过 MAX_ROWS 子句来指定表的最大行数。

MEMORY 类型的存储引擎主要用于那些内容变化不频繁的代码表,或作为统计操作的中间结果表,便于高效地对中间结果进行分析并且得到最终的计算结果。对存储引擎为 MEMORY 的表更新操作要谨慎,因为数据并没有实际写入磁盘中,所以一定要对下次重启服务如何获取这些修改后的数据有所考虑。

临时表数据量受MySQL设置影响,超过设置临时表数据,无法写入数据:

mysql> INSERT INTO tab_memory SELECT * FROM tab_memory;
ERROR 1114 (HY000): The table 'tab_memory' is full

设置的是临时表内存占用64MB,目前这张表数据量刚好达到64MB,继续写入就会提示这个

MERGE 引擎

MERGE 存储引擎也被称之为 MRG_MyISAM,是一组 MyISAM 表的组合。这些 MyISAM 表必须结构完全相同,MERGE 本身并不存储数据,对 MERGE 类型的表进行删、改、查操作,本质上是对内部的 MyISAM 表进行的。对于 MERGE 类型的表进行插入操作是通过 INSERT_METHOD 子句定义插入的表。可以有三个不同的值,FIRST 或 LAST 值使得插入操作被相应的作用在第一个表或最后一个表上,不定义这个语句或定义为 NO 表示不能对这个 MERGE 表执行插入操作。

可以对 MERGE 表进行 DROP 操作,这个操作只是删除 MERGE 表的定义,对内部的表没有任何影响。

MERGE 在磁盘存储上只保留两个文件,文件名以表名开始,一个 .frm 文件存储表定义,另一个 .MRG 存储组合表信息,包括 MERGE 表是由那些表组成的,插入新数据时的依据。可以通过修改 .MRG 文件来修改 MERGE表,但修改后需要通过 FLUSH TABELES 刷新。

下面是创建和使用 MERGE 表的示例:

  1. 创建三个表 payment_2006payment_2007payment_all,其中 payment_all 是前面两张表的MERGE表:
mysql> CREATE TABLE payment_2006(
    -> country_id SMALLINT,
    -> payment_data datetime,
    -> amount DECIMAL(15,2),
    -> KEY idx_fk_country_id (country_id)
    -> )ENGINE=MyISAM;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE payment_2007(
    -> country_id SMALLINT,
    -> payment_data datetime,
    -> amount DECIMAL(15,2),
    -> KEY idx_fk_country_id (country_id)
    -> )ENGINE=MyISAM;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE payment_all(
    -> country_id SMALLINT,
    -> payment_data datetime,
    -> amount DECIMAL(15,2),
    -> INDEX(country_id)
    -> )ENGINE=MERGE UNION=(payment_2006,payment_2007) INSERT_METHOD=LAST;
Query OK, 0 rows affected (0.00 sec)

payment_2006payment_2007表中插入数据,并且查看三张表的数据:

mysql> INSERT INTO payment_2006 VALUES(1,'2006-05-01',100000),(2,'2006-08-15',15000);
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> INSERT INTO payment_2007 VALUES(1,'2007-02-20',35000),(2,'2007-07-15',220000);
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM payment_2006;
+------------+---------------------+-----------+
| country_id | payment_data        | amount    |
+------------+---------------------+-----------+
|          1 | 2006-05-01 00:00:00 | 100000.00 |
|          2 | 2006-08-15 00:00:00 |  15000.00 |
+------------+---------------------+-----------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM payment_2007;
+------------+---------------------+-----------+
| country_id | payment_data        | amount    |
+------------+---------------------+-----------+
|          1 | 2007-02-20 00:00:00 |  35000.00 |
|          2 | 2007-07-15 00:00:00 | 220000.00 |
+------------+---------------------+-----------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM payment_all;
+------------+---------------------+-----------+
| country_id | payment_data        | amount    |
+------------+---------------------+-----------+
|          1 | 2006-05-01 00:00:00 | 100000.00 |
|          2 | 2006-08-15 00:00:00 |  15000.00 |
|          1 | 2007-02-20 00:00:00 |  35000.00 |
|          2 | 2007-07-15 00:00:00 | 220000.00 |
+------------+---------------------+-----------+
4 rows in set (0.00 sec)

payment_all 表的数据是 payment_2006 和 payment_2007 两张表合并后的结果集。

再向 payment_all 表插入数据,由于 MERGE 表定义的是 INSERT_METHOD=LAST ,就会向最后一张表写入数据:

mysql> INSERT INTO payment_all VALUES(3,'2006-03-31',112200);
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM payment_all;
+------------+---------------------+-----------+
| country_id | payment_data        | amount    |
+------------+---------------------+-----------+
|          1 | 2006-05-01 00:00:00 | 100000.00 |
|          2 | 2006-08-15 00:00:00 |  15000.00 |
|          1 | 2007-02-20 00:00:00 |  35000.00 |
|          2 | 2007-07-15 00:00:00 | 220000.00 |
|          3 | 2006-03-31 00:00:00 | 112200.00 |
+------------+---------------------+-----------+
5 rows in set (0.00 sec)

mysql> SELECT * FROM payment_2007;
+------------+---------------------+-----------+
| country_id | payment_data        | amount    |
+------------+---------------------+-----------+
|          1 | 2007-02-20 00:00:00 |  35000.00 |
|          2 | 2007-07-15 00:00:00 | 220000.00 |
|          3 | 2006-03-31 00:00:00 | 112200.00 |
+------------+---------------------+-----------+
3 rows in set (0.00 sec)

这也是 MERGE 表和分区表的区别,MERGE 表并不能智能的将记录写入对应的表中,而分区表是可以的(分区功能于 MySQL5.1 版本推出,经过多个版本的更新,目前已比较完善 )。

通常我们使用的 MERGE 表来透明地对多个表进行查询和更新操作,而这种按照时间记录的操作日志表则可以透明的进行插入。

TokuDB引擎

MySQL 除了自带的存储引擎之外,还支持一些常见的第三方存储引擎,在某些特定应用中也有广泛的使用,比如列式存储引擎 Infobright 以及 高写行能,高压缩的 TokuDB 就是其中两个非常有代表性的存储引擎,这里简单介绍 TokuDB 。

ToKuDB 是一个高性能、支持事务处理的存储引擎, 在 MySQL5.6 之前,可以安装在 MySQL、MariaDB 中,被 Percona 公司收购后,目前最新版本可在 Percona Server for MySQL (类似于 MariaDB 一样是 MySQL 的分支,完全兼容 MySQL,主要做了服务端的优化增强,客户端无修改) 中使用。 TokuDB 存储引擎具有高扩展性、高压缩率、高效的写入性能,支持大多数在线的 DDL 操作。且是开源的,可以从 Percona 官网下载。

对比 MySQL5.6 InnoDB 存储引擎, TokuDB 有以下特性:

  • 使用 Fractal 树索引保证高效的插入性能 (查了一下 好像是快20 ~ 80 倍)
  • 优秀的压缩特性,比 InnoDB 高近十倍
  • Hot Schema Changes 特性支持 在线创建索引和添加、删除属性列等DDL操作
  • 使用 Bulk Loader 达到快速加载大量数据
  • 提供了主从延迟消除技术
  • 支持 ACID 和 MVCC

通过上面的介绍可以发现 TokuDB 适合以下场景:

  • 日志数据,日志通常插入频繁,且存储量大
  • 历史数据,通常写入后不再操作数据,可以利用 TokuDB 的高压缩特性进行存储
  • 在线 DDL 较频繁的场景,可以使用TokuDB 大大增加系统可用性

如何选择合适的存储引擎

在选择存储引擎时,应根据应用特点选择合适的存储引擎。对于复杂的应用系统,还可以使用多种存储引擎进行组合。

MyISAM 适合的场景

  • 在 MySQL5.5之前的版本是默认的存储引擎,
  • 应用是以读和插入为主,只有极少更新和删除操作。
  • 并且对事务的完整性没有要求,没有并发写操作,

OLTP 环境一般建议不要使用 MyISAM。

InnoDB 适合场景

  • MySQL 5.5 之后的默认存储引擎
  • 用于事务处理应用程序
  • 支持外键
  • 对事务完整性有较高要求
  • 在并发条件下要求数据的一致性
  • 数据除了插入和查询外,还有很多更新、删除操作,InnoDB 存储引擎有效的降低由于删除和更新导致的锁定

在大多数的应用系统,InnoDB 都是合适的选择。

MEMORY 适用场景

  • 将数据保存在内存中,在需要快速定位记录和其他类似数据的环境下,可以提供极快的访问速度。
  • MEMORY 的缺陷是对表的大小有限制,太大的表无法缓存到内存中,其次要保证数据的可恢复,数据库异常终止后表中的数据是可以恢复的
  • MEMORY 表通常用于更新不太频繁的表,用以快速得到访问结果

MERGE 适用场景

  • 将同一个系列的 MyISAM 表以逻辑的方式组合在一起,并作为一个对象引用它们
  • MERGE 表的优点是可以突破对单个 MyISAM 表大小的限制,并且通过将不同的表分布在多个磁盘上,可以有效改善 MERGE 表的访问效率,对于数据仓储等 VLDB 环境十分适合

以上存储引擎选择仅作为建议,实际应用场景千差万别。