MySQL InnoDB文件格式管理

InnoDB文件格式管理
随着InnoDB的发展,数据文件格式有时需要与以前版本的InnoDB不兼容,以支持新特性。为了在升级和降级的情况下管理兼容性,以及运行不同版本MySQL的系统,InnoDB使用命名文件格式。InnoDB目前支持两种命名文件格式,Antelope和Barracuda。

.Antelope是原始的InnoDB文件格式,以前没有一个名字。它支持紧凑和冗余的InnoDB表行格式。

.Barracuda是最新的文件格式。它支持所有InnoDB行格式,包括较新的compressed和dynamic行格式。与compressed和dynamic行格式相关的特性包括压缩表、页外列的高效存储以及高达3072字节的索引键前缀(innodb_large_prefix)。

这将讨论为新的InnoDB表启用InnoDB文件格式,验证MySQL版本之间不同文件格式的兼容性,以及识别正在使用的文件格式。

InnoDB文件格式设置不适用于存储在一般表空间中的表。通用表空间提供对所有行格式和相关特性的支持。

以下文件格式配置参数有新的默认值:
.innodb_file_format默认值修改为Barracuda。之前的默认值是Antelope。

.innodb_large_prefix默认值修改为ON。之前的默认值是OFF。

以下文件格式配置参数已弃用,并可能在将来的版本中删除:
.innodb_file_format

.innodb_file_format_check

.innodb_file_format_max

.innodb_large_prefix

文件格式配置参数用于创建与MySQL 5.1中早期版本的InnoDB兼容的表。现在MySQL 5.1已经结束了它的产品生命周期,不再需要这些参数。

启用文件格式
innodb_file_format配置选项为file-per-table表空间启用一个InnoDB文件格式。

Barracuda是默认的innodb_file_format设置。在早期版本中,默认的文件格式是Antelope。

innodb_file_format配置选项已弃用,可能在将来的版本中被删除。

当你启动mysqld时,你可以在命令行中设置innodb_file_format的值,或者在选项文件中(Unix上是my.cnf, Windows上是my.ini)。你也可以使用SET GLOBAL语句动态地修改它。

SET GLOBAL innodb_file_format=Barracuda;

使用说明
.InnoDB文件格式设置不适用于存储在一般表空间中的表。通用表空间提供对所有行格式和相关特性的支持。

.当使用CREATE table或ALTER table的TABLESPACE [=] innodb_system 表选项在system表空间中存储动态表时,innodb_file_format设置不适用。

.在创建使用动态行格式的表时,innodb_file_format设置会被忽略。

验证文件格式兼容性
InnoDB包含了一些检查来防止可能发生的崩溃和数据损坏,如果你对运行在旧版本的MySQL服务器上的InnoDB数据文件使用新文件格式可能会发生。这些检查发生在服务器启动时,以及用户第一次访问表时。本节描述这些检查,如何控制它们,以及可能出现的错误和警告条件。

向后兼容性
你只需要在使用最新版本的InnoDB (InnoDB的MySQL 5.5或更高版本)和旧版本(MySQL 5.1或更早版本,内置InnoDB而不是InnoDB插件)时考虑向后的文件格式兼容性。为了尽量减少兼容性问题,你可以在所有MySQL 5.1和更早版本的数据库服务器上使用InnoDB插件进行标准化。

一般来说,新版本的InnoDB创建的表或索引在旧版本的InnoDB中无法安全地读取或写入,而不会有崩溃、挂起、错误结果或损坏的风险。InnoDB提供了一种机制来防范这些情况,并有助于保持数据库文件和InnoDB版本之间的兼容性。这种机制可以让你利用InnoDB版本的一些新特性(如性能改进和bug修复),同时通过防止意外使用创建向下不兼容的磁盘文件的新特性,仍然保留使用旧版本InnoDB数据库的选择。

如果某个版本的InnoDB支持一种特定的文件格式(不管这种格式是否是默认的),你就可以查询和更新任何需要这种格式或更早的格式的表。根据所启用的特定文件格式,只有使用新特性创建新表的操作受到限制。相反,如果表空间包含一个使用不支持的文件格式的表或索引,则根本无法访问它,即使是读访问。

将InnoDB表空间“降级”为早期的Antelope文件格式的唯一方法是将数据复制到使用早期格式的新表中。

确定一个已经存在的InnoDB表空间的文件格式最简单的方法是检查它所包含的表的属性,使用SHOW table STATUS命令或者查询表INFORMATION_SCHEMA.TABLES。如果表的Row_format被报告为’Compressed’或’Dynamic’,则包含该表的表空间支持Barracuda格式。

内部细节
每个InnoDB file-per-table表空间(由一个*.ibd文件)文件标记了文件格式标识符。system表空间(由ibdata文件表示)被标记为在一组InnoDB数据库文件中使用的“最高”文件格式,并且当文件被打开时检查这个标记。

创建一个压缩表,或者一个带有ROW_FORMAT=DYNAMIC的表,更新对应file-per-table .ibd文件的文件头和InnoDB数据字典中的表类型,其中包含Barracuda文件格式的标识符。从那以后,这个表就不能在不支持Barracuda文件格式的InnoDB版本中使用了。为了防止异常行为,InnoDB会在打开表时进行兼容性检查。(在很多情况下,ALTER TABLE语句会重新创建一个表,从而改变它的属性。)

一般的表空间,也用*.ibd文件表示,支持Antelope和Barracuda文件格式。

ib文件集的定义
为了避免混淆,为了讨论的目的,我们将术语“ib文件集”定义为InnoDB作为一个单元管理的操作系统文件集。ib文件集合包括以下文件:

.系统表空间(一个或多个ibdata文件),包含内部系统信息(包括内部目录和undo信息),可能包括用户数据和索引。

.零个或多个单表表空间(也称为“file per table”文件,命名为*.ibd文件)。

.InnoDB日志文件;通常有两个,ib_logfile0和ib_logfile1。用于崩溃恢复和备份。

“ib文件集”不包含包含InnoDB表元数据的对应的.frm文件。.frm文件由MySQL创建和管理,有时会与InnoDB中的内部元数据不同步。

多个表(甚至来自多个数据库)可以存储在一个“ib文件集”中。(在MySQL中,“数据库”是表的逻辑集合,其他系统称之为“模式”或“目录”。)

InnoDB启动时的兼容性检查
当InnoDB打开ib文件集时,为了防止可能的崩溃或数据损坏,它会检查是否完全支持ib文件集中正在使用的文件格式。如果系统在崩溃后重新启动,或者“快速关机”(即innodb_fast_shutdown大于零),磁盘上的数据结构(如redo或undo条目,或doublewrite页面)可能对当前软件来说是“太新的”格式。在恢复过程中,如果访问了这些数据结构,可能会对数据文件造成严重的破坏。文件格式的启动检查在任何恢复过程开始之前进行,从而防止新表的一致性问题或MySQL服务器的启动问题。

从版本InnoDB 1.0.1开始,system表空间记录了ib文件集中任何表空间中任何表使用的“最高”文件格式的标识符或标记。这个文件格式标签的检查由配置参数innodb_file_format_check控制,默认是开启的。

如果system表空间中的文件格式标签比当前执行的软件支持的最高版本更新或更高,并且innodb_file_format_check开启,则在服务器启动时会发出以下错误:

InnoDB: Error: the system tablespace is in a
file format that this version doesn't support

mysql> show variables like 'innodb_file_format_check';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_file_format_check | ON    |
+--------------------------+-------+
1 row in set (0.01 sec)

mysql> show variables like 'innodb_file_format';
+--------------------+-----------+
| Variable_name      | Value     |
+--------------------+-----------+
| innodb_file_format | Barracuda |
+--------------------+-----------+
1 row in set (0.02 sec)

也可以设置“innodb_file_format”为文件格式名称。如果当前软件不支持指定的文件格式,这样做可以防止InnoDB启动。它还将“高水位标记”设置为指定的值。如果你手动“降级”ib文件集中的所有表,那么设置innodb_file_format_check的能力(在未来的版本中)是很有用的。如果你随后使用旧版本的InnoDB来访问ib文件集,那么你可以依赖启动时的文件格式检查。

在某些有限的情况下,您可能希望启动服务器并使用ib文件集,该文件集是您正在使用的软件不支持的新文件格式。如果你把配置参数innodb_file_format_check设置为OFF, InnoDB会打开数据库,但会在错误日志中显示如下警告信息:

InnoDB: Warning: the system tablespace is in a
file format that this version doesn't support

这是一个危险的设置,因为它允许恢复过程运行,如果之前的关闭是崩溃或“快速关闭”,可能会破坏数据库。

只有当你确定上一次关闭innodb_fast_shutdown=0时,才应该将innodb_file_format_check设置为OFF,这样基本上没有恢复过程发生。

参数innodb_file_format_check只影响打开数据库时发生的操作,不会影响后续操作。相反,参数innodb_file_format(启用特定的格式)只确定是否可以以启用的格式创建新表,对数据库是否可以打开没有影响。

文件格式标记是一个“高水位标记”,如果创建了一个“更高”格式的表,或者访问一个现有的表进行读或写(假设支持它的格式),那么在服务器启动后,它就会增加。如果你以高于运行软件支持的格式访问一个已经存在的表,系统表空间标签不会更新,但是表级别的兼容性检查会应用(并且会发出错误),如14.10.2.2节“打开表时兼容性检查”所述。每当高水位线更新时,innodb_file_format_check的值也会更新,因此执行SELECT @@innodb_file_format_check;显示当前打开的ib文件集中的表使用的、当前执行软件支持的最新文件格式的名称。

mysql> SELECT @@innodb_file_format_check;
+----------------------------+
| @@innodb_file_format_check |
+----------------------------+
|                          1 |
+----------------------------+
1 row in set (0.00 sec)

打开表时检查兼容性
当第一次访问表时,InnoDB(包括一些InnoDB 1.0之前的版本)会检查存储表的表空间的文件格式是否完全支持。这种检查可以防止使用“太新”数据结构的表出现崩溃或损坏。

使用某个版本支持的任何文件格式的所有表都可以读或写(假设用户有足够的权限)。设置系统配置参数innodb_file_format可以防止创建使用特定文件格式的新表,即使给定的版本支持这种文件格式。这样的设置可以用来保持向后兼容性,但它不会阻止访问任何使用支持的格式的表。

如果在创建表时使用了新的文件格式,那么5.0.21之前的MySQL版本不能可靠地使用新版本创建的数据库文件。为了防止各种错误或损坏,InnoDB在打开文件时(例如,第一次访问一张表时)会检查文件格式的兼容性。如果当前运行的InnoDB版本不支持InnoDB数据字典中表类型标识的文件格式,MySQL报告如下错误:

ERROR 1146 (42S02): Table 'test.t1' doesn't exist

InnoDB也会在错误日志中写入一条消息:

InnoDB: table test/t1: unknown table type 33

MySQL 4.1之前的InnoDB版本在数据库文件中不包含表格式标识符,MySQL 5.0.21之前的版本不包含表格式兼容性检查。因此,如果在5.0.21之前的InnoDB版本中使用了较新的文件格式的表,则无法确保操作是正确的。

InnoDB 1.0及更高版本的文件格式管理能力(表空间标记和运行时检查)允许InnoDB尽快验证运行版本的软件能够正确处理数据库中存在的表。

如果你允许InnoDB打开一个包含它不支持的文件格式的数据库(通过设置参数innodb_file_format_check为OFF),本节描述的表级检查仍然适用。

在使用InnoDB插件的MySQL 5.1之前的版本中强烈建议用户不要使用包含Barracuda文件格式表的数据库文件。可以使用Antelope格式重建这样的表。

识别正在使用的文件格式
如果您使用innodb_file_format配置选项启用不同的文件格式,则更改仅适用于新创建的表。此外,当您创建一个新表时,包含该表的表空间被标记为支持表特性所需的“最早”或“最简单”文件格式。例如,如果您启用了Barracuda文件格式,并创建了一个不使用Dynamic或Compressed行格式的新表,那么包含该表的新表空间将被标记为使用Antelope文件格式。

很容易识别给定表使用的文件格式。如果SHOW TABLE STATUS报告的行格式是Compact或Redundant,则表使用Antelope文件格式。如果SHOW TABLE STATUS报告的行格式是Compressed或Dynamic,则表使用Barracuda文件格式。


mysql> SHOW TABLE STATUS\G
*************************** 1. row ***************************
           Name: big_table
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 3370592
 Avg_row_length: 205
    Data_length: 694140928
Max_data_length: 0
   Index_length: 0
      Data_free: 3145728
 Auto_increment: 3326977
    Create_time: 2023-07-06 18:05:18
    Update_time: NULL
     Check_time: NULL
      Collation: utf8mb4_general_ci
       Checksum: NULL
 Create_options:
        Comment:

您还可以使用InnoDB INFORMATION_SCHEMA表识别给定表或表空间使用的文件格式。例如:

mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES WHERE NAME='undo/big_table'\G
*************************** 1. row ***************************
     TABLE_ID: 488
         NAME: undo/big_table
         FLAG: 33
       N_COLS: 25
        SPACE: 487
  FILE_FORMAT: Barracuda
   ROW_FORMAT: Dynamic
ZIP_PAGE_SIZE: 0
   SPACE_TYPE: Single
1 row in set (0.00 sec)


mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE NAME='undo/big_table'\G
*************************** 1. row ***************************
         SPACE: 487
          NAME: undo/big_table
          FLAG: 33
   FILE_FORMAT: Barracuda
    ROW_FORMAT: Dynamic
     PAGE_SIZE: 16384
 ZIP_PAGE_SIZE: 0
    SPACE_TYPE: Single
 FS_BLOCK_SIZE: 4096
     FILE_SIZE: 708837376
ALLOCATED_SIZE: 708841472
1 row in set (0.00 sec)

修改文件格式
每个InnoDB表空间文件(名称匹配*.ibd)都被标记为用于创建其表和索引的文件格式。修改文件格式的方法是重新创建表及其索引。重建表及其索引的最简单方法是在每个想要修改的表上使用以下命令:

ALTER TABLE t ROW_FORMAT=format_name;

如果您正在修改文件格式以降级到较旧的MySQL版本,则可能存在表存储格式的不兼容性,需要额外的步骤。

MySQL InnoDB页面压缩

InnoDB页面压缩
innoDB支持页面级别的表压缩,这些表位于file-per-table表空间中。这个特性被称为透明页面压缩。通过使用CREATE TABLE或ALTER TABLE指定compression属性,可以启用页面压缩。支持的压缩算法包括Zlib和LZ4。

支持平台
页面压缩需要稀疏文件和穿孔支持。在带有NTFS的Windows上,以及以下支持mysql的Linux平台子集上支持页面压缩,其中内核级别提供了hole punch支持:
. RHEL 7 and derived distributions that use kernel version 3.10.0-123 or higher
. OEL 5.10 (UEK2) kernel version 2.6.39 or higher
. OEL 6.5 (UEK3) kernel version 3.8.13 or higher
. OEL 7.0 kernel version 3.8.13 or higher
. SLE11 kernel version 3.0-x
. SLE12 kernel version 3.12-x
. OES11 kernel version 3.0-x
. Ubuntu 14.0.4 LTS kernel version 3.13 or higher
. Ubuntu 12.0.4 LTS kernel version 3.2 or higher
. Debian 7 kernel version 3.2 or higher

对于给定的Linux发行版,所有可用的文件系统可能都不支持打孔。

页面压缩工作原理
在写入一页时,使用指定的压缩算法对其进行压缩。压缩后的数据被写入磁盘,在磁盘上打孔机制从页的末尾释放空块。如果压缩失败,数据会原样写入。

Linux上的打孔尺寸
在Linux系统中,文件系统块长度是用于打孔的单位长度。因此,只有当页面数据可以压缩到小于或等于InnoDB页面大小减去文件系统块大小时,页面压缩才有效。例如,如果innodb_page_size=16K,文件系统块大小为4K,则页面数据必须压缩到小于或等于12K,才能穿孔。

Windows上的打孔尺寸
在Windows系统上,用于稀疏文件的底层基础设施是基于NTFS压缩。冲孔尺寸为NTFS压缩单元,是NTFS集群尺寸的16倍。集群大小及其压缩单位如下表所示:

Windows NTFS集群大小和压缩单元
集群大小 压缩单元
512 Bytes 8 KB
1 KB 16 KB
2 KB 32 KB
4 KB 64 KB

在Windows系统上,只有当页面数据可以压缩到小于或等于InnoDB页面大小减去压缩单位大小时,页面压缩才有效。

NTFS集群的默认大小是4K,其中压缩单位大小是64K。这意味着页面压缩对于开箱即用的Windows NTFS配置没有任何好处,因为innodb_page_size的最大值也是64K。

为了让页面压缩在Windows上工作,创建文件系统时必须使用小于4K的集群大小,并且innodb_page_size必须至少是压缩单元大小的两倍。例如,为了让页面压缩在Windows上工作,你可以构建一个512字节的集群文件系统(压缩单位是8KB),并将InnoDB初始化为16K或更高的值。

启用页面压缩
要启用页面压缩,请在CREATE TABLE语句中指定compression属性。例如:

mysql> CREATE TABLE t7 (c1 INT) COMPRESSION="zlib";
Query OK, 0 rows affected (0.03 sec)

您还可以在ALTER TABLE语句中启用页面压缩。但是,ALTER TABLE…COMPRESSION只更新表空间的COMPRESSION属性。在设置新的压缩算法之后,对表空间的写入将使用新的设置,但是要将新的压缩算法应用到现有页面,必须使用OPTIMIZE table重建表。
mysql>  CREATE TABLE t8 (c1 INT);
Query OK, 0 rows affected (0.01 sec)

mysql>  ALTER TABLE t8 COMPRESSION="zlib";
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> OPTIMIZE TABLE t8;
+---------+----------+----------+-------------------------------------------------------------------+
| Table   | Op       | Msg_type | Msg_text                                                          |
+---------+----------+----------+-------------------------------------------------------------------+
| undo.t8 | optimize | note     | Table does not support optimize, doing recreate + analyze instead |
| undo.t8 | optimize | status   | OK                                                                |
+---------+----------+----------+-------------------------------------------------------------------+
2 rows in set (0.02 sec)

对于Innodb存储引擎不能支持optimize table,Myisam存储引擎支持optimize table。

禁用页面压缩
要禁用页面压缩,请使用ALTER TABLE设置compression =None。在设置COMPRESSION=None后,写入表空间不再使用页面压缩。要解压缩现有页面,必须在设置COMPRESSION=None后使用OPTIMIZE TABLE重新构建表。

mysql> ALTER TABLE t8 COMPRESSION="None";
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> OPTIMIZE TABLE t8;
+---------+----------+----------+-------------------------------------------------------------------+
| Table   | Op       | Msg_type | Msg_text                                                          |
+---------+----------+----------+-------------------------------------------------------------------+
| undo.t8 | optimize | note     | Table does not support optimize, doing recreate + analyze instead |
| undo.t8 | optimize | status   | OK                                                                |
+---------+----------+----------+-------------------------------------------------------------------+
2 rows in set (0.02 sec)

mysql> show create table t8;
+-------+------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                               |
+-------+------------------------------------------------------------------------------------------------------------+
| t8    | CREATE TABLE `t8` (
  `c1` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMPRESSION='None' |
+-------+------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

页面压缩元数据
页面压缩元数据可以在INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES表中找到。在下列列中:
.FS_BLOCK_SIZE:文件系统块大小,打孔时使用的单位大小。

.FILE_SIZE:文件的外观大小,表示未压缩时文件的最大大小。

.ALLOCATED_SIZE:文件的实际大小,即在磁盘上分配的空间量。

在类unix系统上,使用ls -l tablespace_name.ibd以字节为单位给出了明显的文件长度(相当于FILE_SIZE)。要查看磁盘上实际分配的空间大小(相当于ALLOCATED_SIZE),可以使用du –block-size=1 tablespace_name.ibd。–block-size=1选项以字节(而不是块)打印分配的空间,以便与ls -l的输出进行比较。

使用SHOW CREATE TABLE查看当前页面压缩设置(Zlib、Lz4或None)。一个表可能包含具有不同压缩设置的混合页面。

在下面的示例中,从INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES表中检索employees表的页面压缩元数据。

# Create the employees table with Zlib page compression
mysql> CREATE TABLE employees (
    -> emp_no INT NOT NULL,
    -> birth_date DATE NOT NULL,
    -> first_name VARCHAR(14) NOT NULL,
    -> last_name VARCHAR(16) NOT NULL,
    -> gender ENUM ('M','F') NOT NULL,
    -> hire_date DATE NOT NULL,
    -> PRIMARY KEY (emp_no)
    -> ) COMPRESSION="zlib";
Query OK, 0 rows affected (0.01 sec)

# Insert data
mysql> insert into employees values(1,'1985-02-28','yong','jing','M','2023-08-04');
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)


# Query page compression metadata in INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES

mysql> SELECT SPACE, NAME, FS_BLOCK_SIZE, FILE_SIZE, ALLOCATED_SIZE FROM
    -> INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE NAME='undo/employees'\G
*************************** 1. row ***************************
         SPACE: 498
          NAME: undo/employees
 FS_BLOCK_SIZE: 4096
     FILE_SIZE: 98304
ALLOCATED_SIZE: 61440
1 row in set (0.01 sec)

employees表的页面压缩元数据显示,表面文件大小为98304字节,而实际文件大小(使用页面压缩)为61440字节。文件系统块大小为4096字节,即穿孔时使用的块大小。

页面压缩限制和使用说明
.如果文件系统块大小(或Windows上的压缩单位大小)* 2>innodb_page_size禁用页面压缩。

.对于驻留在共享表空间中的表,包括system表空间、临时表空间和一般表空间,不支持页面压缩。

.undo日志表空间不支持页面压缩。

.重做日志页面不支持页面压缩。

.用于空间索引的R树页面不能被压缩。

.属于压缩表(ROW_FORMAT= compressed)的页面保持原样。

.在恢复过程中,更新后的页面将以未压缩的形式输出。

.在不支持所用压缩算法的服务器上加载页压缩表空间会导致I/O错误。

.在降级到不支持页面压缩的MySQL早期版本之前,请解压使用页面压缩特性的表。要解压一张表,运行ALTER table…compression=None和
optimize table。

.如果所使用的压缩算法在Linux和Windows服务器上可用,则可以在Linux和Windows服务器之间复制页压缩表空间。

.在将页面压缩的表空间文件从一台主机移动到另一台主机时保持页面压缩需要一个保留稀疏文件的实用程序。

.与其他平台相比,使用NVMFS的Fusion-io硬件可以实现更好的页面压缩,因为NVMFS旨在利用打孔功能。

.在InnoDB页面大小较大和文件系统块相对较小的情况下使用页面压缩特性可能会导致写入放大。例如,InnoDB页的最大大小为64KB,而文件系统块的大小为4KB,这可能会提高压缩性能,但也可能会增加对缓冲池的需求,从而导致I/O增加和潜在的写放大。

MySQL InnoDB表压缩

InnoDB表压缩
InnoDB表压缩,它支持在file_per_table表空间或普通表空间中的InnoDB表。使用ROW_FORMAT=COMPRESSED属性与CREATE Table或ALTER Table一起启用表压缩。

表压缩概述
InnoDB表压缩,它支持在file_per_table表空间或普通表空间中的InnoDB表。使用ROW_FORMAT=COMPRESSED属性与CREATE Table或ALTER Table一起启用表压缩。

表压缩概述
由于处理器和缓存内存的速度比磁盘存储设备提高得更快,因此许多工作负载都是磁盘绑定的。数据压缩可以实现更小的数据库大小、更少的I/O和更高的吞吐量,而增加CPU利用率的代价很小。压缩对于读密集型应用程序尤其有价值,因为系统有足够的RAM将经常使用的数据保存在内存中。

使用ROW_FORMAT=COMPRESSED创建的InnoDB表可以在磁盘上使用比配置的innodb_page_size值更小的页面大小。更小的页面需要更少的I/O来读写磁盘,这对于SSD设备来说特别有价值。

压缩后的页面大小通过CREATE TABLE或ALTER TABLE KEY_BLOCK_SIZE参数指定。不同的页面大小要求将表放在file-per-table表空间或通用表空间中,而不是放在系统表空间中,因为系统表空间不能存储压缩表。

无论KEY_BLOCK_SIZE值如何,压缩级别都是相同的。当您为KEY_BLOCK_SIZE指定较小的值时,您将获得越来越小的页面所带来的I/O好处。但是,如果指定的值太小,则在数据值无法压缩到足以容纳每个页中的多行时,会有重新组织页的额外开销。表的KEY_BLOCK_SIZE可以有多小是有严格限制的,这取决于每个索引的键列的长度。如果指定的值太小,CREATE TABLE或ALTER TABLE语句将失败。

在缓冲池中,压缩数据保存在小页面中,页面大小基于KEY_BLOCK_SIZE值。为了提取或更新列值,MySQL还使用未压缩的数据在缓冲池中创建一个未压缩的页面。在缓冲池中,对未压缩页面的任何更新也会被重新写回等效的压缩页面。您可能需要调整缓冲池的大小,以容纳压缩和未压缩页面的附加数据,尽管在需要空间时将未压缩页面从缓冲池中取出,然后在下一次访问时再次解压缩。

创建压缩表
压缩表可以在file-per-table表空间中创建,也可以在一般表空间中创建。InnoDB系统表空间不支持表压缩。系统表空间(空间0,.ibdata文件)可以包含用户创建的表,但它也包含内部系统数据,这些数据从未被压缩过。因此,压缩只适用于存储在file-per-table或一般表空间中的表(和索引)。

在file-per-table表空间中创建压缩表
要在file-per-table表空间中创建压缩表,innodb_file_per_table必须启用(MySQL 5.6.6默认设置),innodb_file_format必须设置为Barracuda。您可以在MySQL配置文件(my.cnf或my.ini)中设置这些参数,也可以使用set语句动态设置这些参数

在配置了innodb_file_per_table和innodb_file_format选项后,在CREATE TABLE或ALTER TABLE语句中指定ROW_FORMAT=COMPRESSED或KEY_BLOCK_SIZE子句,或两者都指定,以在file-per-table表空间中创建压缩表。

例如,你可以使用以下语句:

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

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

mysql> CREATE TABLE t1
    -> (c1 INT PRIMARY KEY)
    -> ROW_FORMAT=COMPRESSED
    -> KEY_BLOCK_SIZE=8;
Query OK, 0 rows affected (0.01 sec)

在通用表空间中创建压缩表
要在通用表空间中创建压缩表,必须为通用表空间定义FILE_BLOCK_SIZE,该值在创建表空间时指定。FILE_BLOCK_SIZE值必须是相对于innodb_page_size值的有效压缩页大小,并且由CREATE table或ALTER table KEY_BLOCK_SIZE子句定义的压缩表的页大小必须等于FILE_BLOCK_SIZE/1024。例如,innodb_page_size=16384, FILE_BLOCK_SIZE=8192,则表的KEY_BLOCK_SIZE必须为8。

下面的示例演示创建一个通用表空间和添加一个压缩表。本例innodb_page_size的默认值为16K。FILE_BLOCK_SIZE为8192要求压缩表的KEY_BLOCK_SIZE为8。

mysql> show variables like 'innodb_page_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+
1 row in set (0.02 sec)

mysql> CREATE TABLESPACE ts3 ADD DATAFILE 'ts3.ibd' FILE_BLOCK_SIZE = 8192 Engine=InnoDB;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE t2 (c1 INT PRIMARY KEY) TABLESPACE ts3 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
Query OK, 0 rows affected (0.01 sec)

注意:
.如果指定ROW_FORMAT=COMPRESSED,则可以省略KEY_BLOCK_SIZE;KEY_BLOCK_SIZE默认为innodb_page_size值的一半。

.如果你指定了一个有效的KEY_BLOCK_SIZE值,你可以忽略ROW_FORMAT=COMPRESSED;自动启用压缩功能。

.要确定KEY_BLOCK_SIZE的最佳值,通常需要为该子句创建具有不同值的同一表的多个副本,然后测量生成的.ibd文件的大小,并查看每个文件在实际工作负载下的性能如何。对于一般的表空间,请记住,删除表并不会减少一般表空间.ibd文件的大小,也不会将磁盘空间返回给操作系统。

.KEY_BLOCK_SIZE值被视为提示;如果需要,InnoDB可以使用不同的大小。对于file-per-table表空间,KEY_BLOCK_SIZE只能小于或等于innodb_page_size值。如果指定的值大于innodb_page_size,则忽略指定的值,并发出警告,并将KEY_BLOCK_SIZE设置为innodb_page_size值的一半。如果innodb_strict_mode=ON,指定无效的KEY_BLOCK_SIZE值将返回错误。对于一般的表空间,有效的KEY_BLOCK_SIZE值取决于表空间的FILE_BLOCK_SIZE设置。

.32k和64k页面大小不支持压缩。

.InnoDB数据页的默认未压缩大小是16KB。根据选项值的组合,MySQL使用1KB、2KB、4KB、8KB或16KB的页大小来存放表空间数据文件(.ibd文件)。实际的压缩算法不受KEY_BLOCK_SIZE值的影响;该值决定每个压缩块的大小,进而影响每个压缩页中可以装入多少行。

.当在file-per-table表空间中创建压缩表时,将KEY_BLOCK_SIZE设置为InnoDB页面大小通常不会导致太多压缩。例如,设置KEY_BLOCK_SIZE=16通常不会导致太多的压缩,因为正常的InnoDB页面大小是16KB。此设置对于具有许多长BLOB、VARCHAR或TEXT列的表可能仍然有用,因为这些值通常可以很好地压缩,因此可能需要更少的溢出页。对于一般的表空间,KEY_BLOCK_SIZE值等于InnoDB页面大小是不允许的。

.表的所有索引(包括聚集索引)使用相同的页面大小进行压缩,这在CREATE table或ALTER table语句中指定。表属性如ROW_FORMAT和KEY_BLOCK_SIZE不是InnoDB表的CREATE INDEX语法的一部分,如果指定了,它们将被忽略(尽管,如果指定了,它们将出现在SHOW CREATE Table语句的输出中)。

压缩表的限制
.5.1之前的MySQL版本不能处理压缩表。

.压缩表不能存储在InnoDB系统表空间中。

.一般表空间可以包含多个表,但是压缩表和未压缩表不能在同一个表空间中共存。

.压缩适用于整个表及其所有关联索引,而不是单独的行,尽管子句名为ROW_FORMAT。

InnoDB表压缩调优
大多数情况下,InnoDB数据存储和压缩中描述的内部优化可以确保系统在压缩数据下运行良好。但是,由于压缩的效率取决于数据的性质,因此您可以做出影响压缩表性能的决定:
.要压缩哪些表。

.要使用的压缩页面大小。

.是否根据运行时性能特征(例如系统压缩和解压缩数据所花费的时间)调整缓冲池的大小。工作负载更像数据仓库(主要是查询)还是OLTP系统(查询和DML的混合)。

.如果系统在压缩表上执行DML操作,并且数据分布的方式在运行时导致代价高昂的压缩失败,那么您可能需要调整其他高级配置选项。

何时使用压缩
一般来说,压缩在包含合理数量的字符串列的表上效果最好,并且数据的读取次数远远多于写入次数。因为没有保证的方法来预测压缩是否有利于特定情况,所以总是使用在代表性配置上运行的特定工作负载和数据集进行测试。在决定压缩哪些表时,请考虑以下因素。

数据特性与压缩
在压缩数据文件时,决定压缩效率的一个关键因素是数据本身的性质。回想一下,压缩的工作原理是识别数据块中重复的字节串。完全随机的数据是最糟糕的情况。典型的数据通常有重复的值,因此压缩效果很好。无论是定义在CHAR、VARCHAR、TEXT还是BLOB列中的字符串,通常压缩效果都很好。另一方面,主要包含二进制数据(整数或浮点数)或之前压缩过的数据(例如JPEG或PNG图像)的表通常不能很好地压缩,或者显著压缩,或者根本压缩不了。

你可以选择是否为每个InnoDB表开启压缩。一张表及其所有索引使用相同的(压缩的)页面大小。可能是包含表中所有列数据的主键(聚集)索引比辅助索引压缩更有效。对于那些行很长的情况,使用压缩可能会导致长列值被存储在“页外”,那些溢出的页面可以很好地压缩。考虑到这些因素,对于许多应用程序,有些表的压缩比其他表的压缩更有效,并且您可能会发现只有在压缩了一部分表的情况下,您的工作负载才能达到最佳性能。

要确定是否要压缩一个特定的表,可以进行实验。通过使用对未压缩表的.ibd文件副本实现LZ77压缩的实用程序(如gzip或WinZip),您可以粗略估计数据压缩的效率。与基于文件的压缩工具相比,MySQL压缩表的压缩更少,因为MySQL根据页面大小(默认为16KB)分块压缩数据。除了用户数据之外,页面格式还包括一些未压缩的内部系统数据。基于文件的压缩工具可以检查更大的数据块,因此可能会在一个巨大的文件中发现更多重复的字符串比MySQL在单个页面中所能找到的多。

在特定表上测试压缩的另一种方法是将一些数据从未压缩的表复制到一个类似的、压缩的表(具有相同的索引)中,并查看生成的.ibd文件的大小。
例如:

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

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

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

mysql> CREATE TABLE big_table AS SELECT * FROM information_schema.columns;
Query OK, 3249 rows affected, 1 warning (0.52 sec)
Records: 3249  Duplicates: 0  Warnings: 1

mysql> INSERT INTO big_table SELECT * FROM big_table;
Query OK, 3249 rows affected (0.05 sec)
Records: 3249  Duplicates: 0  Warnings: 0

mysql> INSERT INTO big_table SELECT * FROM big_table;
Query OK, 6498 rows affected (0.10 sec)
Records: 6498  Duplicates: 0  Warnings: 0

mysql> INSERT INTO big_table SELECT * FROM big_table;
Query OK, 12996 rows affected (0.20 sec)
Records: 12996  Duplicates: 0  Warnings: 0

mysql> INSERT INTO big_table SELECT * FROM big_table;
Query OK, 25992 rows affected (0.51 sec)
Records: 25992  Duplicates: 0  Warnings: 0

mysql> INSERT INTO big_table SELECT * FROM big_table;
Query OK, 51984 rows affected (0.78 sec)
Records: 51984  Duplicates: 0  Warnings: 0

mysql> INSERT INTO big_table SELECT * FROM big_table;
Query OK, 103968 rows affected (1.40 sec)
Records: 103968  Duplicates: 0  Warnings: 0

mysql> INSERT INTO big_table SELECT * FROM big_table;
Query OK, 207936 rows affected (2.84 sec)
Records: 207936  Duplicates: 0  Warnings: 0

mysql> INSERT INTO big_table SELECT * FROM big_table;
Query OK, 415872 rows affected (5.72 sec)
Records: 415872  Duplicates: 0  Warnings: 0

mysql> INSERT INTO big_table SELECT * FROM big_table;
Query OK, 831744 rows affected (11.46 sec)
Records: 831744  Duplicates: 0  Warnings: 0

mysql> INSERT INTO big_table SELECT * FROM big_table;
Query OK, 1663488 rows affected (22.86 sec)
Records: 1663488  Duplicates: 0  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> ALTER TABLE big_table ADD id int unsigned NOT NULL PRIMARY KEY auto_increment;
Query OK, 0 rows affected (48.18 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SHOW CREATE TABLE big_table\G
*************************** 1. row ***************************
       Table: big_table
Create Table: CREATE TABLE `big_table` (
  `TABLE_CATALOG` varchar(512) CHARACTER SET utf8 NOT NULL DEFAULT '',
  `TABLE_SCHEMA` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
  `TABLE_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
  `COLUMN_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
  `ORDINAL_POSITION` bigint(21) unsigned NOT NULL DEFAULT '0',
  `COLUMN_DEFAULT` longtext CHARACTER SET utf8,
  `IS_NULLABLE` varchar(3) CHARACTER SET utf8 NOT NULL DEFAULT '',
  `DATA_TYPE` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
  `CHARACTER_MAXIMUM_LENGTH` bigint(21) unsigned DEFAULT NULL,
  `CHARACTER_OCTET_LENGTH` bigint(21) unsigned DEFAULT NULL,
  `NUMERIC_PRECISION` bigint(21) unsigned DEFAULT NULL,
  `NUMERIC_SCALE` bigint(21) unsigned DEFAULT NULL,
  `DATETIME_PRECISION` bigint(21) unsigned DEFAULT NULL,
  `CHARACTER_SET_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
  `COLLATION_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
  `COLUMN_TYPE` longtext CHARACTER SET utf8 NOT NULL,
  `COLUMN_KEY` varchar(3) CHARACTER SET utf8 NOT NULL DEFAULT '',
  `EXTRA` varchar(30) CHARACTER SET utf8 NOT NULL DEFAULT '',
  `PRIVILEGES` varchar(80) CHARACTER SET utf8 NOT NULL DEFAULT '',
  `COLUMN_COMMENT` varchar(1024) CHARACTER SET utf8 NOT NULL DEFAULT '',
  `GENERATION_EXPRESSION` longtext CHARACTER SET utf8 NOT NULL,
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3326977 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

mysql> select count(id) from big_table;
+-----------+
| count(id) |
+-----------+
|   3326976 |
+-----------+
1 row in set (1.09 sec)


mysql> \! ls -l /mysqldata/mysql/undo/big_table.ibd
-rw-r-----. 1 mysql mysql 708837376 Jul  6 18:06 /mysqldata/mysql/undo/big_table.ibd


mysql> CREATE TABLE key_block_size_4 LIKE big_table;
Query OK, 0 rows affected (0.02 sec)

mysql> ALTER TABLE key_block_size_4 key_block_size=4 row_format=compressed;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> INSERT INTO key_block_size_4 SELECT * FROM big_table;
Query OK, 3326976 rows affected (2 min 52.11 sec)
Records: 3326976  Duplicates: 0  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> \! ls -l /mysqldata/mysql/undo/key_block_size_4.ibd
-rw-r-----. 1 mysql mysql 155189248 Jul  6 18:11 /mysqldata/mysql/undo/key_block_size_4.ibd

这个实验产生了以下数字,当然,根据您的表结构和数据,这些数字可能会有很大的不同:
-rw-r-----. 1 mysql mysql 708837376 Jul  6 18:06 /mysqldata/mysql/undo/big_table.ibd
-rw-r-----. 1 mysql mysql 155189248 Jul  6 18:11 /mysqldata/mysql/undo/key_block_size_4.ibd

要查看压缩对您的特定工作负载是否有效:
.对于简单的测试,使用一个没有其他压缩表的MySQL实例,并对INFORMATION_SCHEMA.INNODB_CMP表进行查询。

.对于涉及多个压缩表的工作负载的更复杂的测试,可以对INFORMATION_SCHEMA.INNODB_CMP_PER_INDEX表进行查询。因为收集INNODB_CMP_PER_INDEX表中的统计信息非常昂贵,所以你必须在查询这张表之前启用配置选项innodb_cmp_per_index_enabled,并且你可以将这样的测试限制在开发服务器或非关键的从服务器上。

.对正在测试的压缩表运行一些典型的SQL语句。

.通过查询INFORMATION_SCHEMA.INNODB_CMP或INFORMATION_SCHEMA.INNODB_CMP_PER_INDEX表,并比较COMPRESS_OPS和COMPRESS_OPS_OK,可以查看成功压缩操作与总体压缩操作的比率。

.如果成功完成的压缩操作的百分比很高,则该表可能是压缩的一个很好的候选者。

.如果压缩失败的比例很高,您可以调整innodb_compression_level、innodb_compression_failure_threshold_pct和innodb_compression_pad_pct_max选项,并尝试进一步的测试。

数据库压缩与应用程序压缩
决定压缩应用程序中的数据还是表中的数据;不要对同一数据同时使用两种压缩方式。当压缩应用程序中的数据并将结果存储在压缩表中时,几乎不可能节省额外的空间,而双重压缩只会浪费CPU周期。

在数据库中压缩
当启用时,MySQL表压缩是自动的,并且适用于所有列和索引值。这些列仍然可以使用LIKE之类的操作符进行测试,即使索引值被压缩了,排序操作仍然可以使用索引。因为索引通常占数据库总大小的很大一部分,所以压缩可以显著节省存储、I/O或处理器时间。压缩和解压缩操作发生在数据库服务器上,这可能是一个强大的系统,其大小可以处理预期的负载。

应用程序压缩
如果在应用程序中压缩文本等数据,在将其插入数据库之前,通过压缩某些列而不压缩其他列,可以为压缩效果不好的数据节省开销。这种方法使用CPU周期在客户机机器上而不是数据库服务器上进行压缩和解压缩,这可能适用于具有许多客户机的分布式应用程序,或者客户机机器有空闲CPU周期的情况。

混合方法
当然,也可以结合使用这两种方法。对于某些应用程序,可能适合使用一些压缩表和一些非压缩表。最好从外部压缩一些数据(并将其存储在未压缩的表中),并允许MySQL压缩应用程序中的其他表。与往常一样,预先设计和真实的测试对于做出正确的决定是有价值的。

工作负载特征和压缩
除了选择要压缩的表(和页面大小)之外,工作负载是性能的另一个关键决定因素。如果应用程序主要是读操作,而不是更新操作,那么在索引页耗尽MySQL为压缩数据维护的每页“修改日志”的空间后,需要重新组织和重新压缩的页面就会更少。如果更新主要是更改非索引的列,或者那些包含blob或大字符串的列,它们恰巧存储在“页外”,那么压缩的开销是可以接受的。如果对表的唯一更改是使用单调递增主键的插入,并且辅助索引很少,那么就不需要重新组织和重新压缩索引页。由于MySQL可以通过修改未压缩的数据“就地”删除压缩页上的行,因此表上的删除操作是相对高效的。

对于某些环境,加载数据所需的时间可能与运行时检索同样重要。特别是在数据仓库环境中,许多表可能是只读的或大多数情况下是只读的。在这些情况下,以增加加载时间为代价进行压缩可能是可接受的,也可能是不可接受的,除非由此减少磁盘读取或存储成本的节省非常显著。

基本上,当CPU时间可以用于压缩和解压缩数据时,压缩工作得最好。因此,如果您的工作负载是I/O绑定的,而不是cpu绑定的,您可能会发现压缩可以提高整体性能。当您使用不同的压缩配置测试应用程序性能时,请在与生产系统的计划配置类似的平台上进行测试。

配置特点及压缩
从磁盘读写数据库页面是系统性能最慢的方面。压缩试图通过使用CPU时间来压缩和解压缩数据来减少I/O,当I/O相对于处理器周期来说是一种稀缺资源时,这种方法最有效。

当运行在具有快速多核cpu的多用户环境中时,情况尤其如此。当压缩表的一页在内存中时,MySQL通常会使用额外的内存,通常是16KB,在缓冲池中用于页面的未压缩副本。自适应LRU算法试图平衡压缩页和未压缩页之间的内存使用,以考虑工作负载是以I/ o绑定方式还是cpu绑定方式运行。尽管如此,在使用压缩表时,为缓冲池提供更多内存的配置往往比内存高度受限的配置运行得更好。

选择压缩页面大小
压缩页大小的最佳设置取决于表及其索引包含的数据的类型和分布。压缩后的页面大小应该总是大于最大记录大小,否则操作可能会失败,正如b树页面压缩中提到的那样。

将压缩页的大小设置得太大会浪费一些空间,但并不需要经常压缩页。如果压缩页大小设置得太小,插入或更新可能需要耗时的重新压缩,并且b树节点可能需要更频繁地分割,导致数据文件更大和索引效率更低。

通常,您可以将压缩页面大小设置为8K或4K字节。考虑到InnoDB表的最大行大小大约是8K, KEY_BLOCK_SIZE=8通常是一个安全的选择。

在运行时监控InnoDB表压缩
应用程序的整体性能、CPU和I/O利用率以及磁盘文件的大小都可以很好地反映压缩对应用程序的有效性。基于“InnoDB表的压缩调优”中的性能调优建议,并展示如何发现在初始测试中可能不会出现的问题。

为了更深入地了解压缩表的性能考虑因素,你可以使用INFORMATION_SCHEMA方案中的表来监控运行时的压缩性能。这些表反映了内存的内部使用情况和总体的压缩率。

INNODB_CMP表报告了每个压缩页大小(KEY_BLOCK_SIZE)的压缩活动信息。这些表中的信息是系统范围的:它总结了数据库中所有压缩表的压缩统计信息。当没有其他压缩表被访问时,通过检查这些表,可以使用这些数据来帮助决定是否要压缩表。它在服务器上的开销相对较低,所以你可以定期在生产服务器上查询它,以检查压缩特性的整体效率。

INNODB_CMP_PER_INDEX表报告了关于单个表和索引的压缩活动的信息。这些信息更有针对性,对于评估压缩效率和诊断一次一个表或索引的性能问题更有用。(因为每个InnoDB表都表示为一个聚集索引,MySQL在这个上下文中没有对表和索引做很大的区分。)INNODB_CMP_PER_INDEX表确实涉及大量的开销,因此它更适合于开发服务器,在那里你可以单独比较不同工作负载、数据和压缩设置的影响。为了防止意外造成这种监控开销,用户必须在查询INNODB_CMP_PER_INDEX表之前启用innodb_cmp_per_index_enabled配置选项。

要考虑的主要统计数据是执行压缩和解压缩操作的数量和时间。由于MySQL在修改后会在b树节点满到无法包含压缩后的数据时拆分b树节点,因此需要将“成功”的压缩操作的数量与此类操作的总体数量进行比较。根据INNODB_CMP和INNODB_CMP_PER_INDEX表中的信息以及应用程序的整体性能和硬件资源利用率,您可以更改硬件配置,调整缓冲池的大小,选择不同的页面大小,或者选择不同的表集进行压缩。

如果压缩和解压缩所需的CPU时间非常多,那么在相同的数据、应用程序工作负载和压缩表集的情况下,改用更快的CPU或多核CPU可以帮助提高性能。增加缓冲池的大小也可能有助于提高性能,这样更多未压缩的页可以驻留在内存中,减少了对仅以压缩形式存在于内存中的页进行压缩的需求。

总体上大量的压缩操作(与应用程序中的插入、更新和删除操作的数量以及数据库的大小相比)可能表明某些压缩表的更新过于频繁,无法进行有效的压缩。如果是这样,请选择更大的页面大小,或者对要压缩的表更有选择性。

如果“成功”的压缩操作数量(COMPRESS_OPS_OK)占压缩操作总数(COMPRESS_OPS)的比例很高,那么系统可能性能良好。如果这个比率很低,那么MySQL会频繁地重组、重新压缩和分裂b树节点。在这种情况下,请避免压缩某些表,或者增大某些已压缩表的KEY_BLOCK_SIZE。如果你关闭了表的压缩,导致应用程序中的“压缩失败”数量超过了总数的1%或2%,那么你可以关闭表的压缩。(在数据加载等临时操作期间,这样的失败率是可以接受的)。

如何压缩InnoDB表
本节描述一些关于InnoDB表压缩的内部实现细节。这里提供的信息可能对性能调优有帮助,但对于压缩的基本使用来说没有必要知道。

压缩算法
有些操作系统在文件系统级别实现压缩。文件通常被划分为固定大小的块,然后被压缩为可变大小的块,这很容易导致碎片。每次数据块中的某些内容被修改时,整个数据块都会被重新压缩,然后再写入磁盘。这些特性使得这种压缩技术不适合在更新密集型数据库系统中使用。

MySQL在著名的zlib库的帮助下实现了压缩,该库实现了LZ77压缩算法。该压缩算法成熟、稳定,在CPU利用率和数据压缩方面都是有效的。该算法是“无损”的,因此原始未压缩的数据总是可以从压缩的形式中重构出来。LZ77压缩的工作原理是找出在要压缩的数据中重复出现的数据序列。数据中值的模式决定了它的压缩效果,但典型的用户数据通常会压缩50%或更多。

InnoDB只支持zlib库到1.2.3版本。

与应用程序执行的压缩或其他一些数据库管理系统的压缩特性不同,InnoDB压缩同时适用于用户数据和索引。在许多情况下,索引占数据库总大小的40-50%或更多,因此这种差异是显著的。当数据集的压缩工作正常时,InnoDB数据文件的大小(file-per-table表空间或一般的表空间.idb文件)是未压缩大小的25%到50%,甚至可能更小。根据工作负载的不同,这个较小的数据库可以反过来减少I/O,增加吞吐量,但在增加CPU利用率方面代价不大。您可以通过修改innodb_compression_level配置选项来调整压缩级别和CPU开销之间的平衡。

InnoDB数据存储和压缩
InnoDB表中的所有用户数据都存储在由b树索引(聚集索引)组成的页中。在其他一些数据库系统中,这种类型的索引被称为“索引组织表”。索引节点中的每一行包含(用户指定的或系统生成的)主键和表中所有其他列的值。

InnoDB表中的辅助索引也是b树,包含成对的值:索引键和指向聚集索引中的某一行的指针。指针实际上是表的主键的值,如果需要索引键和主键以外的列,则用于访问聚集索引。辅助索引记录必须始终能够放置在单个b树页上。

b树节点(集群索引和辅助索引)的压缩与用于存储长VARCHAR、BLOB或TEXT列的溢出页的压缩处理不同,以下各节将对此进行解释。

压缩b树页面
由于b树页经常更新,因此需要特殊处理。重要的是尽量减少b树节点拆分的次数,以及尽量减少解压缩和重新压缩它们的内容的需要。

MySQL使用的一种技术是在b树节点中以未压缩的形式维护一些系统信息,从而方便某些就地更新。例如,这允许在不进行任何压缩操作的情况下删除标记的行。

此外,当索引页发生变化时,MySQL试图避免不必要的解压缩和重压缩。在每个B-tree页面中,系统保存一个未压缩的“修改日志”来记录对页面所做的更改。小记录的更新和插入可以写入该修改日志,而不需要完全重建整个页面。

当修改日志的空间耗尽时,InnoDB解压缩页面,应用更改并重新压缩页面。如果重新压缩失败(这种情况称为压缩失败),则分割b树节点,并重复该过程,直到更新或插入成功。

为了避免在写密集的应用(比如OLTP应用)中频繁发生压缩失败,MySQL有时会在页面中预留一些空白空间(填充),这样修改日志就会很快填满,并且在有足够空间避免拆分页面时重新压缩页面。随着系统跟踪页面拆分的频率,每个页面中剩余的填充空间的大小也会变化。在一个繁忙的服务器上对压缩表进行频繁的写操作,你可以调整innodb_compression_failure_threshold_pct和innodb_compression_pad_pct_max配置选项来微调这种机制。

一般来说,MySQL要求InnoDB表中的每个B-tree页至少能容纳两条记录。对于压缩表,这个要求已经放宽了。b树节点的叶子页(无论是主键还是辅助索引)只需要容纳一条记录,但该记录必须以未压缩的形式容纳到每个页面的修改日志中。如果innodb_strict_mode是ON, MySQL会在CREATE TABLE或CREATE INDEX时检查最大行大小。如果行不合适,则会发出以下错误消息:error HY000: Too big row。

如果你在innodb_strict_mode关闭时创建表,并且后续的INSERT或UPDATE语句试图创建一个不适合压缩页面大小的索引项,操作将失败,ERROR 42000:Row size too large。(此错误消息没有指出哪个索引的记录太大,也没有提到该索引记录的长度或该特定索引页上的最大记录大小。)要解决这个问题,用ALTER table重建表并选择一个更大的压缩页大小(KEY_BLOCK_SIZE),缩短任何列前缀索引,或完全禁用压缩ROW_FORMAT=DYNAMIC或ROW_FORMAT=COMPACT。

Innodb_strict_mode不适用于普通表空间,普通表空间也支持压缩表。一般表空间的表空间管理规则是严格执行的,独立于innodb_strict_mode。

压缩BLOB、VARCHAR和TEXT列
在InnoDB表中,不属于主键的BLOB、VARCHAR和TEXT列可能会存储在单独分配的溢出页上。我们把这些列称为页外列。其值存储在溢出页的单链表上。

对于以ROW_FORMAT=DYNAMIC或ROW_FORMAT=COMPRESSED创建的表,BLOB、TEXT或VARCHAR列的值可以完全存储在页外,这取决于它们的长度和整行的长度。对于存储在页外的列,聚集索引记录只包含指向溢出页的20字节指针,每个列一个。是否有列存储在页外取决于页大小和行总大小。当一行太长,不能完全装入聚集索引页时,MySQL选择最长的列进行页外存储,直到该行装入聚集索引页为止。如上所述,如果压缩页中某一行不适合它自己,就会发生错误。

对于以ROW_FORMAT=DYNAMIC或ROW_FORMAT=COMPRESSED创建的表,小于或等于40字节的TEXT和BLOB列总是行内存储。

在旧版本的MySQL中创建的表使用羚羊文件格式,它只支持ROW_FORMAT= redundancy和ROW_FORMAT=COMPACT。在这些格式中,MySQL将BLOB、VARCHAR和TEXT列的前768字节与主键一起存储在聚集索引记录中。768字节的前缀后面是一个20字节的指针,指向包含该列剩余值的溢出页。

在表采用压缩格式时,写入溢出页的所有数据都“按原样”压缩;也就是说,MySQL对整个数据项应用zlib压缩算法。除了数据之外,压缩溢出页面还包含一个未压缩的头部和尾部,其中包含一个页面校验和和指向下一个溢出页面的链接。因此,如果数据是高度可压缩的,那么较长的BLOB、TEXT或VARCHAR列可以显著节省存储空间,文本数据通常就是这种情况。图像数据,如JPEG,通常已经被压缩,因此不能从存储在压缩表中获得太多好处;双重压缩可能会浪费CPU周期,但节省的空间很少或根本没有。

溢出页的大小与其他页相同。一个包含10列且存储在页外的行会占用10个溢出页,即使所有列的总长度只有8K字节。在未压缩的表中,10个未压缩的溢出页占用160K字节。在一个页面大小为8K的压缩表中,它们只占用80K字节。因此,对于with的表,使用压缩表格式通常更有效长列值。

对于file-per_table表空间,使用16K的压缩页大小可以减少BLOB、VARCHAR或TEXT列的存储和I/O成本,因为这些数据通常压缩得很好,因此可能需要更少的溢出页,即使b树节点本身占用与未压缩形式相同的页。一般的表空间不支持16K的压缩页大小(KEY_BLOCK_SIZE)。

压缩和InnoDB缓冲池
在一个压缩的InnoDB表中,每个压缩页(无论是1K、2K、4K还是8K)对应一个16K字节的未压缩页(如果设置了innodb_page_size,则更小)。为了访问页面中的数据,MySQL会从磁盘中读取压缩过的页面(如果它不在缓冲池中),然后将页面解压缩为原始形式。本节描述了InnoDB如何管理压缩表的页的缓冲池。

为了尽量减少I/O和减少解压缩页面的需要,缓冲池有时同时包含数据库页面的压缩形式和未压缩形式。为了给其他需要的数据库页面腾出空间,MySQL可以从缓冲池中移除未压缩的页面,而将压缩的页面留在内存中。或者,如果页有一段时间没有被访问,则可能将该页的压缩格式写入磁盘,以便为其他数据释放空间。因此,在任何给定的时间,缓冲池可能同时包含页的压缩形式和未压缩形式,或者只包含页的压缩形式,或者两者都不包含。

MySQL使用最近最少使用(least-recently-used, LRU)列表来跟踪哪些页应该保存在内存中,哪些页应该清除,这样热的(频繁访问的)数据就倾向于保存在内存中。当访问压缩表时,MySQL使用自适应LRU算法来实现内存中压缩页和未压缩页的适当平衡。该算法对系统是I/O密集型运行还是cpu密集型运行非常敏感。目标是避免在CPU繁忙时花费过多的处理时间来解压缩页,并避免在CPU有空闲周期可用于解压缩已压缩页(可能已经在内存中)时进行过多的I/O操作。当系统处于I/O绑定时,该算法倾向于移除一个页面的未压缩副本,而不是两个副本,以便为其他磁盘页面腾出更多空间成为内存驻留。当系统使用CPU 绑定时,MySQL倾向于同时移除压缩和未压缩的页面,这样就可以将更多的内存用于“热”页面,从而减少
只对压缩格式的数据进行解压的需求。

压缩和重做日志文件
在将压缩页面写入数据文件之前,MySQL将页面的一个副本写入重做日志(如果它从上次写入数据库以来已经被重新压缩过)。这样做是为了确保重做日志对于崩溃恢复是可用的,即使在zlib库升级的情况下,该更改也会引入与压缩数据的兼容性问题。因此,在使用压缩时,日志文件的大小可能会增加,或者需要更频繁的检查点。日志文件大小或检查点频率的增加量,取决于以需要重新组织和重新压缩的方式修改压缩页的次数。

压缩表需要Barracuda文件格式。要在一个以文件为表的表空间中创建一个压缩表,必须启用innodb_file_per_table,并且必须将innodb_file_format设置为Barracuda。在普通表空间中创建压缩表时,不依赖于innodb_file_format设置。

OLTP工作负载压缩
传统上,InnoDB压缩特性主要被推荐用于只读或以读为主的工作负载,例如在数据仓库配置中。SSD存储设备速度很快,但相对较小和昂贵,这使得压缩对于OLTP工作负载也很有吸引力:高流量、交互式的网站可以通过对频繁插入、更新和删除操作的应用程序使用压缩表来减少它们的存储需求和每秒I/O操作(IOPS)。

MySQL 5.6引入的配置选项允许你针对特定的MySQL实例调整压缩的工作方式,重点是写密集型操作的性能和可伸缩性:
.Innodb_compression_level允许你提高或降低压缩程度。值越高,存储设备上的数据就越多,但压缩时的CPU开销也越大。当存储空间不是很重要,或者你希望数据不是特别可压缩时,较小的值可以减少CPU开销。

.innodb_compression_failure_threshold_pct压缩表更新失败的截止点。当超过这个阈值时,MySQL开始在每个新的压缩页面中留下额外的空闲空间,动态调整空闲空间的数量,直到innodb_compression_pad_pct_max指定的页面大小的百分比

.innodb_compression_pad_pct_max允许您调整每个页面中保留的最大空间大小,以记录对压缩行的更改,而不需要再次压缩整个页面。该值越高,在不重新压缩页面的情况下可以记录的更改越多。MySQL为每个压缩表中的页面使用的空闲空间是可变的,只有当压缩操作在运行时“失败”时,需要执行昂贵的操作来分割压缩页面。

.Innodb_compression_pad_pct_max允许您调整每个页面中保留的最大空间大小,以记录对压缩行的更改,而不需要再次压缩整个页面。该值越高,在不重新压缩页面的情况下可以记录的更改越多。MySQL为每个压缩表中的页面使用的空闲空间是可变的,只有当压缩操作在运行时“失败”时,需要执行昂贵的操作来分割压缩页面。

.innodb_log_compressed_pages允许你禁止将重新压缩的页面的图像写入重做日志。当对压缩数据进行更改时,可能会发生重新压缩。默认启用此选项是为了防止在恢复过程中使用不同版本的zlib压缩算法时发生损坏。如果你确定zlib版本不会改变,禁用innodb_log_compressed_pages来减少修改压缩数据的工作负载的重做日志生成。

因为在处理压缩数据时,有时需要在内存中同时保存一个页面的压缩版本和未压缩版本,所以在使用oltp风格的工作负载时,要准备好增加innodb_buffer_pool_size配置选项的值。

SQL压缩语法警告和错误
在使用file-per-tabl表空间和通用表空间的表压缩特性时可能遇到的语法警告和错误。

针对每个表文件的表空间的SQL压缩语法警告和错误
当innodb_strict_mode被启用时(默认),在CREATE TABLE或ALTER TABLE语句中指定ROW_FORMAT=COMPRESSED或KEY_BLOCK_SIZE,如果innodb_file_per_table被禁用或者innodb_file_format被设置为Antelope而不是Barracuda会产生如下错误。

ERROR 1031 (HY000): Table storage engine for 't1' doesn't have this option

如果当前配置不允许使用压缩表,则不会创建该表。

当innodb_strict_mode被禁用时,在CREATE TABLE或ALTER TABLE语句中指定ROW_FORMAT=COMPRESSED或KEY_BLOCK_SIZE,如果innodb_file_per_table被禁用会产生如下警告。

mysql> SHOW WARNINGS;
+---------+------+---------------------------------------------------------------+
| Level | Code | Message |
+---------+------+---------------------------------------------------------------+
| Warning | 1478 | InnoDB: KEY_BLOCK_SIZE requires innodb_file_per_table. |
| Warning | 1478 | InnoDB: ignoring KEY_BLOCK_SIZE=4. |
| Warning | 1478 | InnoDB: ROW_FORMAT=COMPRESSED requires innodb_file_per_table. |
| Warning | 1478 | InnoDB: assuming ROW_FORMAT=DYNAMIC. |
+---------+------+---------------------------------------------------------------+

如果innodb_file_format设置为Antelope而不是Barracuda,则会发出类似的警告。

这些消息只是警告,而不是错误,而且创建表时没有压缩,就像没有指定选项一样。

“非严格”行为允许您将mysqldump文件导入到不支持压缩表的数据库中,即使源数据库包含压缩表。在这种情况下,MySQL会在ROW_FORMAT=COMPACT中创建表,而不是阻止操作。

要将转储文件导入到一个新的数据库中,并重新创建原始数据库中的表,请确保服务器对innodb_file_format和innodb_file_per_table配置参数有适当的设置。

只有在ROW_FORMAT指定为COMPRESSED或省略时,属性KEY_BLOCK_SIZE才允许使用。使用任何其他ROW_FORMAT指定KEY_BLOCK_SIZE都会产生一个警告,用户可以通过SHOW WARNINGS查看。但是,该表没有被压缩。忽略指定的KEY_BLOCK_SIZE)。

Level   Code Message
Warning 1478 InnoDB: ignoring KEY_BLOCK_SIZE=n unless ROW_FORMAT=COMPRESSED.

如果运行时启用了innodb_strict_mode, KEY_BLOCK_SIZE和任何ROW_FORMAT的组合都会产生一个错误,而不是一个警告,并且表不会被创建。

当innodb_strict_mode关闭时,MySQL创建或修改表,但忽略某些设置,如下所示。你可以在MySQL错误日志中看到警告消息。当innodb_strict_mode开启时,这些指定的选项组合将产生错误,表不会被创建或修改。要查看错误条件的完整描述,请执行SHOW ERRORS语句:示例:

mysql> show variables like 'innodb_strict_mode';
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| innodb_strict_mode | ON    |
+--------------------+-------+
1 row in set (0.02 sec)

mysql> CREATE TABLE x (id INT PRIMARY KEY, c INT)  ENGINE=INNODB KEY_BLOCK_SIZE=33333;
ERROR 1005 (HY000): Can't create table 'test.x' (errno: 1478)

mysql> SHOW ERRORS;
+-------+------+-------------------------------------------+
| Level | Code | Message |
+-------+------+-------------------------------------------+
| Error | 1478 | InnoDB: invalid KEY_BLOCK_SIZE=33333. |
| Error | 1005 | Can't create table 'test.x' (errno: 1478) |
+-------+------+-------------------------------------------+

当innodb_strict_mode开启时,MySQL会拒绝无效的ROW_FORMAT或KEY_BLOCK_SIZE参数并抛出错误。当innodb_strict_mode关闭时,MySQL会对忽略的无效参数发出警告而不是错误。innodb_strict_mode默认开启。

当innodb_strict_mode开启时,MySQL拒绝无效的ROW_FORMAT或KEY_BLOCK_SIZE参数。为了兼容MySQL的早期版本,默认不会启用严格模式。相反,MySQL会对被忽略的无效参数发出警告(而不是错误)。

使用SHOW TABLE STATUS是不可能看到选定的KEY_BLOCK_SIZE的。语句SHOW CREATE TABLE显示KEY_BLOCK_SIZE(即使在创建表时忽略了它)。MySQL无法显示表的实际压缩页大小。

通用表空间的SQL压缩语法警告和错误
.如果在创建表空间时没有为普通表空间定义FILE_BLOCK_SIZE,则该表空间不能包含压缩表。如果试图添加压缩表,则返回错误,如下面的例子所示:

mysql> CREATE TABLESPACE ts4 ADD DATAFILE 'ts4.ibd' Engine=InnoDB;
Query OK, 0 rows affected (0.00 sec)


mysql> CREATE TABLE t4 (c1 INT PRIMARY KEY) TABLESPACE ts4 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
ERROR 1478 (HY000): InnoDB: Tablespace `ts4` cannot contain a COMPRESSED table

.试图将无效KEY_BLOCK_SIZE设置的表添加到普通表空间会返回错误,如下面的例子所示:

mysql> CREATE TABLESPACE `ts5` ADD DATAFILE 'ts5.ibd' FILE_BLOCK_SIZE = 8192 Engine=InnoDB;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE t5 (c1 INT PRIMARY KEY) TABLESPACE ts5 ROW_FORMAT=COMPRESSED  KEY_BLOCK_SIZE=4;
ERROR 1478 (HY000): InnoDB: Tablespace `ts5` uses block size 8192 and cannot contain a table with physical page size 4096

对于普通表空间,表的KEY_BLOCK_SIZE必须等于表空间的FILE_BLOCK_SIZE除以1024。例如,表空间的FILE_BLOCK_SIZE为8192,则表的KEY_BLOCK_SIZE必须为8。

.尝试将一个未压缩行格式的表添加到配置为存储压缩表的一般表空间会返回一个错误,如下面的例子所示:

mysql> CREATE TABLESPACE `ts6` ADD DATAFILE 'ts6.ibd' FILE_BLOCK_SIZE = 8192 Engine=InnoDB;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE t6 (c1 INT PRIMARY KEY) TABLESPACE ts6 ROW_FORMAT=COMPACT;
ERROR 1478 (HY000): InnoDB: Tablespace `ts6` uses block size 8192 and cannot contain a table with physical page size 16384

innodb_strict_mode不适用于一般表空间。一般表空间的表空间管理规则是严格执行的,独立于innodb_strict_mode。

MySQL InnoDB全文索引

InnoDB全文索引
FULLTEXT索引是在基于文本的列(CHAR、VARCHAR或TEXT列)上创建的,以帮助加快对这些列中包含的数据的查询和DML操作,从而省略定义为停止词的任何单词。FULLTEXT索引定义为CREATE TABLE语句的一部分,或者使用ALTER TABLE或CREATE index将其添加到现有表中。全文搜索使用MATCH()…对语法。

InnoDB全文索引设计
InnoDB FULLTEXT索引采用倒排索引设计。倒排索引存储一个单词列表,对于每个单词,存储该单词出现在其中的文档列表。为了支持邻近搜索,每个字的位置信息也以字节偏移量的形式存储。

InnoDB全文索引表
当创建一个InnoDB FULLTEXT索引时,会创建一组索引表,示例如下:

mysql> CREATE TABLE opening_lines (
    -> id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
    -> opening_line TEXT(500),
    -> author VARCHAR(200),
    -> title VARCHAR(200),
    -> FULLTEXT idx (opening_line)
    -> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.05 sec)

mysql> SELECT table_id, name, space from INFORMATION_SCHEMA.INNODB_SYS_TABLES WHERE name LIKE 'undo/%';
+----------+----------------------------------------------------+-------+
| table_id | name                                               | space |
+----------+----------------------------------------------------+-------+
|      453 | undo/FTS_00000000000001bf_00000000000002bb_INDEX_1 |   452 |
|      454 | undo/FTS_00000000000001bf_00000000000002bb_INDEX_2 |   453 |
|      455 | undo/FTS_00000000000001bf_00000000000002bb_INDEX_3 |   454 |
|      456 | undo/FTS_00000000000001bf_00000000000002bb_INDEX_4 |   455 |
|      457 | undo/FTS_00000000000001bf_00000000000002bb_INDEX_5 |   456 |
|      458 | undo/FTS_00000000000001bf_00000000000002bb_INDEX_6 |   457 |
|      448 | undo/FTS_00000000000001bf_BEING_DELETED            |   447 |
|      449 | undo/FTS_00000000000001bf_BEING_DELETED_CACHE      |   448 |
|      450 | undo/FTS_00000000000001bf_CONFIG                   |   449 |
|      451 | undo/FTS_00000000000001bf_DELETED                  |   450 |
|      452 | undo/FTS_00000000000001bf_DELETED_CACHE            |   451 |
|      447 | undo/opening_lines                                 |   446 |
+----------+----------------------------------------------------+-------+
12 rows in set (0.00 sec)

前六个表表示倒排索引,称为辅助索引表。在对传入文档进行标记时,将单个单词(也称为“标记”)与位置信息和相关文档ID (DOC_ID)一起插入到索引表中。根据单词第一个字符的字符集排序权重,将单词完全排序并在六个索引表中进行分区。

倒排索引被划分为六个辅助索引表,以支持并行索引创建。默认情况下,两个线程对单词和相关数据进行标记、排序和插入索引表。线程的数量可以使用innodb_ft_sort_pll_degree选项进行配置。考虑在大型表上创建FULLTEXT索引时增加线程数。

辅助索引表名的前缀是FTS_,后缀是INDEX_*。每个索引表通过索引表名称中的十六进制值与被索引表相关联,该值与被索引表的table_id相匹配。例如,test/opening_lines表的table_id为447,其十六进制值为0x1bf。如前面的示例所示,“1bf”十六进制值出现在与test/opening_lines表相关联的索引表的名称中。

表示FULLTEXT索引的index_id的十六进制值也出现在辅助索引表名中。例如,在辅助表名undo/FTS_00000000000001bf_00000000000002bb_INDEX_1中,十六进制值2bb的十进制值为699。opening_lines表(idx)上定义的索引可以通过查询INFORMATION_SCHEMA来识别。INNODB_SYS_INDEXES表的值(699)。

mysql> SELECT index_id, name, table_id, space from INFORMATION_SCHEMA.INNODB_SYS_INDEXES WHERE index_id=699;
+----------+------+----------+-------+
| index_id | name | table_id | space |
+----------+------+----------+-------+
|      699 | idx  |      447 |   446 |
+----------+------+----------+-------+
1 row in set (0.00 sec)

如果主表是在每个表文件的表空间中创建的,则索引表存储在它们自己的表空间中。

前面示例中显示的其他索引表被称为公共索引表,用于删除处理和存储FULLTEXT索引的内部状态。与为每个全文索引创建的倒排索引表不同,这组表对于在特定表上创建的所有全文索引都是通用的。

即使删除全文索引,也会保留常见的辅助表。当全文索引被删除时,为索引创建的FTS_DOC_ID列将被保留,因为删除FTS_DOC_ID列将需要重建表。管理FTS_DOC_ID列需要使用普通的辅助表。
.FTS_*_DELETED and FTS_*_DELETED_CACHE

包含已删除但其数据尚未从全文索引中删除的文档的文档id (DOC_ID)。FTS_*_DELETED_CACHE是FTS_*_DELETED表的内存版本。

.FTS_*_BEING_DELETED and FTS_*_BEING_DELETED_CACHE
包含要删除的文档的文档id (DOC_ID),这些文档的数据目前正在从全文索引中删除。FTS_*_BEING_DELETED_CACHE表是FTS_*_BEING_DELETED表的内存版本。

.FTS_*_CONFIG
存储关于FULLTEXT索引的内部状态的信息。最重要的是,它存储FTS_SYNCED_DOC_ID,它标识已解析并刷新到磁盘的文档。在崩溃恢复的情况下,FTS_SYNCED_DOC_ID值用于标识尚未刷新到磁盘的文档,以便可以重新解析文档并将其添加回FULLTEXT索引缓存。要查看该表中的数据,请查询INFORMATION_SCHEMA.INNODB_FT_CONFIG表。

InnoDB全文索引缓存
插入文档时,将对其进行标记,并将单个单词和相关数据插入到FULLTEXT索引中。这个过程,即使对于小文档,也可能导致对辅助索引表进行大量小的插入,从而使对这些表的并发访问成为争用点。为了避免这个问题,InnoDB使用FULLTEXT索引缓存来临时缓存索引表中最近插入的行。这个内存缓存结构保存插入,直到缓存满,然后将它们批量刷新到磁盘(到辅助索引表)。可以查询INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE表用于查看最近插入的行的标记化数据。

缓存和批处理刷新行为避免了对辅助索引表的频繁更新,这可能在繁忙的插入和更新期间导致并发访问问题。批处理技术还避免了对同一个单词的多次插入,并最大限度地减少了重复条目。不是逐个刷新每个单词,而是将相同单词的插入合并并作为单个条目刷新到磁盘,从而提高了插入效率,同时使辅助索引表尽可能小。

innodb_ft_cache_size变量用于配置全文索引缓存大小(以每个表为基础),它影响全文索引缓存刷新的频率。您还可以使用innodb_ft_total_cache_size选项为给定实例中的所有表定义全局全文索引缓存大小限制。

全文索引缓存存储与辅助索引表相同的信息。但是,全文索引缓存仅为最近插入的行缓存标记化的数据。查询时,已经刷新到磁盘(全文辅助表)的数据不会被带回到全文索引缓存中。直接查询辅助索引表中的数据,并且在返回之前,将辅助索引表中的结果与全文索引缓存中的结果合并。

InnoDB全文索引文档ID和FTS_DOC_ID列
InnoDB使用一个唯一的文档标识符,即文档ID (DOC_ID),将全文索引中的单词映射到该单词出现的文档记录。映射需要索引表上的FTS_DOC_ID列。如果没有定义FTS_DOC_ID列,InnoDB会在创建全文索引时自动添加一个隐藏的FTS_DOC_ID列。下面的示例演示了这种行为。

下面的表定义不包含FTS_DOC_ID列:

mysql> CREATE TABLE opening_lines (
    -> id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
    -> opening_line TEXT(500),
    -> author VARCHAR(200),
    -> title VARCHAR(200)
    -> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.01 sec)

当使用create FULLTEXT index语法在表上创建全文索引时,会返回一个警告,报告InnoDB正在重建表以添加FTS_DOC_ID列。

mysql> CREATE FULLTEXT INDEX idx ON opening_lines(opening_line);
Query OK, 0 rows affected, 1 warning (0.10 sec)
Records: 0  Duplicates: 0  Warnings: 1

mysql> SHOW WARNINGS;
+---------+------+--------------------------------------------------+
| Level   | Code | Message                                          |
+---------+------+--------------------------------------------------+
| Warning |  124 | InnoDB rebuilding table to add column FTS_DOC_ID |
+---------+------+--------------------------------------------------+
1 row in set (0.00 sec)

当使用ALTER TABLE向没有FTS_DOC_ID列的表添加全文索引时,也会返回相同的警告。如果你在create TABLE时间创建一个全文索引,并且没有指定FTS_DOC_ID列,InnoDB会添加一个隐藏的FTS_DOC_ID列,没有警告。

在CREATE TABLE时定义一个FTS_DOC_ID列比在一个已经加载了数据的表上创建一个全文索引要便宜。如果在加载数据之前在表上定义了FTS_DOC_ID列,则不必重新构建表及其索引来添加新列。如果你不关心CREATE FULLTEXT INDEX的性能,可以省略FTS_DOC_ID列,让InnoDB为你创建它。InnoDB创建一个隐藏的FTS_DOC_ID列,并在FTS_DOC_ID列上创建一个唯一的索引(FTS_DOC_ID_INDEX)。如果你想创建自己的FTS_DOC_ID列,该列必须定义为BIGINT UNSIGNED NOT NULL,并命名为FTS_DOC_ID(全大写),如下所示:

FTS_DOC_ID列不需要定义为AUTO_INCREMENT列,但是AUTO_INCREMENT可以使加载数据更容易。

mysql> CREATE TABLE opening_lines (
    -> FTS_DOC_ID BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
    -> opening_line TEXT(500),
    -> author VARCHAR(200),
    -> title VARCHAR(200)
    -> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.01 sec)

如果选择自己定义FTS_DOC_ID列,则负责管理该列,以避免空值或重复值。FTS_DOC_ID的值不能重复使用,这意味着FTS_DOC_ID的值必须不断增加。

可选地,您可以在FTS_DOC_ID列上创建所需的惟一FTS_DOC_ID_INDEX(全部大写)。

mysql> CREATE UNIQUE INDEX FTS_DOC_ID_INDEX on opening_lines(FTS_DOC_ID);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

如果你没有创建FTS_DOC_ID_INDEX, InnoDB会自动创建它。

在MySQL 5.7.13之前,已使用的FTS_DOC_ID值与新的FTS_DOC_ID值之间允许的最大差值为10000。在MySQL 5.7.13及以后版本中,允许的间隙是65535。

为了避免重新构建表,在删除全文索引时保留FTS_DOC_ID列。

InnoDB全文索引删除处理
删除具有全文索引列的记录可能会导致辅助索引表中出现大量小的删除,从而使对这些表的并发访问成为争用点。为了避免这个问题,被删除文档的文档ID (DOC_ID)记录在一个特殊的FTS_*_DELETED表中,当一条记录从索引表中删除时,被索引的记录保留在全文索引中。在返回查询结果之前,使用FTS_*_DELETED表中的信息过滤已删除的Document id。这种设计的好处是,删除是快速和廉价的。缺点是在删除记录后索引的大小不会立即减小。要删除已删除记录的全文索引项,可以在已索引的表上运行OPTIMIZE TABLE命令(innodb_optimize_fulltext_only= on)重建全文索引。

InnoDB全文索引事务处理
InnoDB FULLTEXT索引由于其缓存和批处理行为而具有特殊的事务处理特性。具体来说,FULLTEXT索引上的更新和插入是在事务提交时处理的,这意味着FULLTEXT搜索只能看到提交的数据。下面的示例演示了这种行为。FULLTEXT搜索只在提交插入的行之后返回结果。

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO opening_lines(opening_line,author,title) VALUES
    -> ('Call me Ishmael.','Herman Melville','Moby-Dick'),
    -> ('A screaming comes across the sky.','Thomas Pynchon','Gravity\'s Rainbow'),
    -> ('I am an invisible man.','Ralph Ellison','Invisible Man'),
    -> ('Where now? Who now? When now?','Samuel Beckett','The Unnamable'),
    -> ('It was love at first sight.','Joseph Heller','Catch-22'),
    -> ('All this happened, more or less.','Kurt Vonnegut','Slaughterhouse-Five'),
    -> ('Mrs. Dalloway said she would buy the flowers herself.','Virginia Woolf','Mrs. Dalloway'),
    -> ('It was a pleasure to burn.','Ray Bradbury','Fahrenheit 451');
Query OK, 8 rows affected (0.00 sec)
Records: 8  Duplicates: 0  Warnings: 0

mysql> SELECT COUNT(*) FROM opening_lines WHERE MATCH(opening_line) AGAINST('Ishmael');
+----------+
| COUNT(*) |
+----------+
|        0 |
+----------+
1 row in set (0.01 sec)

mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT COUNT(*) FROM opening_lines WHERE MATCH(opening_line) AGAINST('Ishmael');
+----------+
| COUNT(*) |
+----------+
|        1 |
+----------+
1 row in set (0.00 sec)

MySQL InnoDB的AUTO_INCREMENT处理

InnoDB的AUTO_INCREMENT处理
InnoDB提供了一种可配置的锁机制,可以显著提高将行添加到具有AUTO_INCREMENT列的表中的SQL语句的可伸缩性和性能。要在InnoDB表中使用AUTO_INCREMENT机制,必须将一个AUTO_INCREMENT列定义为索引的一部分,这样就可以在表上执行相当于索引SELECT MAX(ai_col)的查找以获得最大列值。通常,这是通过使某一列成为某些表索引的第一列来实现的。

本节描述AUTO_INCREMENT锁模式的行为,不同AUTO_INCREMENT锁模式设置的使用影响,以及InnoDB如何初始化AUTO_INCREMENT计数器。
.InnoDB AUTO_INCREMENT锁模式
.InnoDB AUTO_INCREMENT锁模式使用影响
.InnoDB AUTO_INCREMENT计数器初始化

InnoDB AUTO_INCREMENT锁模式
用于生成自动增量值的AUTO_INCREMENT锁模式的行为,以及每种锁模式如何影响复制。自动增量锁模式在启动时使用innodb_autoinc_lock_mode配置参数配置。

以下术语用于描述innodb_autoinc_lock_mode设置:
.“INSERT-like”语句

所有在表中生成新行的语句,包括INSERT, INSERT… SELECT,REPLACE,REPLACE… SELECT和LOAD DATA。包括“简单插入”、“批量插入”和“混合模式”插入。

.“简单插入”
可以提前确定要插入的行数的语句(在初始处理语句时)。这包括没有嵌套子查询的单行和多行INSERT和REPLACE语句,但不包括INSERT … ON DUPLICATE KEY UPDATE。

.“混合模式插入”
这些是“简单插入”语句,它们为一些(但不是全部)新行指定自动增量值。下面是一个例子,其中c1是表t1的AUTO_INCREMENT列:

INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');

另一种“混合模式插入”是INSERT…ON DUPLICATE KEY UPDATE,在最坏的情况下,它实际上是INSERT,然后是UPDATE,其中AUTO_INCREMENT列的分配值可能在更新阶段使用,也可能不使用。

innodb_autoinc_lock_mode配置参数有三种可能的设置。“传统”、“连续”、“交错”锁模式的取值分别为0、1、2。
.innodb_autoinc_lock_mode = 0(“传统”锁模式)

传统的锁模式提供了与MySQL 5.1中引入innodb_autoinc_lock_mode配置参数之前相同的行为。传统的锁模式选项是为了向后兼容、性能测试和解决“混合模式插入”的问题而提供的,因为可能存在语义上的差异。

在这种锁模式下,所有“INSERT-like”语句都获得一个特殊的表级AUTO-INC锁,用于向具有AUTO_INCREMENT列的表中插入数据。该锁通常保持到语句的末尾(而不是事务的末尾),以确保对给定的INSERT语句序列按可预测和可重复的顺序分配自动递增值,并确保任何给定语句分配的自动递增值是连续的。

在基于语句的复制的情况下,这意味着当从服务器上复制SQL语句时,自动递增列的值与主服务器上相同。多个INSERT语句的执行结果是确定的,从服务器复制的数据与主服务器相同。如果多个INSERT语句生成的自动递增值是交错的,则两个并发INSERT语句的结果将是不确定的,并且不能使用基于语句的复制可靠地将其传播到从服务器。

为了更清楚地说明这一点,考虑一个使用这个表的例子:

CREATE TABLE t1 (
  c1 INT(11) NOT NULL AUTO_INCREMENT,
  c2 VARCHAR(10) DEFAULT NULL,
  PRIMARY KEY (c1)
) ENGINE=InnoDB;

假设有两个事务正在运行,每个事务将行插入到表中AUTO_INCREMENT列。一个事务正在使用INSERT…SELECT语句,该语句插入1000行,另一个是使用一个简单的INSERT语句插入一行:

Tx1: INSERT INTO t1 (c2) SELECT 1000 rows from another table ...
Tx2: INSERT INTO t1 (c2) VALUES ('xxx');

InnoDB无法预先告诉Tx1中的INSERT语句从SELECT中检索了多少行,并且它会在语句执行过程中每次分配一个自动递增值。使用一直保持到语句末尾的表级锁,一次只能执行一条引用table t1的INSERT语句,并且不同语句生成的自动递增编号不会交叉。由Tx1 INSERT…SELECT语句生成的自动递增值是连续的,而Tx2中INSERT语句使用的(单个)自动递增值比Tx1中使用的所有自动递增值是小或还是大,这取决于首先执行哪条语句。

只要SQL语句以二进制日志中相同的顺序执行(在使用基于语句的复制时,或在恢复场景中),结果就与Tx1和Tx2第一次运行时相同。因此,表级别的锁一直保持到语句结束,使得使用自动递增的INSERT语句可以安全地与基于语句的复制一起使用。然而,当多个事务同时执行insert语句时,这些表级锁限制了并发性和可伸缩性。

在前面的示例中,如果没有表级锁,那么Tx2中用于插入的自动递增列的值完全取决于语句执行的时间。如果Tx2的插入是在Tx1的插入运行时执行的(而不是在它开始之前或完成之后),那么两个INSERT语句分配的特定的自动递增值是不确定的,并且可能在不同的运行中有所不同。

在连续锁模式下,对于预先知道行数的“simple insert”语句InnoDB可以避免使用表级的AUTO-INC锁来处理,并保持执行的确定性和基于语句的复制的安全性。

如果你不使用二进制日志来重放SQL语句作为恢复或复制的一部分,那么交错锁模式可以用来消除所有表级的AUTO-INC锁,以获得更大的并发性和性能,但代价是允许语句分配的自动递增编号之间有间隙,并且可能通过交错并发执行语句分配编号。

.innodb_autoinc_lock_mode = 1 (“连续”锁模式)

这是默认的锁定模式。在这种模式下,“批量插入”使用特殊的AUTO-INC表级锁,并一直保持到语句结束。这适用于所有的INSERT…SELECT,REPLACE … SELECT和LOAD DATA语句。一次只能执行一个持有AUTO-INC锁的语句。如果批量插入操作的源表与目标表不同,则在源表中选择的第一行获得共享锁之后,在目标表上获得AUTO-INC锁。如果批量插入操作的源和目标是同一个表,那么在对所有选定行使用共享锁之后,会使用AUTO-INC锁。

“简单插入”(需要插入的行数是预先知道的)通过在互斥量(轻量级锁)的控制下获取所需的自动递增值的数量来避免表级的AUTO-INC锁,该互斥量只在分配过程中保持,直到语句完成。除非AUTO-INC锁被另一个事务持有,否则不会使用表级的AUTO-INC锁。如果另一个事务持有AUTO-INC锁,则“简单插入”会等待AUTO-INC锁,就像“批量插入”一样。

这种锁模式确保,如果INSERT语句的行数事先不知道(并且随着语句的执行分配了自动递增号),任何“类INSERT”语句分配的所有自动递增值都是连续的,并且基于语句的复制操作是安全的。

简而言之,这种锁模式显著提高了可伸缩性,同时可以安全地与基于语句的复制一起使用。此外,与“传统”锁模式一样,任何给定语句分配的自动递增数字都是连续的。与“传统”模式相比,任何使用自动递增的语句在语义上都没有变化,但有一个重要的例外。

例外情况是“混合模式插入”,在这种情况下,用户在多行“简单插入”中为某些(但不是全部)行显式地提供AUTO_INCREMENT列的值。对于这样的插入,InnoDB分配的自动增量值比要插入的行数要多。然而,所有自动赋值的值都是连续生成的(因而高于)最近执行的前一个语句自动递增生成的值。“多余”的数字会丢失。

.innodb_autoinc_lock_mode = 2(“交错”锁模式)

在这种锁模式下,没有“INSERT-like”语句使用表级AUTO-INC锁,并且多个语句可以同时执行。这是最快和最具可伸缩性的锁模式,但是当重放来自二进制日志中SQL语句执行基于语句的复制或恢复场景时,它并不安全。

在这种锁模式下,自动增量值保证在所有并发执行的“INSERT-like”语句中是唯一且单调递增的。但是,因为多个语句可以同时生成数字(也就是说,数字的分配是跨语句交叉的),任何给定语句插入的行生成的值都可能不是连续的。

如果唯一执行的语句是“简单插入”,其中要插入的行数是提前知道的,那么除了“混合模式插入”之外,为单个语句生成的行数没有间隔。然而,当执行“批量插入”时,任何给定语句分配的自动增量值可能存在间隙。

InnoDB AUTO_INCREMENT锁模式使用影响
.对复制使用自动增量

如果使用基于语句的复制,请将innodb_autoinc_lock_mode设置为0或1,并在主服务器和从服务器上使用相同的值。如果使用innodb_autoinc_lock_mode = 2 (” interleaved “),或者配置主、从不使用相同的锁模式,则不能确保从上的自动增量值与主上的相同。

如果您使用的是基于行或混合格式的复制,那么所有的自动增量锁模式都是安全的,因为基于行的复制对SQL语句的执行顺序不敏感(对于基于语句的复制来说不安全的任何语句混合格式使用基于行的复制来进行处理)。

.“丢失”自动增量值和序列间隙

在所有锁模式(0、1和2)中,如果生成自动递增值的事务回滚,则这些自动递增值将“丢失”。一旦为自动递增的列生成了值,无论“INSERT-like”语句是否完成,以及包含它的事务是否回滚,它都不能回滚。这些丢失的值不会被重用。因此,存储在表的AUTO_INCREMENT列中的值可能会有差距。

.为AUTO_INCREMENT列指定NULL或0
在所有的锁模式下(0、1和2),如果用户为INSERT语句中的AUTO_INCREMENT列指定了NULL或0,InnoDB将该行视为未指定值,并为其生成一个新值。

.给AUTO_INCREMENT列赋一个负值
在所有锁模式(0、1和2)中,如果给AUTO_INCREMENT列赋一个负值,则不会定义自动递增机制的行为。

.如果AUTO_INCREMENT值大于指定整数类型的最大整数
在所有锁模式(0、1和2)中,如果值大于可以存储在指定整数类型中的最大整数,则不定义自动递增机制的行为。

.“bulk inserts”的自动增量值存在间隔
当innodb_autoinc_lock_mode设置为0(“传统”)或1(“连续”)时,任何给定语句生成的自动增量值都是连续的,没有间隔,因为表级AUTOINC锁一直保持到语句结束,并且一次只能执行一个这样的语句。

当innodb_autoinc_lock_mode设置为2(“交错”)时,“bulk inserts”生成的自动增量值可能会有间隙,但仅当有并发执行“类似插入”语句时才会如此。

对于锁模式1或2,因为对于批量插入,可能不知道每个语句所需的自动增量值的确切数量,并且可能会高估因此连续语句之间可能会出现间隙。

.“混合模式插入”分配的自动增量值

考虑一个“混合模式插入”,其中一个“简单插入”指定了一些结果行(但不是全部)的自动增量值。这样的语句在锁模式0、1和2中的行为不同。例如,假设c1是表t1的AUTO_INCREMENT列,并且最近自动生成的序列号是100。

mysql> CREATE TABLE t1
    -> (
    ->   c1 INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    ->   c2 CHAR(1)
    -> ) ENGINE = INNODB;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into t1 values(100,'x');
Query OK, 1 row affected (0.00 sec)

mysql> select * from t1;
+-----+------+
| c1  | c2   |
+-----+------+
| 100 | x    |
+-----+------+
1 row in set (0.00 sec)

现在,考虑下面的“混合模式插入”语句:

mysql> INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
+-----+------+
| c1  | c2   |
+-----+------+
|   1 | a    |
| 101 | b    |
|   5 | c    |
| 102 | d    |
| 100 | x    |
+-----+------+
5 rows in set (0.00 sec)

下一个可用的自动递增值是103,因为自动递增值是每次分配一个,而不是在语句执行开始时一次性分配的。无论是否有并发执行的“INSERT-like”语句(任何类型),该结果都为true。

当innodb_autoinc_lock_mode设置为1(“连续”)时,这四个新行也是:

mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
+-----+------+
| c1  | c2   |
+-----+------+
|   1 | a    |
| 101 | b    |
|   5 | c    |
| 102 | d    |
| 100 | x    |
+-----+------+
5 rows in set (0.01 sec)

然而,在本例中,下一个可用的自动增量值是105,而不是103,因为在处理语句时分配了四个自动增量值,但只使用了两个。无论是否并发执行“INSERT-like”语句(任何类型),这个结果都为真。

mysql> show create table t1;
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                      |
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| t1    | CREATE TABLE `t1` (
  `c1` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `c2` char(1) DEFAULT NULL,
  PRIMARY KEY (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=105 DEFAULT CHARSET=utf8mb4 |
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

innodb_autoinc_lock_mode设置为模式2(“交错”),这四个新行是:

mysql> show variables like 'innodb_autoinc_lock_mode';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_autoinc_lock_mode | 2     |
+--------------------------+-------+
1 row in set (0.00 sec)

mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
+-----+------+
| c1  | c2   |
+-----+------+
|   1 | a    |
|   x | b    |
|   5 | c    |
|   y | d    |
| 100 | x    |
+-----+------+
5 rows in set (0.00 sec)

x和y的值是唯一的,且大于之前生成的任何行。但是,x和y的具体值取决于并发执行语句生成的自动增量值的数量。

最后,考虑以下语句,当最近生成的序列号为4时发出:

mysql> truncate table t1;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into t1 values(4,'x');
Query OK, 1 row affected (0.00 sec)

mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
+----+------+
| c1 | c2   |
+----+------+
|  4 | x    |
+----+------+
1 row in set (0.00 sec)

mysql> show create table t1;
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                    |
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| t1    | CREATE TABLE `t1` (
  `c1` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `c2` char(1) DEFAULT NULL,
  PRIMARY KEY (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 |
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
ERROR 1062 (23000): Duplicate entry '5' for key 'PRIMARY'

对于任何innodb_autoinc_lock_mode设置,该语句都会生成一个ERROR 1062 (23000): Duplicate entry ‘5’ for key ‘PRIMARY’,因为5被分配给行(NULL, ‘b’),并且行(5,’c’)的插入失败。

.修改INSERT语句序列中间的AUTO_INCREMENT列值

在所有锁模式(0、1和2)中,在INSERT语句序列的中间修改AUTO_INCREMENT列值可能会导致“重复条目”错误。例如,如果您执行更新操作,将AUTO_INCREMENT列的值更改为大于当前最大自动递增值的值,则后续的插入操作如果没有指定未使用的自动递增值,可能会遇到“重复条目”错误。下面的例子演示了这种行为。

mysql> drop table t1;
Query OK, 0 rows affected (0.02 sec)

mysql> create table t1
    -> (c1 int not null auto_increment,
    -> primary key(c1)
    -> ) engine=innodb;
Query OK, 0 rows affected (0.01 sec)

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

mysql> SELECT c1 FROM t1;
+----+
| c1 |
+----+
|  1 |
|  2 |
|  3 |
+----+
3 rows in set (0.00 sec)

mysql> UPDATE t1 SET c1 = 4 WHERE c1 = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT c1 FROM t1;
+----+
| c1 |
+----+
|  2 |
|  3 |
|  4 |
+----+
3 rows in set (0.00 sec)

mysql> INSERT INTO t1 VALUES(0);
ERROR 1062 (23000): Duplicate entry '4' for key 'PRIMARY'

< ?pre>
InnoDB AUTO_INCREMENT计数器初始化

如果你为InnoDB表指定了一个AUTO_INCREMENT列,那么InnoDB数据字典中的表句柄中就会包含一个特殊的计数器,叫做自动递增计数器(auto-increment counter),用于为这一列赋值。这个计数器只存储在主内存中,而不是磁盘中。

为了在服务器重启后初始化一个自动递增计数器,InnoDB在第一次向包含AUTO_INCREMENT列的表中插入数据时,执行等价于下面的语句。
SELECT MAX(ai_col) FROM table_name FOR UPDATE;

InnoDB递增语句检索到的值,并将其分配给列和表的autoincrement计数器。缺省情况下,加1。这个默认值可以通过auto_increment_increment配置设置覆盖。

mysql> show variables like 'auto_increment_increment';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| auto_increment_increment | 1     |
+--------------------------+-------+
1 row in set (0.02 sec)

如果表为空,InnoDB使用值1。这个默认值可以被auto_increment_offset配置设置覆盖。

mysql> show variables like 'auto_increment_offset';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| auto_increment_offset | 1     |
+-----------------------+-------+
1 row in set (0.00 sec)

如果SHOW TABLE STATUS语句在自动递增计数器初始化之前检查了表,InnoDB会初始化该值,但不会递增。该值被存储起来,供后续插入使用。此初始化使用表上的普通排它锁定读取,锁持续到事务结束。InnoDB为新创建的表初始化自增计数器的过程与此相同。

在自动递增计数器被初始化之后,如果你没有显式地为AUTO_INCREMENT列指定一个值,InnoDB会递增计数器并将新值赋值给列。如果插入显式指定列值的行,且该值大于当前计数器值,则计数器设置为指定的列值。

只要服务器在运行,InnoDB就会使用内存中的自动递增计数器。当服务器停止并重新启动时,InnoDB会在第一次插入表时重新初始化每个表的计数器,如前所述。

服务器重启也会取消CREATE table和ALTER table语句中的AUTO_INCREMENT = N 表选项的影响,你可以在InnoDB表中使用它来设置初始的计数器值或改变当前的计数器值。

MySQL参数名不正确导致启动时报The server quit without updating PID file pre

在MySQL参数文件my.cnf中配置参数innodb_autoinc_lock_mode在,配置后重启服务时出现以下错误:

[root@localhost mysql]# service mysqld restart
Shutting down MySQL.... SUCCESS!
Starting MySQL..... ERROR! The server quit without updating PID file (/mysqldata/mysql/mysqld.pid).

由于查看参数文件my.cnf,发现参数变成了nnodb_autoinc_lock_mode=2,这是由于复制粘贴造成少了一个i字母

[mysql@localhost mysql]$ cat my.cnf
......
nnodb_autoinc_lock_mode=2
......

修改正确

[mysql@localhost mysql]$ vi my.cnf
......
innodb_autoinc_lock_mode=2
......

再重启服务

[root@localhost mysql]# service mysqld restart
 ERROR! MySQL server PID file could not be found!
Starting MySQL... SUCCESS!

Oracle Linux 6.7 安装MySQL 8.0.33

在Oracle Linux 6.7中安装MySQL 8.0.33 mysql安装位置:/mysqlsoft/mysql,数据库文件数据位置:/mysqldata/mysql
1.首先下载安装介质

mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz

2. 在根目录下创建文件夹mysqlsoft和数据库数据文件/mysqldata/mysql

[root@sjbf /]# mkdir -p /mysqlsoft
[root@sjbf /]# mkdir -p /mysqldata/mysql

3.上传介质mysql-8.0.33-linux-glibc2.28-x86_64.tar.gz到/mysqlsoft目录中并解压

[root@sjbf /]# cd /mysqlsoft
[root@sjbf mysqlsoft]# ll
total 527224
-rw-r--r-- 1 root root 539869923 May  5 09:50 mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz

[root@sjbf mysqlsoft]# tar xvJf mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz

[root@sjbf mysqlsoft]# ll
total 600560
drwxr-xr-x 9 root root      4096 May  6 03:49 mysql-8.0.33-linux-glibc2.12-x86_64
-rw-r--r-- 1 root root 614964216 May  5 15:28 mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz

建议一般不要修改默认文件名,通过软连接来完成

[root@sjbf mysqlsoft]# ln -s mysql-8.0.33-linux-glibc2.12-x86_6 mysql
[root@sjbf mysqlsoft]# ll
total 600560
lrwxrwxrwx 1 root root        34 May  6 03:53 mysql -> mysql-8.0.33-linux-glibc2.12-x86_6
drwxr-xr-x 9 root root      4096 May  6 03:49 mysql-8.0.33-linux-glibc2.12-x86_64
-rw-r--r-- 1 root root 614964216 May  5 15:28 mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz



[root@sjbf mysqlsoft]# cd mysql
[root@sjbf mysql]# ll
total 312
drwxr-xr-x  2 7161 31415   4096 Mar 17 02:46 bin
drwxr-xr-x  2 7161 31415   4096 Mar 17 02:46 docs
drwxr-xr-x  3 7161 31415   4096 Mar 17 02:46 include
drwxr-xr-x  6 7161 31415   4096 Mar 17 02:46 lib
-rw-r--r--  1 7161 31415 284945 Mar 17 01:22 LICENSE
drwxr-xr-x  4 7161 31415   4096 Mar 17 02:46 man
-rw-r--r--  1 7161 31415    666 Mar 17 01:22 README
drwxr-xr-x 28 7161 31415   4096 Mar 17 02:46 share
drwxr-xr-x  2 7161 31415   4096 Mar 17 02:46 support-files

4. 创建mysql用户与用户组

[root@sjbf /]# groupadd mysql
[root@sjbf /]# useradd -r -g mysql -s /bin/false mysql
[root@sjbf /]# id mysql
uid=492(mysql) gid=54323(mysql) groups=54323(mysql)

因为用户只用于所有权目的,而不是登录目的,useradd命令使用-r与-s /bin/false选项来创建一个用户没有登录服务器主机的权限。

5.修改/mysqlsoft/mysql与/mysqldata/mysql目录权限

[root@sjbf /]# chown -R mysql:mysql /mysqlsoft/mysql
[root@sjbf /]# chown -R mysql:mysql /mysqldata/mysql
[root@sjbf /]# chmod -R 775 /mysqlsoft/mysql
[root@sjbf /]# chmod -R 775 /mysqldata/mysql

6. MySQL对于libaio库有依赖性。台果这个libaio库没有安装那么数据目录初始化与后续的数据库服务启动将会失败,安装libaio库执行以下操作:
查询是否安装了libaio库

[root@sjbf /]# yum search libaio
Loaded plugins: aliases, changelog, kabi, presto, refresh-packagekit, security, tmprepo, ulninfo, verify, versionlock
Loading support for kernel ABI
=========================================================================================================== N/S Matched: libaio ===========================================================================================================
libaio.i686 : Linux-native asynchronous I/O access library
libaio.x86_64 : Linux-native asynchronous I/O access library
libaio-devel.i686 : Development files for Linux-native asynchronous I/O access
libaio-devel.x86_64 : Development files for Linux-native asynchronous I/O access

  Name and summary matches only, use "search all" for everything.

如果没有安装,可以执行下面的命令来安装

[root@sjbf /]# yum install libaio
Loaded plugins: aliases, changelog, kabi, presto, refresh-packagekit, security, tmprepo, ulninfo, verify, versionlock
Loading support for kernel ABI
Setting up Install Process
Package libaio-0.3.107-10.el6.x86_64 already installed and latest version
Nothing to do

7.配置mysql参数
只是设置几个简单的mysql运行参数

[root@sjbf mysql]# vi my.cnf
[mysqld]
basedir=/mysqlsoft/mysql
datadir=/mysqldata/mysql
bind-address=0.0.0.0
user=mysql
port=3306
mysqlx_port=33060
log-error=/mysqldata/mysql/mysql.err
pid-file=/mysqldata/mysql/mysqld.pid
socket = /mysqlsoft/mysql/mysql.sock
mysqlx_socket=/mysqlsoft/mysql/mysqlx.sock
character-set-server=utf8
default-storage-engine=INNODB

注意:log-error 一定要配置,因为如果mysql启动错误,可以从日志文件中找到错误原因。其次bind-address配置0.0.0.0是为了监听所有的连接。还有就是socket参数所指定的mysql.sock文件的路径最好设置为/tmp/mysql.sock,因为unix socket文件的缺省位置在/tmp目录中。

8.初始化mysql

[root@sjbf bin]# ./mysqld --user=mysql  --defaults-file=/mysqlsoft/mysql/my.cnf --basedir=/mysqlsoft/mysql --datadir=/mysqldata/mysql --initialize
2023-05-05T20:21:18.355164Z 0 [System] [MY-013169] [Server] /mysqlsoft/mysql-8.0.33-linux-glibc2.12-x86_64/bin/mysqld (mysqld 8.0.33) initializing of server in progress as process 26730
2023-05-05T20:21:18.412269Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2023-05-05T20:21:25.831475Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
2023-05-05T20:21:42.025674Z 0 [ERROR] [MY-000067] [Server] unknown variable 'defaults-file=/mysqlsoft/mysql/my.cnf'.
2023-05-05T20:21:42.025812Z 0 [ERROR] [MY-013236] [Server] The designated data directory /mysqldata/mysql/ is unusable. You can remove all files that the server added to it.
2023-05-05T20:21:42.025914Z 0 [ERROR] [MY-010119] [Server] Aborting
2023-05-05T20:21:53.091946Z 0 [System] [MY-010910] [Server] /mysqlsoft/mysql-8.0.33-linux-glibc2.12-x86_64/bin/mysqld: Shutdown complete (mysqld 8.0.33)  MySQL Community Server - GPL.

调整一下参数顺序

[root@sjbf bin]# ./mysqld --defaults-file=/mysqlsoft/mysql/my.cnf --initialize --user=mysql --basedir=/mysqlsoft/mysql --datadir=/mysqldata/mysql



[root@sjbf mysql]# cat mysql.err
2023-05-05T20:26:01.800998Z 0 [System] [MY-013169] [Server] /mysqlsoft/mysql-8.0.33-linux-glibc2.12-x86_64/bin/mysqld (mysqld 8.0.33) initializing of server in progress as process 27188
2023-05-05T20:26:01.805553Z 0 [Warning] [MY-013242] [Server] --character-set-server: 'utf8' is currently an alias for the character set UTF8MB3, but will be an alias for UTF8MB4 in a future release. Please consider using UTF8MB4 in order to be unambiguous.
2023-05-05T20:26:01.870227Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2023-05-05T20:26:09.160682Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
2023-05-05T20:26:25.588664Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: #uVfq!szm7y/

其中[Note] A temporary password is generated for root@localhost:#uVfq!szm7y/ 的root@localhost: 后面跟的是mysql数据库登录的临时密码,各人安装生成的临时密码不一样。可以看到到日志文件没有报错,而且有了临时密码,表示初始化成功。

9. 如果想服务能够部署自动支持安全连接,使用mysql_ssl_rsa_setup工具来创建缺省SSL与RSA文件

[root@sjbf bin]# ./mysql_ssl_rsa_setup --datadir=/mysqldata/mysql

10.启动mysql服务

[root@sjbf /]# sh /mysqlsoft/mysql/support-files/mysql.server start
/mysqlsoft/mysql/support-files/mysql.server: line 239: my_print_defaults: command not found
/mysqlsoft/mysql/support-files/mysql.server: line 259: cd: /usr/local/mysql: No such file or directory
Starting MySQLCouldn't find MySQL server (/usr/local/mysql/bin/mysqld_safe)[FAILED]

启动mysql服务命令会报错,因为没有修改mysql的配置文件修改Mysql配置文件,修改前为以下内容

if test -z "$basedir"
then
  basedir=/usr/local/mysql
  bindir=/usr/local/mysql/bin
  if test -z "$datadir"
  then
    datadir=/usr/local/mysql/data
  fi
  sbindir=/usr/local/mysql/bin
  libexecdir=/usr/local/mysql/bin
else
  bindir="$basedir/bin"
  if test -z "$datadir"
  then
    datadir="$basedir/data"
  fi
  sbindir="$basedir/sbin"
  libexecdir="$basedir/libexec"
fi

修改后的内容如下

if test -z "$basedir"
then
  basedir=/mysqlsoft/mysql
  bindir=/mysqlsoft/mysql/bin
  if test -z "$datadir"
  then
    datadir=/mysqldata/mysql
  fi
  sbindir=/mysqlsoft/mysql/bin
  libexecdir=/mysqlsoft/mysql/bin
else
  bindir="$basedir/bin"
  if test -z "$datadir"
  then
    datadir="$basedir/data"
  fi
  sbindir="$basedir/sbin"
  libexecdir="$basedir/libexec"
fi

[root@sjbf /]# cp /mysqlsoft/mysql/support-files/mysql.server  /etc/init.d/mysqld
[root@sjbf /]# chmod 755 /etc/init.d/mysqld

启动MySQL

[root@sjbf /]# service mysqld start
Starting MySQL......[  OK  ]

11.配置环境变量

[root@sjbf /]#
# /etc/profile

# System wide environment and startup programs, for login setup
# Functions and aliases go in /etc/bashrc

# It's NOT a good idea to change this file unless you know what you
# are doing. It's much better to create a custom.sh shell script in
# /etc/profile.d/ to make custom changes to your environment, as this
# will prevent the need for merging in future updates.

export MYSQL_HOME=/mysqlsoft/mysql/
export PATH=$PATH:$MYSQL_HOME/bin

12.登录Mysql
初始化成功后,查看初始化密码

[root@sjbf ~]# cat /mysqldata/mysql/mysql.err | grep password
2023-05-05T20:26:25.588664Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: #uVfq!szm7y/

并输入刚刚复制的密码,但是 却提示不能通过mysql.sock文件实现连接

[root@sjbf bin]# ./mysqladmin -u root -p password
Enter password:
mysqladmin: connect to server at 'localhost' failed
error: 'Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)'
Check that mysqld is running and that the socket: '/tmp/mysql.sock' exists!

这里就奇怪了,因为在my.cnf文件中设置的socket文件路径为/mysqlsoft/mysql/mysql.sock,但mysql所使用的文件不是启动服务所生成的。

使用-S选项来指定生成的mysql.sock文件进行登录是可以成功登录的

[root@sjbf ~]# mysql -S /mysqlsoft/mysql/mysql.sock -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.33

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

为了方便登录不每次指定-S参数可以在my.cnf文件中指定socket参数

[client]
socket = /mysqlsoft/mysql/mysql.sock

[root@sjbf tmp]# mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.33

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

13.重置root用户密码

[root@sjbf tmp]# mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.33

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> set password=password("123456");
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'password("123456")' at line 1

用5.7的方法修改密码会报错

mysql> ALTER user 'root'@'localhost' IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.14 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.03 sec)

mysql> use mysql
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

mysql> select host,user from user;
+-----------+------------------+
| host      | user             |
+-----------+------------------+
| localhost | mysql.infoschema |
| localhost | mysql.session    |
| localhost | mysql.sys        |
| localhost | root             |
+-----------+------------------+
4 rows in set (0.00 sec)

14.设置允许远程登录mysql
如果要远程访问数据库,只需要把拥有全部权限的root账号对应的记录的Host字段改为%就可以了

mysql> update user set host='%' where user='root';
Query OK, 1 row affected (0.13 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> flush privileges;
Query OK, 0 rows affected (0.03 sec)

mysql> ALTER USER 'root'@'%' IDENTIFIED BY '123456' PASSWORD EXPIRE NEVER;
Query OK, 0 rows affected (0.09 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.02 sec)

mysql> grant all privileges on *.* to 'root'@'%';
Query OK, 0 rows affected (0.14 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.04 sec)

测试远程登录

[root@sjbf tmp]# mysql -h 10.13.10.99 -P 3306 -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 16
Server version: 8.0.33 MySQL Community Server - GPL

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.



mysql> select host,user,plugin from user;
+-----------+------------------+-----------------------+
| host      | user             | plugin                |
+-----------+------------------+-----------------------+
| %         | root             | caching_sha2_password |
| localhost | mysql.infoschema | caching_sha2_password |
| localhost | mysql.session    | caching_sha2_password |
| localhost | mysql.sys        | caching_sha2_password |
+-----------+------------------+-----------------------+
4 rows in set (0.00 sec)

[root@sjbf tmp]# mysql -h 10.13.10.99 -P 3306 -uroot -p123456 --ssl-cert=/mysqldata/mysql/client-cert.pem --ssl-key=/mysqldata/mysql/client-key.pem
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 17
Server version: 8.0.33 MySQL Community Server - GPL

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

在Oracle Linux 6.7操作系统上初始化MySQL 8.0.33时报’unknown variable ‘defaults-file’错误

在Oracle Linux 6.7操作系统上初始化MySQL 8.0.33时报’unknown variable ‘defaults-file’错误

[root@sjbf bin]# ./mysqld --user=mysql  --defaults-file=/mysqlsoft/mysql/my.cnf --basedir=/mysqlsoft/mysql --datadir=/mysqldata/mysql --initialize
2023-05-05T20:21:18.355164Z 0 [System] [MY-013169] [Server] /mysqlsoft/mysql-8.0.33-linux-glibc2.12-x86_64/bin/mysqld (mysqld 8.0.33) initializing of server in progress as process 26730
2023-05-05T20:21:18.412269Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2023-05-05T20:21:25.831475Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
2023-05-05T20:21:42.025674Z 0 [ERROR] [MY-000067] [Server] =/mysqlsoft/mysql/my.cnf'.
2023-05-05T20:21:42.025812Z 0 [ERROR] [MY-013236] [Server] The designated data directory /mysqldata/mysql/ is unusable. You can remove all files that the server added to it.
2023-05-05T20:21:42.025914Z 0 [ERROR] [MY-010119] [Server] Aborting
2023-05-05T20:21:53.091946Z 0 [System] [MY-010910] [Server] /mysqlsoft/mysql-8.0.33-linux-glibc2.12-x86_64/bin/mysqld: Shutdown complete (mysqld 8.0.33)  MySQL Community Server - GPL.

把defaults-file参数调整为mysqld命令的第一个参数执行成功

[root@sjbf bin]# ./mysqld --defaults-file=/mysqlsoft/mysql/my.cnf --initialize --user=mysql --basedir=/mysqlsoft/mysql --datadir=/mysqldata/mysql

[root@sjbf mysql]# tail -f mysql.err
2023-05-05T20:26:01.800998Z 0 [System] [MY-013169] [Server] /mysqlsoft/mysql-8.0.33-linux-glibc2.12-x86_64/bin/mysqld (mysqld 8.0.33) initializing of server in progress as process 27188
2023-05-05T20:26:01.805553Z 0 [Warning] [MY-013242] [Server] --character-set-server: 'utf8' is currently an alias for the character set UTF8MB3, but will be an alias for UTF8MB4 in a future release. Please consider using UTF8MB4 in order to be unambiguous.
2023-05-05T20:26:01.870227Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2023-05-05T20:26:09.160682Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
2023-05-05T20:26:25.588664Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: #uVfq!szm7y/

Oracle 19c IMP-00058 ORA-03113

用imp命令向Oracle 19c数据库导入数据时出现IMP-00058 ORA-03113错误

经过排查最后发现是因为执行imp命令的客户端机器内存不足引起的,在关闭无关的程序后再次执行可以正常导入。

MySQL 5.7 将表从MyISAM存储引擎转换为InnoDB存储引擎

将表从MyISAM存储引擎转换为InnoDB存储引擎
如果你想将MyISAM表转换为InnoDB,以获得更好的可靠性和可伸缩性,那么在转换之前,请查看以下指南和技巧。
.调整MyISAM和InnoDB的内存使用
.处理过长或过短的事务
.处理死锁
.规划存储布局
.转换现有表
.克隆表的结构
.传送现有数据
.储存需求
.为每个表定义一个主键
.应用程序性能考虑
.了解与InnoDB表关联的文件

调整MyISAM和InnoDB的内存使用
当用户将MyISAM表转换为InnoDB表时,降低key_buffer_size配置选项的值,以释放缓存结果不再需要的内存。增加innodb_buffer_pool_size配置选项的值,该选项的作用类似于为InnoDB表分配缓存内存。InnoDB缓冲池缓存了表数据和索引数据,加快了查询的查找速度,并将查询结果保存在内存中以供重用。

在繁忙的服务器上,在关闭查询缓存的情况下运行基准测试。InnoDB缓冲池提供了类似的好处,所以查询缓存可能不必要地占用内存。

处理过长或过短的事务
因为MyISAM表不支持事务,所以您可能没有太注意自动提交配置选项以及COMMIT和ROLLBACK语句。这些关键字对于允许多个会话并发地读写InnoDB表非常重要,在写工作负载繁重的情况下提供大量的可伸缩性优势。

当事务处于打开状态时,系统会保留事务开始时看到的数据快照,如果系统插入、更新和删除数百万行,而事务继续运行,这可能会导致大量开销。因此,要注意避免事务运行时间过长:

.如果您使用mysql会话进行交互实验,当完成后请始终COMMIT(完成更改)或ROLLBACK(撤销更改)。关闭交互式会话,而不是让它们长时间开放,以避免意外地使事务长时间开放。

.确保应用程序中的任何错误处理程序也ROLLBACK未完成的更改或COMMIT已完成的更改。

.ROLLBACK是一个相对昂贵的操作,因为INSERT, UPDATE和DELETE操作在COMMIT之前被写入InnoDB表,并且期望大多数更改被成功提交并且很少回滚。在对大量数据进行实验时,避免对大量行进行更改,然后回滚这些更改。

.当使用INSERT语句顺序加载大量数据时,请定期提交结果,以避免事务持续数小时。在典型的数据仓库加载操作中,如果出现问题,您将截断表(使用truncate table)并从头开始,而不是执行ROLLBACK。

前面的技巧可以节省内存和磁盘空间,避免在太长的事务中浪费。当事务比应有的时间短时,问题是I/O过多。对于每次COMMIT, MySQL确保每个更改都被安全地记录到磁盘上,这涉及到一些I/O。

.对于大多数InnoDB表的操作,你应该设置autocommit=0。从效率的角度来看,当发出大量连续的INSERT、UPDATE或DELETE语句时,这避免了不必要的I/O。从安全的角度来看,如果你在mysql命令行或应用程序的异常处理中犯了错误,这允许你发出回滚语句来恢复丢失或混淆的数据。

.autocommit=1适合InnoDB表的情况是,当运行一系列查询以生成报告或分析统计数据时。在这种情况下,不存在与COMMIT或ROLLBACK相关的I/O惩罚, InnoDB可以自动优化只读工作负载。

.如果您进行了一系列相关的更改,请在最后使用COMMIT命令一次完成所有更改。例如,如果将相关的信息片段插入到几个表中,则在进行所有更改后执行一次COMMIT。或者,如果您运行了许多连续的INSERT语句,请在加载完所有数据后执行单个COMMIT;如果您要执行数百万条INSERT语句,也许可以通过每10,000或100,000条记录发出COMMIT来拆分巨大的事务,这样事务就不会变得太大。

.请记住,即使是SELECT语句也会打开一个事务,因此在交互式mysql会话中运行一些报告或调试查询后,发出COMMIT或关闭mysql会话。

处理死锁
你可能会在MySQL错误日志中看到提到“死锁”的警告消息,或者在SHOW ENGINE INNODB STATUS的输出中看到。尽管死锁的名字听起来很可怕,但对于InnoDB表来说,死锁并不是一个严重的问题,通常不需要任何纠正措施。当两个事务开始修改多个表,以不同的顺序访问表时,它们可能会达到一种状态,即每个事务都在等待另一个事务,而不能继续进行。当启用死锁检测(默认)时,MySQL立即检测到这种情况,并取消(回滚)“较小的”事务,允许另一个事务继续进行。如果使用innodb_deadlock_detect配置选项禁用了死锁检测,InnoDB依赖innodb_lock_wait_timeout设置在死锁发生的情况下回滚事务。

无论哪种方式,应用程序都需要错误处理逻辑来重新启动由于死锁而被强制取消的事务。当您像以前一样重新发出相同的SQL语句时,最初的计时问题将不再适用。要么是其他事务已经完成,您的事务可以继续,要么是其他事务仍在进行中,您的事务等待它完成。

如果死锁警告不断发生,则可以检查应用程序代码以重新排序
以一致的方式进行SQL操作,或者缩短事务。你可以使用innodb_print_all_deadlocks选项来测试,在MySQL错误日志中查看所有死锁警告,而不仅仅是SHOW ENGINE INNODB STATUS输出中的最后一个警告。

规划存储布局
为了从InnoDB表中获得最佳性能,您可以调整一些与存储布局相关的参数。

当您转换大型、频繁访问和保存重要数据的MyISAM表时,请调查并考虑innodb_file_per_table、innodb_file_format和innodb_page_size配置选项,以及CREATE TABLE语句的ROW_FORMAT和KEY_BLOCK_SIZE子句。

在最初的实验中,最重要的设置是innodb_file_per_table。当启用这个设置时,MySQL 5.6.6默认会在file-per–table表空间中隐式创建新的InnoDB表。与InnoDB系统表空间不同的是,在表被截断或删除时,操作系统可以通过file-per-table表空间回收磁盘空间。file-per-table表空间还支持Barracuda文件格式和相关特性,如表压缩、变长列的高效页外存储和大索引前缀。

您也可以将InnoDB表存储在共享的通用表空间中。通用表空间支持Barracuda文件格式,可以包含多个表。

转换现有表
使用ALTER table命令将一个非InnoDB表转换为InnoDB表。
ALTER TABLE table_name ENGINE=InnoDB;

不要将MySQL数据库中的MySQL系统表从MyISAM转换为InnoDB类型。这是一个不受支持的操作。类型。

克隆表的结构
你可能会为一个MyISAM表克隆一个InnoDB表,而不是使用ALTER table执行转换,以便在切换之前并行测试新旧表。

用相同的列和索引定义创建一个空的InnoDB表。使用SHOW CREATE TABLE table_name\G查看完整的CREATE TABLE语句。将ENGINE子句更改为引擎= INNODB。

转换现有表
为了将大量数据转移到上一节创建的空InnoDB表中,使用insert into innodb_table SELECT * FROM myisam_table ORDER BY primary_key_columns来插入记录。

也可以在插入数据后为InnoDB表创建索引。以前,创建新的二级索引对于InnoDB来说是一个很慢的操作,但是现在您可以在加载数据之后创建索引,而创建索引的开销相对较小。

如果你在副键上有UNIQUE约束,你可以通过在导入操作中暂时关闭唯一性检查来加速表的导入:

SET unique_checks=0;
... import operation ...
SET unique_checks=1;

对于大型表,这节省了磁盘I/O,因为InnoDB可以使用它的更改缓冲区来批量写入二级索引记录。确保数据不包含重复键。Unique_checks允许但不要求存储引擎忽略重复的键。

为了更好地控制插入过程,您可以将大表分段插入:

INSERT INTO newtable SELECT * FROM oldtable WHERE yourkey > something AND yourkey < = somethingelse;

插入所有记录后,可以重命名表。

在转换大表的过程中,增加InnoDB缓冲池的大小以减少磁盘I/O,最大不超过物理内存的80%。你也可以增加InnoDB日志文件的大小。

储存需求
如果您打算在转换过程中为InnoDB表中的数据创建多个临时副本,建议您以文件/表的方式创建表,以便在删除表时回收磁盘空间。当innodb_file_per_table配置选项被启用(默认)时,新创建的InnoDB表将隐式地创建在file-per-table表空间中。

无论您是直接转换MyISAM表还是创建一个克隆的InnoDB表,都要确保在转换过程中有足够的磁盘空间来容纳新旧表。InnoDB表比MyISAM表需要更多的磁盘空间。如果ALTER TABLE操作耗尽了空间,它将启动回滚,如果是磁盘绑定,则回滚可能需要数小时。对于插入,InnoDB使用insert buffer将辅助索引记录批量合并到索引中。这节省了大量的磁盘I/O。对于回滚,没有使用这种机制,回滚可能比插入时间长30倍。

在回滚失控的情况下,如果您的数据库中没有有价值的数据,那么建议终止数据库进程,而不是等待数百万个磁盘I/O操作完成。

为每个表定义一个主键
主键子句是影响MySQL查询性能以及表和索引空间使用的一个关键因素。主键唯一地标识表中的一行。表中的每一行都必须有一个主键值,并且任何两行都不能有相同的主键值。

这些是主键的指导原则,后面是更详细的解释。
.为每个表声明一个主键。通常,在WHERE子句中查询单行时,它是最重要的一列。

.在原始的CREATE TABLE语句中声明PRIMARY KEY子句,而不是稍后通过ALTER TABLE语句添加它。

.仔细选择列及其数据类型。首选数字列,而不是字符或字符串列。

.如果没有其他稳定的、唯一的、非空的数字列可供使用,请考虑使用自动递增列。

.如果怀疑主键列的值是否会改变,自动递增列也是一个不错的选择。更改主键列的值是一项代价高昂的操作,可能涉及重新安排表内和每个辅助索引内的数据。

考虑为任何还没有主键的表添加一个主键。根据表的最大投影大小,使用最小的实际数值类型。这可以使每一行稍微紧凑一些,对于大型表可以节省大量空间。如果表有辅助索引,那么节省的空间会成倍增加,因为主键值在每个辅助索引项中都是重复的。除了减少磁盘上的数据大小,小主键还可以让缓冲池容纳更多的数据,加快各种操作并提高并发性。

如果表中某些较长的列已经有了主键,比如VARCHAR,可以考虑添加一个新的unsigned AUTO_INCREMENT列,并切换主键到该列,即使该列在查询中没有被引用。这种设计改变可以在二级索引中节省大量空间。用户可以将之前的主键列指定为UNIQUE NOT NULL,以实施与主键子句相同的约束,即防止所有这些列中出现重复值或NULL值。

如果您将相关信息分散到多个表中,通常每个表使用相同的列作为其主键。例如,人事数据库可能有几个表,每个表都有一个员工编号的主键。销售数据库中可能有一些表的主键是客户编号,还有一些表的主键是订单编号。因为使用主键的查找非常快,所以可以为这样的表构造高效的连接查询。

应用程序性能考虑
InnoDB的可靠性和可扩展性比等价的MyISAM表需要更多的磁盘存储空间。您可以稍微改变列和索引的定义,以便更好地利用空间,减少处理结果集时的I/O和内存消耗,以及更好地利用索引查找的查询优化计划。

如果你设置了一个数字ID作为主键,使用这个值与其他表中的相关值进行交叉引用,尤其是在连接查询中。例如,与其接受一个国家名作为输入并进行查询以搜索相同的名称,不如进行一次查找以确定国家ID,然后进行其他查询(或单个连接查询)以跨几个表查找相关信息。与其将顾客或商品编号存储为数字字符串(可能会占用几个字节),不如将其转换为数字ID以便存储和查询。一个4字节的unsigned INT列可以索引超过40亿个元素。