MySQL8-中文参考-三十一-

MySQL8 中文参考(三十一)

原文:docs.oracle.com/javase/tutorial/reallybigindex.html

17.6.5 重做日志

原文:dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html

重做日志是一种基于磁盘的数据结构,在崩溃恢复期间用于纠正由不完整事务写入的数据。在正常操作期间,重做日志对由 SQL 语句或低级 API 调用导致的更改表数据的请求进行编码。在意外关闭之前未完成更新数据文件的修改在初始化期间和在接受连接之前自动重放。有关重做日志在崩溃恢复中的作用的信息,请参见第 17.18.2 节,“InnoDB 恢复”。

重做日志在磁盘上以重做日志文件的形式物理表示。写入重做日志文件的数据以受影响的记录为编码,这些数据被统称为重做。数据通过重做日志文件的传递由一个不断增加的 LSN 值表示。重做日志数据在数据修改发生时追加,而最旧的数据在检查点进展时被截断。

有关重做日志的信息和程序在以下主题中描述:

  • 配置重做日志容量(MySQL 8.0.30 或更高版本)

  • 配置重做日志容量(MySQL 8.0.30 之前)

  • 自动重做日志容量配置

  • 重做日志归档

  • 禁用重做日志记录

  • 相关主题

配置重做日志容量(MySQL 8.0.30 或更高版本)

从 MySQL 8.0.30 开始,innodb_redo_log_capacity系统变量控制重做日志文件占用的磁盘空间量。您可以在启动时或运行时使用SET GLOBAL语句在选项文件中设置此变量;例如,以下语句将重做日志容量设置为 8GB:

SET GLOBAL innodb_redo_log_capacity = 8589934592;

在运行时设置时,配置更改会立即生效,但新限制可能需要一些时间才能完全实施。如果重做日志文件占用的空间少于指定值,则从缓冲池中更积极地刷新脏页到表空间数据文件,最终增加重做日志文件占用的磁盘空间。如果重做日志文件占用的空间多于指定值,则更积极地刷新脏页,最终减少重做日志文件占用的磁盘空间。

innodb_redo_log_capacity变量取代了已弃用的innodb_log_files_in_groupinnodb_log_file_size变量。当定义了innodb_redo_log_capacity设置时,innodb_log_files_in_groupinnodb_log_file_size设置将被忽略;否则,这些设置将用于计算innodb_redo_log_capacity设置(innodb_log_files_in_group * innodb_log_file_size = innodb_redo_log_capacity)。如果没有设置这些变量中的任何一个,重做日志容量将设置为innodb_redo_log_capacity的默认值,即 104857600 字节(100MB)。最大重做日志容量为 128GB。

重做日志文件位于数据目录中的#innodb_redo目录中,除非通过innodb_log_group_home_dir变量指定了不同的目录。如果定义了innodb_log_group_home_dir,则重做日志文件位于该目录中的#innodb_redo目录中。有两种类型的重做日志文件,普通和备用。普通的重做日志文件是正在使用的。备用的重做日志文件是等待使用的。InnoDB尝试总共维护 32 个重做日志文件,每个文件的大小等于 1/32 * innodb_redo_log_capacity;但是,在修改innodb_redo_log_capacity设置后,文件大小可能会有所不同。

重做日志文件使用#ib_redo*N*的命名约定,其中N是重做日志文件编号。备用的重做日志文件以_tmp后缀表示。以下示例显示了一个#innodb_redo目录中的重做日志文件,其中有 21 个活动重做日志文件和 11 个备用重做日志文件,按顺序编号。

'#ib_redo582'  '#ib_redo590'  '#ib_redo598'      '#ib_redo606_tmp'
'#ib_redo583'  '#ib_redo591'  '#ib_redo599'      '#ib_redo607_tmp'
'#ib_redo584'  '#ib_redo592'  '#ib_redo600'      '#ib_redo608_tmp'
'#ib_redo585'  '#ib_redo593'  '#ib_redo601'      '#ib_redo609_tmp'
'#ib_redo586'  '#ib_redo594'  '#ib_redo602'      '#ib_redo610_tmp'
'#ib_redo587'  '#ib_redo595'  '#ib_redo603_tmp'  '#ib_redo611_tmp'
'#ib_redo588'  '#ib_redo596'  '#ib_redo604_tmp'  '#ib_redo612_tmp'
'#ib_redo589'  '#ib_redo597'  '#ib_redo605_tmp'  '#ib_redo613_tmp'

每个普通的重做日志文件与特定范围的 LSN 值相关联;例如,以下查询显示了前面示例中列出的活动重做日志文件的START_LSNEND_LSN值:

mysql> SELECT FILE_NAME, START_LSN, END_LSN FROM performance_schema.innodb_redo_log_files;
+----------------------------+--------------+--------------+
| FILE_NAME                  | START_LSN    | END_LSN      |
+----------------------------+--------------+--------------+
| ./#innodb_redo/#ib_redo582 | 117654982144 | 117658256896 |
| ./#innodb_redo/#ib_redo583 | 117658256896 | 117661531648 |
| ./#innodb_redo/#ib_redo584 | 117661531648 | 117664806400 |
| ./#innodb_redo/#ib_redo585 | 117664806400 | 117668081152 |
| ./#innodb_redo/#ib_redo586 | 117668081152 | 117671355904 |
| ./#innodb_redo/#ib_redo587 | 117671355904 | 117674630656 |
| ./#innodb_redo/#ib_redo588 | 117674630656 | 117677905408 |
| ./#innodb_redo/#ib_redo589 | 117677905408 | 117681180160 |
| ./#innodb_redo/#ib_redo590 | 117681180160 | 117684454912 |
| ./#innodb_redo/#ib_redo591 | 117684454912 | 117687729664 |
| ./#innodb_redo/#ib_redo592 | 117687729664 | 117691004416 |
| ./#innodb_redo/#ib_redo593 | 117691004416 | 117694279168 |
| ./#innodb_redo/#ib_redo594 | 117694279168 | 117697553920 |
| ./#innodb_redo/#ib_redo595 | 117697553920 | 117700828672 |
| ./#innodb_redo/#ib_redo596 | 117700828672 | 117704103424 |
| ./#innodb_redo/#ib_redo597 | 117704103424 | 117707378176 |
| ./#innodb_redo/#ib_redo598 | 117707378176 | 117710652928 |
| ./#innodb_redo/#ib_redo599 | 117710652928 | 117713927680 |
| ./#innodb_redo/#ib_redo600 | 117713927680 | 117717202432 |
| ./#innodb_redo/#ib_redo601 | 117717202432 | 117720477184 |
| ./#innodb_redo/#ib_redo602 | 117720477184 | 117723751936 |
+----------------------------+--------------+--------------+

在执行检查点时,InnoDB将检查点 LSN 存储在包含此 LSN 的文件的头部。在恢复过程中,所有重做日志文件都会被检查,恢复从最新的检查点 LSN 开始。

提供了几个状态变量用于监视重做日志和重做日志容量调整操作;例如,您可以查询Innodb_redo_log_resize_status以查看调整操作的状态:

mysql> SHOW STATUS LIKE 'Innodb_redo_log_resize_status';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| Innodb_redo_log_resize_status | OK    |
+-------------------------------+-------+

Innodb_redo_log_capacity_resized状态变量显示当前重做日志容量限制:

mysql> SHOW STATUS LIKE 'Innodb_redo_log_capacity_resized';
 +----------------------------------+-----------+
| Variable_name                    | Value     |
+----------------------------------+-----------+
| Innodb_redo_log_capacity_resized | 104857600 |
+----------------------------------+-----------+

其他适用的状态变量包括:

  • Innodb_redo_log_checkpoint_lsn

  • Innodb_redo_log_current_lsn

  • Innodb_redo_log_flushed_to_disk_lsn

  • Innodb_redo_log_logical_size

  • Innodb_redo_log_physical_size

  • Innodb_redo_log_read_only

  • Innodb_redo_log_uuid

请参考状态变量描述以获取更多信息。

您可以通过查询innodb_redo_log_files性能模式表查看有关活动重做日志文件的信息。以下查询检索所有表列的数据:

SELECT FILE_ID, START_LSN, END_LSN, SIZE_IN_BYTES, IS_FULL, CONSUMER_LEVEL 
FROM performance_schema.innodb_redo_log_files;

配置重做日志容量(MySQL 8.0.30 之前)

在 MySQL 8.0.30 之前,默认情况下,InnoDB在数据目录中创建两个重做日志文件,命名为ib_logfile0ib_logfile1,并以循环方式写入这些文件。

修改重做日志容量需要更改重做日志文件的数量或大小,或两者兼而有之。

  1. 停止 MySQL 服务器,并确保它在没有错误的情况下关闭。

  2. 编辑my.cnf以更改重做日志文件配置。要更改重做日志文件大小,请配置innodb_log_file_size。要增加重做日志文件数量,请配置innodb_log_files_in_group

  3. 再次启动 MySQL 服务器。

如果InnoDB检测到innodb_log_file_size与重做日志文件大小不同,它会写入日志检查点,关闭并删除旧日志文件,以请求的大小创建新日志文件,并打开新日志文件。

自动重做日志容量配置

当启用innodb_dedicated_server时,InnoDB会自动配置某些InnoDB参数,包括重做日志容量。自动配置适用于驻留在专用于 MySQL 的服务器上的 MySQL 实例,其中 MySQL 服务器可以使用所有可用的系统资源。有关更多信息,请参见第 17.8.12 节,“为专用 MySQL 服务器启用自动配置”。

重做日志归档

备份工具有时可能在备份操作进行时未能跟上重做日志生成的速度,导致由于这些记录被覆盖而丢失重做日志记录。这个问题在备份操作期间有大量 MySQL 服务器活动,并且重做日志文件存储介质的运行速度比备份存储介质快时最常发生。MySQL 8.0.17 中引入的重做日志归档功能通过将重做日志记录顺序写入归档文件来解决这个问题,除了重做日志文件外。备份工具可以根据需要从归档文件中复制重做日志记录,从而避免数据的潜在丢失。

如果在服务器上配置了重做日志归档,MySQL 企业备份,可用于MySQL 企业版,在备份 MySQL 服务器时使用重做日志归档功能。

在服务器上启用重做日志归档需要为innodb_redo_log_archive_dirs系统变量设置一个值。该值被指定为带标签的重做日志归档目录的分号分隔列表。*label:directory*对由冒号(:)分隔。例如:

mysql> SET GLOBAL innodb_redo_log_archive_dirs='*label1*:*directory_path1*[;*label2*:*directory_path2*;…]';

label是归档目录的任意标识符。它可以是任何字符的字符串,但不允许使用冒号(😃。空标签也是允许的,但在这种情况下仍然需要冒号(😃。必须指定directory_path。在激活重做日志归档时,用于重做日志归档文件的目录必须存在,否则会返回错误。路径可以包含冒号('😂,但不允许使用分号(😉。

必须在激活重做日志归档之前配置innodb_redo_log_archive_dirs变量。默认值为NULL,不允许激活重做日志归档。

注意

您指定的归档目录必须满足以下要求。(这些要求在激活重做日志归档时会被强制执行。):

  • 目录必须存在。目录不会被重做日志归档过程创建。否则,将返回以下错误:

    错误 3844 (HY000):重做日志归档目录'directory_path1'不存在���不是目录

  • 目录不能对所有用户开放访问。这是为了防止重做日志数据暴露给系统上的未经授权用户。否则,将返回以下错误:

    错误 3846 (HY000):重做日志归档目录'directory_path1'对所有操作系统用户可访问

  • 目录不能是由datadirinnodb_data_home_dirinnodb_directoriesinnodb_log_group_home_dirinnodb_temp_tablespaces_dirinnodb_tmpdirinnodb_undo_directorysecure_file_priv定义的目录,也不能是这些目录的父目录或子目录。否则,将返回类似以下的错误:

    错误 3845 (HY000):重做日志归档目录'directory_path1'在服务器目录'datadir' - '/path/to/data_directory'中、下或上

当支持重做日志归档的备份实用程序启动备份时,备份实用程序通过调用innodb_redo_log_archive_start()函数激活重做日志归档。

如果您没有使用支持重做日志归档的备份实用程序,也可以手动激活重做日志归档,如下所示:

mysql> SELECT innodb_redo_log_archive_start('*label*', '*subdir*');
+------------------------------------------+
| innodb_redo_log_archive_start('*label*') |
+------------------------------------------+
| 0                                        |
+------------------------------------------+

或:

mysql> DO innodb_redo_log_archive_start('*label*', '*subdir*');
Query OK, 0 rows affected (0.09 sec)

注意

激活重做日志归档的 MySQL 会话(使用innodb_redo_log_archive_start())必须保持打开状态以进行归档。相同的会话必须停用重做日志归档(使用innodb_redo_log_archive_stop())。如果会话在显式停用重做日志归档之前终止,则服务器会隐式停用重做日志归档并删除重做日志归档文件。

其中label是由innodb_redo_log_archive_dirs定义的标签;subdir是用于指定保存归档文件的label标识的目录的子目录的可选参数;它必须是一个简单的目录名称(不允许斜杠(/)、反斜杠(\)或冒号(:))。subdir可以为空,为 null,或者可以省略。

只有具有INNODB_REDO_LOG_ARCHIVE权限的用户才能通过调用innodb_redo_log_archive_start()激活重做日志归档,或使用innodb_redo_log_archive_stop()停用它。运行备份实用程序的 MySQL 用户或手动激活和停用重做日志归档的 MySQL 用户必须具有此权限。

重做日志归档文件路径为*directory_identified_by_label*/[*subdir*/]archive.*serverUUID*.000001.log,其中*directory_identified_by_label*是由innodb_redo_log_archive_start()*label*参数标识的归档目录。*subdir*是用于innodb_redo_log_archive_start()的可选参数。

例如,重做日志归档文件的完整路径和名称类似于以下内容:

/*directory_path*/*subdirectory*/archive.e71a47dc-61f8-11e9-a3cb-080027154b4d.000001.log

备份工具完成复制InnoDB数据文件后,通过调用innodb_redo_log_archive_stop()函数来停用重做日志归档。

如果您没有使用支持重做日志归档的备份工具,也可以手动停用重做日志归档,如下所示:

mysql> SELECT innodb_redo_log_archive_stop();
+--------------------------------+
| innodb_redo_log_archive_stop() |
+--------------------------------+
| 0                              |
+--------------------------------+

或者:

mysql> DO innodb_redo_log_archive_stop();
Query OK, 0 rows affected (0.01 sec)

在停止函数成功完成后,备份工具会从归档文件中查找相关部分的重做日志数据,并将其复制到备份中。

备份工具完成复制重做日志数据并不再需要重做日志归档文件后,会删除该归档文件。

在正常情况下,归档文件的删除是备份工具的责任。但是,如果重做日志归档操作在调用innodb_redo_log_archive_stop()之前意外退出,则 MySQL 服务器会删除该文件。

性能考虑

激活重做日志归档通常会因为额外的写入活动而产生轻微的性能成本。

在 Unix 和类 Unix 操作系统上,性能影响通常较小,假设没有持续高速更新。在 Windows 上,性能影响通常略高,假设情况相同。

如果更新速率持续较高且重做日志归档文件与重做日志文件位于相同存储介质上,则由于复合写入活动,性能影响可能更为显著。

如果更新速率持续较高且重做日志归档文件位于比重做日志文件更慢的存储介质上,则性能会受到任意影响。

写入重做日志归档文件不会妨碍正常的事务日志记录,除非重做日志归档文件存储介质的速率远远低于重做日志文件存储介质,并且有大量持久化的重做日志块等待写入重做日志归档文件。在这种情况下,事务日志记录速率会降低到可以由重做日志归档文件所在的较慢存储介质管理的水平。

禁用重做日志记录

截至 MySQL 8.0.21 版本,您可以使用ALTER INSTANCE DISABLE INNODB REDO_LOG语句禁用重做日志记录。此功能旨在将数据加载到新的 MySQL 实例中。禁用重做日志记录通过避免重做日志写入和双写缓冲来加快数据加载速度。

警告

此功能仅用于将数据加载到新的 MySQL 实例中。不要在生产系统上禁用重做日志。允许在禁用重做日志时关闭和重新启动服务器,但在禁用重做日志时发生意外服务器停止可能会导致数据丢失和实例损坏。

在禁用重做日志后尝试重新启动服务器会被拒绝,并显示以下错误:

[ERROR] [MY-013598] [InnoDB] Server was killed when Innodb Redo 
logging was disabled. Data files could be corrupt. You can try 
to restart the database with innodb_force_recovery=6

在这种情况下,初始化一个新的 MySQL 实例并重新启动数据加载过程。

启用和禁用重做日志需要INNODB_REDO_LOG_ENABLE权限。

Innodb_redo_log_enabled状态变量允许监视重做日志状态。

在禁用重做日志时,不允许克隆操作和重做日志归档,反之亦然。

ALTER INSTANCE [ENABLE|DISABLE] INNODB REDO_LOG操作需要独占备份元数据锁,这会阻止其他ALTER INSTANCE操作并发执行。其他ALTER INSTANCE操作必须等待锁释放后才能执行。

以下过程演示了在将数据加载到新的 MySQL 实例时如何禁用重做日志。

  1. 在新的 MySQL 实例上,向负责禁用重做日志的用户帐户授予INNODB_REDO_LOG_ENABLE权限。

    mysql> GRANT INNODB_REDO_LOG_ENABLE ON *.* to 'data_load_admin';
    
  2. 作为data_load_admin用户,禁用重做日志:

    mysql> ALTER INSTANCE DISABLE INNODB REDO_LOG;
    
  3. 检查Innodb_redo_log_enabled状态变量,确保重做日志已禁用。

    mysql> SHOW GLOBAL STATUS LIKE 'Innodb_redo_log_enabled';
    +-------------------------+-------+
    | Variable_name           | Value |
    +-------------------------+-------+
    | Innodb_redo_log_enabled | OFF   |
    +-------------------------+-------+
    
  4. 运行数据加载操作。

  5. 作为data_load_admin用户,在数据加载操作完成后启用重做日志:

    mysql> ALTER INSTANCE ENABLE INNODB REDO_LOG;
    
  6. 检查Innodb_redo_log_enabled状态变量,确保重做日志已启用。

    mysql> SHOW GLOBAL STATUS LIKE 'Innodb_redo_log_enabled';
    +-------------------------+-------+
    | Variable_name           | Value |
    +-------------------------+-------+
    | Innodb_redo_log_enabled | ON    |
    +-------------------------+-------+
    

相关主题

  • 重做日志配置

  • 第 10.5.4 节,“优化 InnoDB 重做日志”

  • 重做日志加密

17.6.6 撤销日志

原文:dev.mysql.com/doc/refman/8.0/en/innodb-undo-logs.html

撤销日志是与单个读写事务关联的撤销日志记录集合。撤销日志记录包含有关如何撤消事务对聚簇索引记录的最新更改的信息。如果另一个事务需要在一致性读操作的一部分中查看原始数据,则从撤销日志记录中检索未修改的数据。撤销日志存在于撤销日志段中,这些段包含在回滚段中。回滚段驻留在撤销表空间和全局临时表空间中。

驻留在全局临时表空间中的撤销日志用于修改用户定义临时表中的数据的事务。这些撤销日志不会被重做记录,因为它们不需要用于崩溃恢复。它们仅在服务器运行时用于回滚。这种类型的撤销日志通过避免重做日志 I/O 来提高性能。

有关撤销日志的数据静态加密信息,请参阅撤销日志加密。

每个撤销表空间和全局临时表空间分别支持最多 128 个回滚段。innodb_rollback_segments变量定义了回滚段的数量。

回滚段支持的事务数量取决于回滚段中的撤销槽数量以及每个事务所需的撤销日志数量。回滚段中的撤销槽数量根据InnoDB页面大小而异。

InnoDB 页面大小 回滚段中的撤销槽数量(InnoDB 页面大小 / 16)
4096 (4KB) 256
8192 (8KB) 512
16384 (16KB) 1024
32768 (32KB) 2048
65536 (64KB) 4096

每个事务分配最多四个撤销日志,分别用于以下每种操作类型:

  1. 用户定义表上的INSERT操作

  2. 用户定义表上的UPDATEDELETE操作

  3. 用户定义临时表上的INSERT操作

  4. 用户定义临时表上的UPDATEDELETE操作

撤销日志根据需要分配。例如,对常规表和临时表执行INSERTUPDATEDELETE操作的事务需要完整分配四个撤销日志。只对常规表执行INSERT操作的事务需要一个撤销日志。

在常规表上执行操作的事务从分配的撤销表空间回滚段中分配撤销日志。在临时表上执行操作的事务从分配的全局临时表空间回滚段中分配撤销日志。

一个分配给事务的撤销日志将一直附加到该事务,直到其结束。例如,分配给常规表上的INSERT操作的事务的撤销日志将用于该事务执行的所有常规表上的INSERT操作。

鉴于上述因素,可以使用以下公式来估计InnoDB能够支持的并发读写事务数量。

注意

在达到InnoDB能够支持的并发读写事务数量之前,可能会遇到并发事务限制错误。当分配给事务的回滚段用完撤销槽时会发生这种情况。在这种情况下,请尝试重新运行事务。

当事务在临时表上执行操作时,InnoDB能够支持的并发读写事务数量受限于分配给全局临时表空间的回滚段数量,默认为 128。

  • 如果每个事务执行INSERT UPDATEDELETE操作,InnoDB能够支持的并发读写事务数量为:

    (innodb_page_size / 16) * innodb_rollback_segments * number of undo tablespaces
    
  • 如果每个事务执行INSERT UPDATEDELETE操作,InnoDB能够支持的并发读写事务数量为:

    (innodb_page_size / 16 / 2) * innodb_rollback_segments * number of undo tablespaces
    
  • 如果每个事务在临时表上执行INSERT操作,InnoDB能够支持的并发读写事务数量为:

    (innodb_page_size / 16) * innodb_rollback_segments
    
  • 如果每个事务在临时表上执行INSERT UPDATEDELETE操作,InnoDB能够支持的并发读写事务数量为:

    (innodb_page_size / 16 / 2) * innodb_rollback_segments
    

17.7 InnoDB 锁定和事务模型

原文:dev.mysql.com/doc/refman/8.0/en/innodb-locking-transaction-model.html

17.7.1 InnoDB 锁定

17.7.2 InnoDB 事务模型

17.7.3 InnoDB 中由不同 SQL 语句设置的锁

17.7.4 幻影行

17.7.5 InnoDB 中的死锁

17.7.6 事务调度

要实现大规模、繁忙或高可靠性的数据库应用程序,从不同数据库系统移植大量代码,或调整 MySQL 性能,了解InnoDB锁定和InnoDB事务模型是非常重要的。

本节讨论了几个与InnoDB锁定和InnoDB事务模型相关的主题,您应该熟悉。

  • 第 17.7.1 节,“InnoDB 锁定” 描述了InnoDB使用的锁类型。

  • 第 17.7.2 节,“InnoDB 事务模型” 描述了事务隔离级别和每种锁定策略的使用。它还讨论了autocommit的使用,一致的非锁定读取和锁定读取。

  • 第 17.7.3 节,“InnoDB 中由不同 SQL 语句设置的锁” 讨论了在InnoDB中为各种语句设置的特定类型的锁。

  • 第 17.7.4 节,“幻影行” 描述了InnoDB如何使用下一个键锁定来避免幻影行。

  • 第 17.7.5 节,“InnoDB 中的死锁” 提供了一个死锁示例,讨论了死锁检测,并提供了最小化和处理InnoDB中死锁的提示。

17.7.1 InnoDB 锁定

原文:dev.mysql.com/doc/refman/8.0/en/innodb-locking.html

本节描述了 InnoDB 使用的锁类型。

  • 共享锁和独占锁

  • 意向锁

  • 记录锁

  • 间隙锁

  • Next-Key 锁

  • 插入意向锁

  • 自增锁

  • 空间索引的谓词锁

共享锁和独占锁

InnoDB 实现标准的行级锁定,其中有两种类型的锁,共享 (S) 锁 和 独占 (X) 锁。

  • 一个共享 (S) 锁 允许持有锁的事务读取一行。

  • 一个独占 (X) 锁允许持有锁的事务更新或删除一行。

如果事务 T1 持有行 r 上的共享 (S) 锁,则来自某个不同事务 T2 对行 r 的锁的请求处理如下:

  • T2 请求 S 锁可以立即被授予。因此,T1T2 都持有 r 上的 S 锁。

  • T2rX 锁请求无法立即被授予。

如果事务 T1 持有行 r 上的独占 (X) 锁,则来自某个不同事务 T2r 上的任一类型锁的请求无法立即被授予。相反,事务 T2 必须等待事务 T1 释放 r 上的锁。

意向锁

InnoDB 支持多粒度锁定,允许行锁和表锁共存。例如,像LOCK TABLES ... WRITE 这样的语句在指定的表上获取一个独占锁(X 锁)。为了使多粒度级别的锁定实用,InnoDB 使用意向锁。意向锁是表级锁,指示事务后续需要对表中的行请求哪种类型的锁(共享或独占)。有两种类型的意向锁:

  • 一个意向共享锁 (IS) 表示事务打算在表中的各个行上设置共享锁。

  • 意向排他锁(IX)表示一个事务打算在表中的单个行上设置排他锁。

例如,SELECT ... FOR SHARE设置一个IS锁,而SELECT ... FOR UPDATE设置一个IX锁。

意向锁定协议如下:

  • 在事务可以在表中的行上获取共享锁之前,必须首先在表上获取一个IS锁或更强的锁。

  • 在事务可以在表中的行上获取排他锁之前,必须首先在表上获取一个IX锁。

表级锁类型兼容性总结如下矩阵。

X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容

如果请求的事务与现有锁兼容,则授予锁,但如果与现有锁冲突,则不授予锁。事务会等待,直到冲突的现有锁被释放。如果锁请求与现有锁冲突,并且由于会导致死锁而无法授予锁,则会发生错误。

意向锁不会阻塞任何东西,除了完整的表请求(例如,LOCK TABLES ... WRITE)。意向锁的主要目的是显示某人正在锁定一行,或者将要在表中锁定一行。

意向锁的事务数据在SHOW ENGINE INNODB STATUS和 InnoDB monitor 输出中看起来类似于以下内容:

TABLE LOCK table `test`.`t` trx id 10080 lock mode IX

记录锁

记录锁是对索引记录的锁定。例如,SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;阻止任何其他事务插入、更新或删除c1值为10的行。

记录锁总是锁定索引记录,即使一个表被定义为没有索引。对于这种情况,InnoDB会创建一个隐藏的聚簇索引,并将此索引用于记录锁定。参见 Section 17.6.2.1, “Clustered and Secondary Indexes”。

记录锁的事务数据在SHOW ENGINE INNODB STATUS和 InnoDB monitor 输出中看起来类似于以下内容:

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 00000000274f; asc     'O;;
 2: len 7; hex b60000019d0110; asc        ;;

间隙锁

间隙锁是索引记录之间的间隙上的锁,或者是第一个索引记录之前或最后一个索引记录之后的间隙上的锁。例如,SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;阻止其他事务将值15插入到列t.c1中,无论该列中是否已经存在任何这样的值,因为范围内所有现有值之间的间隙都被锁定。

一个间隙可能跨越单个索引值,多个索引值,甚至为空。

间隙锁是性能和并发性之间的权衡的一部分,并且在某些事务隔离级别中使用,在其他事务隔离级别中不使用。

使用唯一索引锁定行以搜索唯一行的语句不需要间隙锁定。(这不包括搜索条件仅包含多列唯一索引的某些列的情况;在这种情况下,确实会发生间隙锁定。)例如,如果id列有一个唯一索引,下面的语句仅对具有id值 100 的行使用索引记录锁定,而其他会话是否在前面的间隙中插入行并不重要:

SELECT * FROM child WHERE id = 100;

如果id没有被索引或具有非唯一索引,则该语句确实锁定了前面的间隙。

还值得注意的是,不同事务可以在同一间隙上持有冲突的锁。例如,事务 A 可以在一个间隙上持有共享间隙锁(间隙 S 锁),而事务 B 可以在同一间隙上持有独占间隙锁(间隙 X 锁)。允许冲突的间隙锁的原因是,如果从索引中清除记录,则不同事务持有的记录上的间隙锁必须合并。

InnoDB中,间隙锁是“纯粹抑制性的”,这意味着它们的唯一目的是防止其他事务向间隙中插入数据。间隙锁可以共存。一个事务获取的间隙锁不会阻止另一个事务在同一间隙上获取间隙锁。共享间隙锁和独占间隙锁之间没有区别。它们不会相互冲突,执行相同的功能。

间隙锁定可以被显式禁用。如果将事务隔离级别更改为READ COMMITTED,则会发生这种情况。在这种情况下,间隙锁定对搜索和索引扫描被禁用,仅用于外键约束检查和重复键检查。

使用READ COMMITTED隔离级别还有其他影响。对于不匹配行的记录锁在 MySQL 评估完WHERE条件后被释放。对于UPDATE语句,InnoDB执行“半一致性”读取,以便将最新提交的版本返回给 MySQL,以便 MySQL 可以确定行是否与UPDATEWHERE条件匹配。

下一个键锁

下一个键锁是在索引记录上设置记录锁和在索引记录之前的间隙上设置间隙锁的组合。

InnoDB 以一种使得当搜索或扫描表索引时,在遇到的索引记录上设置共享或排他锁的方式执行行级锁定。因此,行级锁实际上是索引记录锁。对索引记录的下一个键锁也会影响该索引记录之前的“间隙”。也就是说,下一个键锁是索引记录锁加上索引记录之前的间隙锁。如果一个会话在索引中的记录 R 上有共享或排他锁,则另一个会话不能在索引顺序中的 R 紧前的间隙中插入新的索引记录。

假设一个索引包含值 10、11、13 和 20。对于该索引的可能的下一个键锁覆盖以下区间,其中圆括号表示排除区间端点,方括号表示包含端点:

(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)

对于最后一个区间,下一个键锁锁定了索引中最大值上方的间隙和具有高于索引中任何实际值的“最大值”伪记录。最大值不是真正的索引记录,因此,实际上,这个下一个键锁只锁定了紧随最大索引值后的间隙。

默认情况下,InnoDBREPEATABLE READ 事务隔离级别下运行。在这种情况下,InnoDB 对搜索和索引扫描使用下一个键锁,以防止幻影行(参见 第 17.7.4 节,“幻影行”)。

下一个键锁的事务数据在 SHOW ENGINE INNODB STATUS 和 InnoDB 监视器 输出中类似于以下内容:

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 00000000274f; asc     'O;;
 2: len 7; hex b60000019d0110; asc        ;;

插入意向锁

插入意向锁是在 INSERT 操作插入行之前设置的一种间隙锁。此锁表示以一种插入的意图插入,即使多个插入到相同索引间隙的事务不需要等待对方,如果它们不是在间隙内的相同位置插入。假设存在值为 4 和 7 的索引记录。试图分别插入值为 5 和 6 的单独事务,在获取插入行的排他锁之前,都会在值为 4 和 7 之间的间隙上设置插入意向锁,但不会相互阻塞,因为这些行是非冲突的。

以下示例演示了一个事务在获取插入记录的排他锁之前获取插入意向锁。该示例涉及两个客户端,A 和 B。

客户端 A 创建一个包含两个索引记录(90 和 102)的表,然后启动一个事务,对 ID 大于 100 的索引记录进行排他锁定。在记录 102 之前,排他锁包括一个间隙锁:

mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);

mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id  |
+-----+
| 102 |
+-----+

客户端 B 开始一个事务,向间隙中插入一条记录。该事务在等待获取排他锁时会获取插入意向锁。

mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);

插入意向锁的事务数据在 SHOW ENGINE INNODB STATUS 和 InnoDB 监视器 输出中类似于以下内容:

RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000066; asc    f;;
 1: len 6; hex 000000002215; asc     " ;;
 2: len 7; hex 9000000172011c; asc     r  ;;...

AUTO-INC 锁

AUTO-INC 锁是由插入具有 AUTO_INCREMENT 列的表的事务获取的特殊表级锁。在最简单的情况下,如果一个事务正在向表中插入值,任何其他事务必须等待进行自己的插入,以便由第一个事务插入的行接收连续的主键值。

innodb_autoinc_lock_mode 变量控制自增锁定的算法。它允许您选择如何在可预测的自增值序列和插入操作的最大并发性之间进行权衡。

更多信息,请参见 第 17.6.1.6 节,“InnoDB 中的 AUTO_INCREMENT 处理”。

空间索引的谓词锁

InnoDB 支持包含空间数据的列的 SPATIAL 索引(参见 第 13.4.9 节,“优化空间分析”)。

为处理涉及 SPATIAL 索引的操作的锁定,下一个键锁定不适合支持 REPEATABLE READSERIALIZABLE 事务隔离级别。在多维数据中没有绝对排序概念,因此不清楚哪个是“下一个”键。

为了支持具有 SPATIAL 索引的表的隔离级别,InnoDB 使用谓词锁。SPATIAL 索引包含最小边界矩形(MBR)值,因此 InnoDB 通过在查询中使用的 MBR 值上设置谓词锁来强制对索引进行一致读取。其他事务无法插入或修改与查询条件匹配的行。

17.7.2 InnoDB 事务模型

原文:dev.mysql.com/doc/refman/8.0/en/innodb-transaction-model.html

17.7.2.1 事务隔离级别

17.7.2.2 自动提交、提交和回滚

17.7.2.3 一致性非锁定读取

17.7.2.4 锁定读取

InnoDB事务模型旨在将多版本数据库的最佳特性与传统的两阶段锁定相结合。InnoDB在行级别执行锁定,并默认以 Oracle 风格的非锁定一致性读取运行查询。InnoDB中的锁信息以节省空间的方式存储,因此不需要锁升级。通常,允许多个用户锁定InnoDB表中的每一行,或任意随机子集的行,而不会导致InnoDB内存耗尽。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html

17.7.2.1 事务隔离级别

事务隔离是数据库处理的基础之一。隔离是缩写 ACID 中的 I;隔离级别是在多个事务同时进行更改和执行查询时,微调性能和可靠性、一致性和结果可重现性之间平衡的设置。

InnoDB提供了 SQL:1992 标准描述的四种事务隔离级别:READ UNCOMMITTED, READ COMMITTED, REPEATABLE READSERIALIZABLEInnoDB的默认隔离级别是REPEATABLE READ

用户可以使用SET TRANSACTION语句更改单个会话或所有后续连接的隔离级别。要为所有连接设置服务器的默认隔离级别,请在命令行或选项文件中使用--transaction-isolation选项。有关隔离级别和级别设置语法的详细信息,请参见 Section 15.3.7, “SET TRANSACTION Statement”。

InnoDB使用不同的锁定策略支持这里描述的每个事务隔离级别。您可以使用默认的REPEATABLE READ级别强制执行高度一致性,用于关键数据的操作,其中 ACID 合规性很重要。或者您可以通过READ COMMITTED甚至READ UNCOMMITTED放宽一致性规则,用于诸如大量报告之类的情况,其中精确一致性和可重复结果不如最小化锁定开销重要。SERIALIZABLEREPEATABLE READ甚至更严格,主要用于专门情况,例如 XA 事务和用于解决并发和死锁问题。

以下列表描述了 MySQL 支持不同事务级别的方式。列表从最常用的级别到最不常用的级别。

  • REPEATABLE READ

    这是InnoDB的默认隔离级别。在同一事务中进行一致性读取时,读取的是第一次读取时建立的快照。这意味着如果您在同一事务中发出几个普通(非锁定)SELECT语句,这些SELECT语句在彼此之间也是一致的。请参阅 Section 17.7.2.3, “Consistent Nonlocking Reads”。

    对于锁定读取(带有FOR UPDATEFOR SHARESELECT)、UPDATEDELETE语句,锁定取决于语句是否使用具有唯一搜索条件的唯一索引,或范围类型的搜索条件。

    • 对于具有唯一搜索条件的唯一索引,InnoDB仅锁定找到的索引记录,而不是其前面的间隙。

    • 对于其他搜索条件,InnoDB锁定扫描的索引范围,使用间隙锁或下一个键锁来阻止其他会话在范围覆盖的间隙中插入。有关间隙锁和下一个键锁的信息,请参阅 Section 17.7.1, “InnoDB Locking”。

  • READ COMMITTED

    每个一致性读取,即使在同一事务中,也会设置并读取自己的新快照。有关一致性读取的信息,请参阅 Section 17.7.2.3, “Consistent Nonlocking Reads”。

    对于锁定读取(带有FOR UPDATEFOR SHARESELECT)、UPDATEDELETE语句,InnoDB仅锁定索引记录,而不是它们之前的间隙,因此允许在锁定记录旁边自由插入新记录。间隙锁仅用于外键约束检查和重复键检查。

    由于间隙锁定已禁用,可能会出现幻影行问题,因为其他会话可以在间隙中插入新行。有关幻影行的信息,请参阅 Section 17.7.4, “Phantom Rows”。

    仅支持基于行的二进制日志记录与READ COMMITTED隔离级别。如果您在binlog_format=MIXED下使用READ COMMITTED,服务器会自动使用基于行的日志记录。

    使用READ COMMITTED还有额外的影响:

    • 对于UPDATEDELETE语句,InnoDB仅为更新或删除的行保持锁定。对于不匹配的行,MySQL 在评估WHERE条件后释放记录锁。这极大地降低了死锁的概率,但仍然可能发生。

    • 对于UPDATE语句,如果一行已被锁定,InnoDB执行“半一致性”读取,将最新提交版本返回给 MySQL,以便 MySQL 确定该行是否与UPDATEWHERE条件匹配。如果行匹配(必须更新),MySQL 再次读取该行,这时InnoDB要么锁定它,要么等待锁定。

    考虑以下示例,从这个表开始:

    CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB;
    INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2);
    COMMIT;
    

    在这种情况下,表没有索引,因此搜索和索引扫描使用隐藏的聚簇索引进行记录锁定(参见第 17.6.2.1 节,“聚簇索引和辅助索引”),而不是索引列。

    假设一个会话执行以下语句进行UPDATE

    # Session A
    START TRANSACTION;
    UPDATE t SET b = 5 WHERE b = 3;
    

    还假设第二个会话执行以下语句来执行UPDATE,紧随第一个会话之后:

    # Session B
    UPDATE t SET b = 4 WHERE b = 2;
    

    InnoDB执行每个UPDATE时,首先为每一行获取独占锁,然后确定是否修改它。如果InnoDB不修改该行,则释放锁。否则,InnoDB将保留该锁直到事务结束。这会影响事务处理如下。

    当使用默认的REPEATABLE READ隔离级别时,第一个UPDATE在读取每一行时获取 x 锁,并且不释放任何一个:

    x-lock(1,2); retain x-lock
    x-lock(2,3); update(2,3) to (2,5); retain x-lock
    x-lock(3,2); retain x-lock
    x-lock(4,3); update(4,3) to (4,5); retain x-lock
    x-lock(5,2); retain x-lock
    

    第二个UPDATE在尝试获取任何锁定时立即阻塞(因为第一个更新已保留了所有行的锁定),并且在第一个UPDATE提交或回滚之前不会继续进行:

    x-lock(1,2); block and wait for first UPDATE to commit or roll back
    

    如果改用READ COMMITTED,第一个UPDATE在读取每一行时获取 x 锁,并释放那些它不修改的行:

    x-lock(1,2); unlock(1,2)
    x-lock(2,3); update(2,3) to (2,5); retain x-lock
    x-lock(3,2); unlock(3,2)
    x-lock(4,3); update(4,3) to (4,5); retain x-lock
    x-lock(5,2); unlock(5,2)
    

    对于第二个UPDATEInnoDB进行了“半一致性”读取,将其读取的每一行的最新提交版本返回给 MySQL,以便 MySQL 确定该行是否与UPDATEWHERE条件匹配:

    x-lock(1,2); update(1,2) to (1,4); retain x-lock
    x-lock(2,3); unlock(2,3)
    x-lock(3,2); update(3,2) to (3,4); retain x-lock
    x-lock(4,3); unlock(4,3)
    x-lock(5,2); update(5,2) to (5,4); retain x-lock
    

    然而,如果WHERE条件包括一个索引列,并且InnoDB使用了该索引,那么在获取和保留记录锁时只考虑索引列。在下面的例子中,第一个UPDATE在 b = 2 的每一行上获取并保留 x 锁。当第二个UPDATE尝试在相同记录上获取 x 锁时,由于它也使用了在列 b 上定义的索引,它会被阻塞。

    CREATE TABLE t (a INT NOT NULL, b INT, c INT, INDEX (b)) ENGINE = InnoDB;
    INSERT INTO t VALUES (1,2,3),(2,2,4);
    COMMIT;
    
    # Session A
    START TRANSACTION;
    UPDATE t SET b = 3 WHERE b = 2 AND c = 3;
    
    # Session B
    UPDATE t SET b = 4 WHERE b = 2 AND c = 4;
    

    READ COMMITTED隔离级别可以在启动时设置或在运行时更改。在运行时,它可以全局设置给所有会话,或者针对每个会话单独设置。

  • READ UNCOMMITTED

    SELECT语句以非锁定方式执行,但可能使用较早版本的行。因此,在这个隔离级别下,这样的读取是不一致的。这也被称为脏读。否则,这个隔离级别的工作方式类似于READ COMMITTED

  • SERIALIZABLE

    这个级别类似于REPEATABLE READ,但如果autocommit被禁用,InnoDB会隐式地将所有普通的SELECT语句转换为SELECT ... FOR SHARE。如果autocommit被启用,SELECT就是它自己的事务。因此,它被认为是只读的,如果作为一致的(非锁定的)读取执行,可以进行序列化,不需要为其他事务阻塞。(要强制普通的SELECT在其他事务修改了所选行时阻塞,禁用autocommit。)

    注意

    从 MySQL 8.0.22 开始,从 MySQL 授权表中读取数据的 DML 操作(通过连接列表或子查询)但不修改它们的操作不会获取 MySQL 授权表的读锁,无论隔离级别如何。更多信息,请参阅授权表并发性。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-autocommit-commit-rollback.html

17.7.2.2 自动提交、提交和回滚

InnoDB中,所有用户活动都发生在一个事务内。如果启用了autocommit模式,每个 SQL 语句都形成一个独立的事务。默认情况下,MySQL 会在每个新连接的会话中启用autocommit,因此如果该语句没有返回错误,MySQL 会在每个 SQL 语句后执行提交。如果语句返回错误,则提交或回滚行为取决于错误。参见第 17.21.5 节,“InnoDB 错误处理”。

通过使用显式的START TRANSACTIONBEGIN语句开始,以及使用COMMITROLLBACK语句结束,启用了autocommit的会话可以执行多语句事务。参见第 15.3.1 节,“START TRANSACTION, COMMIT 和 ROLLBACK 语句”。

如果在使用SET autocommit = 0禁用了autocommit模式的会话中,该会话始终保持一个事务处于打开状态。COMMITROLLBACK语句结束当前事务并启动一个新事务。

如果禁用了autocommit的会话在没有显式提交最终事务的情况下结束,MySQL 会回滚该事务。

一些语句会隐式结束一个事务,就好像在执行该语句之前执行了COMMIT。详情请参见第 15.3.3 节,“导致隐式提交的语句”。

一个COMMIT表示当前事务中所做的更改已经永久生效,并对其他会话可见。另一方面,ROLLBACK语句会取消当前事务所做的所有修改。COMMITROLLBACK都会释放在当前事务期间设置的所有InnoDB锁。

使用事务对 DML 操作进行分组

默认情况下,连接到 MySQL 服务器时会启用自动提交模式,这会在执行每个 SQL 语句时自动提交。如果您有其他数据库系统的经验,在那些系统中,通常会发出一系列 DML 语句并一起提交或回滚,这种操作模式可能会让您感到陌生。

要使用多语句事务,请使用 SQL 语句SET autocommit = 0关闭自动提交,并在每个事务结束时使用COMMITROLLBACK。要保持自动提交状态,请在每个事务开始时使用START TRANSACTION,并在结束时使用COMMITROLLBACK。以下示例展示了两个事务。第一个被提交;第二个被回滚。

$> mysql test
mysql> CREATE TABLE customer (a INT, b CHAR (20), INDEX (a));
Query OK, 0 rows affected (0.00 sec)
mysql> -- Do a transaction with autocommit turned on.
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO customer VALUES (10, 'Heikki');
Query OK, 1 row affected (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
mysql> -- Do another transaction with autocommit turned off.
mysql> SET autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO customer VALUES (15, 'John');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO customer VALUES (20, 'Paul');
Query OK, 1 row affected (0.00 sec)
mysql> DELETE FROM customer WHERE b = 'Heikki';
Query OK, 1 row affected (0.00 sec)
mysql> -- Now we undo those last 2 inserts and the delete.
mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM customer;
+------+--------+
| a    | b      |
+------+--------+
|   10 | Heikki |
+------+--------+
1 row in set (0.00 sec)
mysql>
客户端语言中的事务

在诸如 PHP、Perl DBI、JDBC、ODBC 或 MySQL 的标准 C 调用接口等 API 中,您可以像发送其他 SQL 语句(如SELECTINSERT)一样,将事务控制语句(如COMMIT)作为字符串发送到 MySQL 服务器。一些 API 还提供单独的特殊事务提交和回滚函数或方法。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html

17.7.2.3 一致性非锁定读取

一致性读取意味着InnoDB使用多版本控制向查询展示数据库在某个时间点的快照。查询看到在该时间点之前提交的事务所做的更改,而不会看到稍后或未提交事务所做的更改。这个规则的例外是查询看到同一事务中较早语句所做的更改。这个例外导致以下异常:如果您更新表中的某些行,SELECT会看到更新行的最新版本,但也可能看到任何行的旧版本。如果其他会话同时更新同一表,这种异常意味着您可能看到数据库中从未存在的表状态。

如果事务的隔离级别是REPEATABLE READ(默认级别),同一事务内的所有一致性读取都读取该事务中第一个这样的读取建立的快照。您可以通过提交当前事务,然后发出新查询来为您的查询获取更新的快照。

使用READ COMMITTED隔离级别时,事务内的每个一致性读取都会设置并读取自己的新快照。

一致性读取是InnoDBREAD COMMITTEDREPEATABLE READ隔离级别下处理SELECT语句的默认模式。一致性读取不会在访问的表上设置任何锁定,因此其他会话可以在执行一致性读取的同时自由修改这些表。

假设您正在使用默认的REPEATABLE READ隔离级别。当您发出一致性读取(即普通的SELECT语句)时,InnoDB根据您的查询看到数据库的时间点为您的事务分配一个时间点。如果另一个事务删除一行并在分配您的时间点后提交,则您不会看到该行已被删除。插入和更新的处理方式类似。

注意

数据库状态的快照适用于事务内的SELECT语句,而不一定适用于 DML 语句。如果你插入或修改了一些行然后提交该事务,另一个并发的REPEATABLE READ事务发出的DELETEUPDATE语句可能会影响那些刚刚提交的行,尽管该会话无法查询它们。如果一个事务更新或删除了另一个事务提交的行,那些更改将对当前事务可见。例如,你可能会遇到以下情况:

SELECT COUNT(c1) FROM t1 WHERE c1 = 'xyz';
-- Returns 0: no rows match.
DELETE FROM t1 WHERE c1 = 'xyz';
-- Deletes several rows recently committed by other transaction.

SELECT COUNT(c2) FROM t1 WHERE c2 = 'abc';
-- Returns 0: no rows match.
UPDATE t1 SET c2 = 'cba' WHERE c2 = 'abc';
-- Affects 10 rows: another txn just committed 10 rows with 'abc' values.
SELECT COUNT(c2) FROM t1 WHERE c2 = 'cba';
-- Returns 10: this txn can now see the rows it just updated.

你可以通过提交事务然后执行另一个SELECTSTART TRANSACTION WITH CONSISTENT SNAPSHOT来推进时间点。

这被称为多版本并发控制。

在下面的例子中,会话 A 只有在 B 提交插入并且 A 也提交后才能看到 B 插入的行,这样时间点就会超过 B 的提交。

 Session A              Session B

           SET autocommit=0;      SET autocommit=0;
time
|          SELECT * FROM t;
|          empty set
|                                 INSERT INTO t VALUES (1, 2);
|
v          SELECT * FROM t;
           empty set
                                  COMMIT;

           SELECT * FROM t;
           empty set

           COMMIT;

           SELECT * FROM t;
           ---------------------
 |    1    |    2    |
           ---------------------

如果你想查看数据库的“最新”状态,请使用READ COMMITTED隔离级别或者锁定读取。

SELECT * FROM t FOR SHARE;

使用READ COMMITTED隔离级别时,事务内的每个一致性读取都设置并读取自己的新快照。使用FOR SHARE时,会发生锁定读取:SELECT会阻塞,直到包含最新行的事务结束(参见第 17.7.2.4 节,“锁定读取”)。

一致性读取在某些 DDL 语句上不起作用:

  • 一致性读取在DROP TABLE上不起作用,因为 MySQL 无法使用已删除的表,而InnoDB会销毁该表。

  • 一致性读取在进行使原始表的临时副本并在构建临时副本时删除原始表的ALTER TABLE操作上不起作用。当在事务内重新发出一致性读取时,新表中的行不可见,因为这些行在事务的快照被拍摄时不存在。在这种情况下,事务会返回一个错误:ER_TABLE_DEF_CHANGED,“表定义已更改,请重试事务”。

对于像INSERT INTO ... SELECTUPDATE ... (SELECT)CREATE TABLE ... SELECT等子句中的选择,如果没有指定FOR UPDATEFOR SHARE,则读取类型会有所不同:

  • 默认情况下,InnoDB对这些语句使用更强的锁,并且SELECT部分的行为类似于READ COMMITTED,即每个一致性读取,即使在同一事务中,也会设置并读取自己的新快照。

  • 要在这种情况下执行非锁定读取,将事务的隔离级别设置为READ UNCOMMITTEDREAD COMMITTED,以避免对从所选表中读取的行设置锁。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html

17.7.2.4 锁定读取

如果您在同一事务中查询数据然后插入或更新相关数据,常规的 SELECT 语句不提供足够的保护。其他事务可以更新或删除您刚刚查询的相同行。InnoDB 支持两种提供额��安全性的 锁定读取 类型:

  • SELECT ... FOR SHARE

    对读取的任何行设置共享模式锁定。其他会话可以读取这些行,但在您的事务提交之前不能修改它们。如果这些行中的任何行已被另一个尚未提交的事务更改,您的查询将等待该事务结束,然后使用最新值。

    注意

    SELECT ... FOR SHARESELECT ... LOCK IN SHARE MODE 的替代品,但 LOCK IN SHARE MODE 仍然可用于向后兼容。这两个语句是等效的。然而,FOR SHARE 支持 OF *table_name*NOWAITSKIP LOCKED 选项。请参阅 Locking Read Concurrency with NOWAIT and SKIP LOCKED。

    在 MySQL 8.0.22 之前,SELECT ... FOR SHARE 需要 SELECT 权限和至少一个 DELETELOCK TABLESUPDATE 权限。从 MySQL 8.0.22 开始,只需要 SELECT 权限。

    从 MySQL 8.0.22 开始,SELECT ... FOR SHARE 语句不会在 MySQL 授权表上获取读取锁。有关更多信息,请参阅 Grant Table Concurrency。

  • SELECT ... FOR UPDATE

    对于搜索遇到的索引记录,锁定行和任何相关的索引条目,就像您为这些行发出 UPDATE 语句一样。其他事务被阻止更新这些行,执行 SELECT ... FOR SHARE,或在某些事务隔离级别下读取数据。一致性读取会忽略在读取视图中存在的记录上设置的任何锁定。(旧版本的记录不能被锁定;它们通过在记录的内存副本上应用 undo logs 来重建。)

    SELECT ... FOR UPDATE 需要 SELECT 权限和至少一个 DELETELOCK TABLESUPDATE 权限。

这些子句主要在处理树形结构或图形结构数据时非常有用,无论是在单个表中还是跨多个表中。您可以从一个地方遍历边缘或树枝到另一个地方,同时保留回来更改任何这些“指针”值的权利。

FOR SHAREFOR UPDATE查询设置的所有锁定在事务提交或回滚时释放。

注意

只有在禁用自动提交时(通过使用START TRANSACTION开始事务或将autocommit设置为 0)才能进行锁定读取。

外部语句中的锁定读取子句不会锁定嵌套子查询中表的行,除非在子查询中也指定了锁定读取子句。例如,以下语句不会锁定表t2中的行。

SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2) FOR UPDATE;

要锁定表t2中的行,请向子查询添加一个锁定读取子句:

SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2 FOR UPDATE) FOR UPDATE;
锁定读取示例

假设您想要向表child中插入新行,并确保子行在表parent中有父行。您的应用程序代码可以确保在这一系列操作中保持引用完整性。

首先,使用一致性读取查询表PARENT并验证父行是否存在。您可以安全地将子行插入到CHILD表中吗?不可以,因为在您的SELECTINSERT之间的某一时刻,其他会话可能会删除父行,而您却不知情。

为避免这种潜在问题,使用FOR SHARE执行SELECT

SELECT * FROM parent WHERE NAME = 'Jones' FOR SHARE;

FOR SHARE查询返回父记录'Jones'后,您可以安全地将子记录添加到CHILD表中并提交事务。任何试图在PARENT表中适用行中获取独占锁的事务都会等待,直到您完成,也就是说,直到所有表中的数据处于一致状态。

举个例子,考虑一个表CHILD_CODES中的整数计数器字段,用于为添加到CHILD表中的每个子记录分配唯一标识符。不要使用一致性读取或共享模式读取来读取计数器的当前值,因为数据库的两个用户可能会看到计数器的相同值,并且如果两个事务尝试向CHILD表中添加具有相同标识符的行,则会发生重复键错误。

在这里,FOR SHARE不是一个好的解决方案,因为如果两个用户同时读取计数器,其中至少一个在尝试更新计数器时会陷入死锁。

要实现对计数器的读取和递增操作,首先使用FOR UPDATE执行计数器的锁定读取,然后再递增计数器。例如:

SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;

SELECT ... FOR UPDATE读取最新可用数据,在读取的每一行上设置排他锁。因此,它设置了与搜索的 SQL UPDATE在行上设置的相同锁。

上述描述仅仅是演示了SELECT ... FOR UPDATE的工作方式的一个例子。在 MySQL 中,实际上可以仅通过一次访问表来完成生成唯一标识符的特定任务:

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();

SELECT语句仅仅检索标识符信息(特定于当前连接)。它不访问任何表。

使用NOWAITSKIP LOCKED进行锁定读取并发

如果一行被事务锁定,请求相同锁定行的SELECT ... FOR UPDATESELECT ... FOR SHARE事务必须等待阻塞事务释放行锁。这种行为防止了事务更新或删除被其他事务查询更新的行。然而,如果希望查询在请求的行被锁定时立即返回,或者接受将被锁定的行排除在结果集之外,则无需等待行锁被释放。

为了避免等待其他事务释放行锁,可以在SELECT ... FOR UPDATESELECT ... FOR SHARE锁定读取语句中使用NOWAITSKIP LOCKED选项。

  • NOWAIT

    使用NOWAIT的锁定读取永远不会等待获取行锁。查询立即执行,如果请求的行被锁定,则失败并返回错误。

  • SKIP LOCKED

    使用SKIP LOCKED的锁定读取永远不会等待获取行锁。查询立即执行,从结果集中移除被锁定的行。

    注意

    跳过被锁定行的查询返回数据的不一致视图。因此,SKIP LOCKED不适用于一般的事务工作。然而,当多个会话访问相同的类似队列的表时,可以用于避免锁争用。

NOWAITSKIP LOCKED仅适用于行级锁。

使用NOWAITSKIP LOCKED的语句对基于语句的复制是不安全的。

以下示例演示了NOWAITSKIP LOCKED。会话 1 启动一个事务,锁定单个记录的行。会话 2 尝试使用NOWAIT选项对相同记录进行锁定读取。由于请求的行被会话 1 锁定,锁定读取立即返回错误。在会话 3 中,使用SKIP LOCKED的锁定读取返回请求的行,除了被会话 1 锁定的行。

# Session 1:

mysql> CREATE TABLE t (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;

mysql> INSERT INTO t (i) VALUES(1),(2),(3);

mysql> START TRANSACTION;

mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE;
+---+
| i |
+---+
| 2 |
+---+

# Session 2:

mysql> START TRANSACTION;

mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE NOWAIT;
ERROR 3572 (HY000): Do not wait for lock.

# Session 3: 
mysql> START TRANSACTION;

mysql> SELECT * FROM t FOR UPDATE SKIP LOCKED;
+---+
| i |
+---+
| 1 |
| 3 |
+---+

17.7.3 InnoDB 中不同 SQL 语句设置的锁

原文:dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html

锁定读取、UPDATEDELETE 通常会在处理 SQL 语句时扫描的每个索引记录上设置记录锁。语句中是否有 WHERE 条件排除行并不重要。InnoDB 不记得确切的 WHERE 条件,只知道扫描了哪些索引范围。这些锁通常是下一个键锁,还会阻止插入到记录之前的“间隙”。但是,可以显式禁用间隙锁定,这会导致不使用下一个键锁。有关更多信息,请参见第 17.7.1 节,“InnoDB 锁定”。事务隔离级别也会影响设置的锁;请参见第 17.7.2.1 节,“事务隔离级别”。

如果在搜索中使用了辅助索引,并且要设置的索引记录锁是排他的,则 InnoDB 还会检索相应的聚簇索引记录并对其设置锁。

如果您的语句没有适合的索引,MySQL 必须扫描整个表来处理语句,那么表的每一行都会被锁定,从而阻止其他用户向表中插入数据。重要的是要创建良好的索引,以便您的查询不会扫描比必要更多的行。

InnoDB 设置特定类型的锁如下。

  • SELECT ... FROM 是一致的读取,读取数据库的快照并不设置锁,除非事务隔离级别设置为 SERIALIZABLE。对于 SERIALIZABLE 级别,搜索会在遇到的索引记录上设置共享的下一个键锁。然而,只有使用唯一索引锁定行以搜索唯一行的语句需要索引记录锁。

  • 使用唯一索引的SELECT ... FOR UPDATESELECT ... FOR SHARE语句会为扫描的行获取锁,并释放不符合结果集包含条件的行的锁(例如,如果它们不符合WHERE子句中给定的条件)。然而,在某些情况下,行可能不会立即解锁,因为在查询执行期间结果行与其原始来源之间的关系丢失。例如,在UNION中,从表中扫描(并锁定)的行可能会在评估它们是否符合结果集之前插入临时表中。在这种情况下,临时表中的行与原始表中的行之间的关系丢失,直到查询执行结束,后者的行才会解锁。

  • 对于锁定读取(带有FOR UPDATEFOR SHARESELECTUPDATEDELETE语句,所采取的锁取决于语句是否使用具有唯一搜索条件或范围类型搜索条件的唯一索引。

    • 对于具有唯一搜索条件的唯一索引,InnoDB仅锁定找到的索引记录,而不是其前面的间隙。

    • 对于其他搜索条件和非唯一索引,InnoDB会锁定扫描的索引范围,使用间隙锁或下一个键锁来阻止其他会话在范围覆盖的间隙中插入。有关间隙锁和下一个键锁的信息,请参见第 17.7.1 节,“InnoDB 锁定”。

  • 对于索引记录,搜索遇到的SELECT ... FOR UPDATE会阻止其他会话执行SELECT ... FOR SHARE或在某些事务隔离级别下读取。一致性读取会忽略读取视图中存在的记录上设置的任何锁。

  • UPDATE ... WHERE ...在搜索遇到的每个记录上设置一个独占的下一个键锁。然而,仅对使用唯一索引锁定行以搜索唯一行的语句需要索引记录锁。

  • UPDATE修改聚集索引记录时,会对受影响的次要索引记录采取隐式锁。在插入新的次要索引记录之前执行重复检查扫描时,UPDATE操作还会对受影响的次要索引记录采取共享锁,并在插入新的次要索引记录时也会采取共享锁。

  • DELETE FROM ... WHERE ...对搜索遇到的每条记录设置排他的 next-key 锁。然而,对于使用唯一索引锁定行以搜索唯一行的语句,只需要一个索引记录锁。

  • INSERT对插入的行设置排他锁。这个锁是一个索引记录锁,而不是一个 next-key 锁(也就是说,没有间隙锁),不会阻止其他会话在插入行之前插入到间隙中。

    在插入行之前,会设置一种称为插入意向间隙锁的间隙锁类型。这个锁表示插入的意图,以便多个插入相同索引间隙的事务不必等待彼此,如果它们不在间隙内的相同位置插入。假设存在值为 4 和 7 的索引记录。尝试插入值为 5 和 6 的单独事务在获得插入行的排他锁之前,会在 4 和 7 之间的间隙上设置插入意向锁,但不会相互阻塞,因为这些行是不冲突的。

    如果发生重复键错误,则会设置对重复索引记录的共享锁。这种共享锁的使用可能导致死锁,如果有多个会话尝试插入相同的行,而另一个会话已经具有排他锁。如果另一个会话删除了该行,则可能会发生这种情况。假设一个InnoDBt1具有以下结构:

    CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
    

    现在假设三个会话按顺序执行以下操作:

    第 1 节:

    START TRANSACTION;
    INSERT INTO t1 VALUES(1);
    

    第 2 节:

    START TRANSACTION;
    INSERT INTO t1 VALUES(1);
    

    第 3 节:

    START TRANSACTION;
    INSERT INTO t1 VALUES(1);
    

    第 1 节:

    ROLLBACK;
    

    会话 1 的第一个操作会为该行获取排他锁。会话 2 和 3 的操作都导致重复键错误,并且它们都请求该行的共享锁。当会话 1 回滚时,它释放了该行的排他锁,并且为会话 2 和 3 排队的共享锁请求被授予。此时,会话 2 和 3 发生死锁:由于彼此持有的共享锁,它们都无法获取该行的排他锁。

    如果表中已经包含具有键值 1 的行,并且三个会话按顺序执行以下操作,则会发生类似的情况:

    第 1 节:

    START TRANSACTION;
    DELETE FROM t1 WHERE i = 1;
    

    第 2 节:

    START TRANSACTION;
    INSERT INTO t1 VALUES(1);
    

    第 3 节:

    START TRANSACTION;
    INSERT INTO t1 VALUES(1);
    

    第 1 节:

    COMMIT;
    

    会话 1 的第一个操作会为该行获取排他锁。会话 2 和 3 的操作都导致重复键错误,并且它们都请求该行的共享锁。当会话 1 提交时,它释放了该行的排他锁,并且为会话 2 和 3 排队的共享锁请求被授予。此时,会话 2 和 3 发生死锁:由于彼此持有的共享锁,它们都无法获取该行的排他锁。

  • INSERT ... ON DUPLICATE KEY UPDATE 与简单的 INSERT 不同,当发生重复键错误时,会在要更新的行上放置一个独占锁而不是共享锁。对于重复的主键值,会采取独占的索引记录锁。对于重复的唯一键值,会采取独占的 next-key 锁。

  • REPLACE 如果在唯一键上没有冲突,则类似于 INSERT。否则,会在要替换的行上放置一个独占的 next-key 锁。

  • INSERT INTO T SELECT ... FROM S WHERE ... 会在插入到 T 中的每一行上设置一个独占的索引记录锁(不包括间隙锁)。如果事务隔离级别是 READ COMMITTEDInnoDB 会将 S 上的搜索作为一致性读取(无锁)进行。否则,InnoDB 会在来自 S 的行上设置共享的 next-key 锁。在后一种情况下,InnoDB 必须设置锁:在使用基于语句的二进制日志进行前滚恢复时,必须以与最初执行时完全相同的方式执行每个 SQL 语句。

    CREATE TABLE ... SELECT ... 使用共享的 next-key 锁或一致性读取执行 SELECT,就像 INSERT ... SELECT 一样。

    当在构造 REPLACE INTO t SELECT ... FROM s WHERE ...UPDATE t ... WHERE col IN (SELECT ... FROM s ...) 中使用 SELECT 时,InnoDB 会在来自表 s 的行上设置共享的 next-key 锁。

  • 在初始化表上先前指定的 AUTO_INCREMENT 列时,InnoDB 会在与 AUTO_INCREMENT 列关联的索引末尾设置一个��占锁。

    使用 innodb_autoinc_lock_mode=0InnoDB 使用一种特殊的 AUTO-INC 表锁模式,在访问自增计数器时获得并保持锁直到当前 SQL 语句结束(而不是整个事务结束)。在持有 AUTO-INC 表锁时,其他客户端无法向表中插入数据。对于具有 innodb_autoinc_lock_mode=1 的“批量插入”,也会发生相同的行为。不使用表级 AUTO-INC 锁与 innodb_autoinc_lock_mode=2。更多信息,请参阅 第 17.6.1.6 节,“InnoDB 中的 AUTO_INCREMENT 处理”。

    InnoDB 在不设置任何锁的情况下获取先前初始化的 AUTO_INCREMENT 列的值。

  • 如果在表上定义了FOREIGN KEY约束,任何需要检查约束条件的插入、更新或删除都会在查看以检查约束的记录上设置共享记录级锁。如果约束失败,InnoDB也会设置这些锁。

  • LOCK TABLES设置表锁,但是在InnoDB层上方的更高 MySQL 层设置这些锁。如果innodb_table_locks = 1(默认)且autocommit = 0,那么InnoDB会意识到表锁,而在InnoDB上方的 MySQL 层知道行级锁。

    否则,InnoDB的自动死锁检测无法检测涉及此类表锁的死锁。此外,因为在这种情况下更高的 MySQL 层不知道行级锁,所以可能在另一个会话当前具有行级锁的表上获取表锁。然而,这并不会危及事务完整性,如第 17.7.5.2 节“死锁检测”中所讨论的。

  • 如果innodb_table_locks=1(默认),LOCK TABLES在每个表上获取两个锁。除了 MySQL 层上的表锁外,它还会获取一个InnoDB表锁。要避免获取InnoDB表锁,设置innodb_table_locks=0。如果没有获取InnoDB表锁,即使某些表的记录被其他事务锁定,LOCK TABLES也会完成。

    在 MySQL 8.0 中,innodb_table_locks=0对使用LOCK TABLES ... WRITE显式锁定的表没有影响。对于通过触发器隐式(例如,通过触发器)或通过LOCK TABLES ... READ读取或写入的表,它会产生影响。

  • 当事务提交或中止时,所有InnoDB锁都会被释放。因此,在autocommit=1模式下对InnoDB表调用LOCK TABLES并没有太多意义,因为获取的InnoDB表锁会立即释放。

  • 你不能在事务中间锁定额外的表,因为LOCK TABLES会执行隐式的COMMITUNLOCK TABLES

17.7.4 幻影行

原文:dev.mysql.com/doc/refman/8.0/en/innodb-next-key-locking.html

所谓的幻影问题发生在事务中,当同一查询在不同时间产生不同的行集时。例如,如果SELECT执行两次,但第二次返回了第一次没有返回的行,则该行是一个“幻影”行。

假设child表的id列上有一个索引,并且您希望从具有大于 100 的标识符值的表中读取并锁定所有行,以便稍后更新所选行中的某些列:

SELECT * FROM child WHERE id > 100 FOR UPDATE;

查询从id大于 100 的第一条记录开始扫描索引。假设表中包含id值为 90 和 102 的行。如果在扫描范围内设置的索引记录上的锁不锁定插入到间隙中的内容(在本例中,90 和 102 之间的间隙),则另一个会话可以在表中插入一个id为 101 的新行。如果在同一事务中执行相同的SELECT,则您会在查询返回的结果集中看到一个id为 101 的新行(“幻影”)。如果我们将一组行视为一个数据项,新的幻影子行将违反事务的隔离原则,即事务在执行期间读取的数据不应在事务期间发生更改。

为了防止幻影,InnoDB使用一种称为 next-key 锁定的算法,将索引行锁定与间隙锁定结合在一起。InnoDB以一种方式执行行级锁定,即当搜索或扫描表索引时,它会在遇到的索引记录上设置共享或排他锁。因此,行级锁实际上是索引记录锁。此外,对索引记录的 next-key 锁定也会影响索引记录之前的“间隙”。也就是说,next-key 锁定是索引记录锁加上索引记录之前的间隙锁。如果一个会话在索引中的记录R上有共享或排他锁,则另一个会话不能在索引顺序中的R之前的间隙中插入新的索引记录。

InnoDB扫描索引时,也可以锁定索引中最后一条记录之后的间隙。就像在前面的例子中发生的那样:为了防止在id大于 100 的表中插入任何内容,InnoDB设置的锁包括对id值为 102 之后的间隙的锁。

您可以使用 next-key 锁定在应用程序中实现唯一性检查:如果以共享模式读取数据,并且在要插入的行中没有看到重复项,则可以安全地插入行,并且知道在读取期间对您行的后继者设置的 next-key 锁定会阻止任何人同时插入一个重复项。因此,next-key 锁定使您能够在表中“锁定”某些内容的不存在。

可以按照第 17.7.1 节,“InnoDB 锁定”中讨论的方法禁用间隙锁定。这可能会导致幻影问题,因为在禁用间隙锁定时,其他会话可以在间隙中插入新行。

17.7.5 InnoDB 中的死锁

原文:dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks.html

17.7.5.1 InnoDB 死锁示例

17.7.5.2 死锁检测

17.7.5.3 如何最小化和处理死锁

死锁是指不同事务由于各自持有对方需要的锁而无法继续进行的情况。因为两个事务都在等待资源可用,所以它们都不会释放自己持有的锁。

当事务以相反的顺序锁定多个表中的行(通过诸如UPDATESELECT ... FOR UPDATE等语句)时,可能会发生死锁。当这些语句锁定索引记录和间隙的范围时,也可能发生死锁,因为每个事务由于时间问题而获取了一些锁,但没有获取其他锁。有关死锁示例,请参见第 17.7.5.1 节,“InnoDB 死锁示例”。

为减少死锁的可能性,请使用事务而不是LOCK TABLES语句;确保插入或更新数据的事务足够小,不会长时间保持打开状态;当不同事务更新多个表或大范围的行时,在每个事务中使用相同的操作顺序(如SELECT ... FOR UPDATE);在SELECT ... FOR UPDATEUPDATE ... WHERE语句中使用的列上创建索引。死锁的可能性不受隔离级别的影响,因为隔离级别改变了读操作的行为,而死锁是由写操作引起的。有关避免和恢复死锁条件的更多信息,请参见第 17.7.5.3 节,“如何最小化和处理死锁”。

当死锁检测被启用(默认情况下),并且确实发生了死锁时,InnoDB会检测到这种情况,并回滚其中一个事务(受害者)。如果使用innodb_deadlock_detect变量禁用了死锁检测,InnoDB会依赖于innodb_lock_wait_timeout设置来在发生死锁时回滚事务。因此,即使您的应用逻辑是正确的,您仍然必须处理事务必须重试的情况。要查看InnoDB用户事务中的最后一个死锁,请使用SHOW ENGINE INNODB STATUS。如果频繁的死锁突显出事务结构或应用程序错误处理的问题,请启用innodb_print_all_deadlocks以将所有死锁的信息打印到mysqld错误日志中。有关死锁如何自动检测和处理的更多信息,请参见 Section 17.7.5.2, “Deadlock Detection”。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-deadlock-example.html

17.7.5.1 InnoDB 死锁示例

以下示例说明了当锁请求导致死锁时会发生错误的情况。该示例涉及两个客户端,A 和 B。

InnoDB 状态包含最后一个死锁的详细信息。对于频繁发生的死锁,请启用全局变量innodb_print_all_deadlocks。这会将死锁信息添加到错误日志中。

客户端 A 启用innodb_print_all_deadlocks,创建两个表,'Animals' 和 'Birds',并向每个表插入数据。客户端 A 开始一个事务,并以共享模式选择 Animals 表中的一行:

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

mysql> CREATE TABLE Animals (name VARCHAR(10) PRIMARY KEY, value INT) ENGINE = InnoDB;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE Birds (name VARCHAR(10) PRIMARY KEY, value INT) ENGINE = InnoDB;
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO Animals (name,value) VALUES ("Aardvark",10);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO Birds (name,value) VALUES ("Buzzard",20);
Query OK, 1 row affected (0.00 sec)

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

mysql> SELECT value FROM Animals WHERE name='Aardvark' FOR SHARE;
+-------+
| value |
+-------+
|    10 |
+-------+
1 row in set (0.00 sec)

接下来,客户端 B 开始一个事务,并以共享模式选择 Birds 表中的一行:

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

mysql> SELECT value FROM Birds WHERE name='Buzzard' FOR SHARE;
+-------+
| value |
+-------+
|    20 |
+-------+
1 row in set (0.00 sec)

性能模式显示了两个 select 语句后的锁定情况:

mysql> SELECT ENGINE_TRANSACTION_ID as Trx_Id, 
              OBJECT_NAME as `Table`, 
              INDEX_NAME as `Index`, 
              LOCK_DATA as Data, 
              LOCK_MODE as Mode, 
              LOCK_STATUS as Status, 
              LOCK_TYPE as Type 
        FROM performance_schema.data_locks;
+-----------------+---------+---------+------------+---------------+---------+--------+
| Trx_Id          | Table   | Index   | Data       | Mode          | Status  | Type   |
+-----------------+---------+---------+------------+---------------+---------+--------+
| 421291106147544 | Animals | NULL    | NULL       | IS            | GRANTED | TABLE  |
| 421291106147544 | Animals | PRIMARY | 'Aardvark' | S,REC_NOT_GAP | GRANTED | RECORD |
| 421291106148352 | Birds   | NULL    | NULL       | IS            | GRANTED | TABLE  |
| 421291106148352 | Birds   | PRIMARY | 'Buzzard'  | S,REC_NOT_GAP | GRANTED | RECORD |
+-----------------+---------+---------+------------+---------------+---------+--------+
4 rows in set (0.00 sec)

然后客户端 B 更新 Animals 表中的一行:

mysql> UPDATE Animals SET value=30 WHERE name='Aardvark';

客户端 B 必须等待。性能模式显示了等待锁的情况:

mysql> SELECT REQUESTING_ENGINE_LOCK_ID as Req_Lock_Id,
              REQUESTING_ENGINE_TRANSACTION_ID as Req_Trx_Id,
              BLOCKING_ENGINE_LOCK_ID as Blk_Lock_Id, 
              BLOCKING_ENGINE_TRANSACTION_ID as Blk_Trx_Id
        FROM performance_schema.data_lock_waits;
+----------------------------------------+------------+----------------------------------------+-----------------+
| Req_Lock_Id                            | Req_Trx_Id | Blk_Lock_Id                            | Blk_Trx_Id      |
+----------------------------------------+------------+----------------------------------------+-----------------+
| 139816129437696:27:4:2:139816016601240 |      43260 | 139816129436888:27:4:2:139816016594720 | 421291106147544 |
+----------------------------------------+------------+----------------------------------------+-----------------+
1 row in set (0.00 sec)

mysql> SELECT ENGINE_LOCK_ID as Lock_Id, 
              ENGINE_TRANSACTION_ID as Trx_id, 
              OBJECT_NAME as `Table`, 
              INDEX_NAME as `Index`, 
              LOCK_DATA as Data, 
              LOCK_MODE as Mode, 
              LOCK_STATUS as Status, 
              LOCK_TYPE as Type 
        FROM performance_schema.data_locks;
+----------------------------------------+-----------------+---------+---------+------------+---------------+---------+--------+
| Lock_Id                                | Trx_Id          | Table   | Index   | Data       | Mode          | Status  | Type   |
+----------------------------------------+-----------------+---------+---------+------------+---------------+---------+--------+
| 139816129437696:1187:139816016603896   |           43260 | Animals | NULL    | NULL       | IX            | GRANTED | TABLE  |
| 139816129437696:1188:139816016603808   |           43260 | Birds   | NULL    | NULL       | IS            | GRANTED | TABLE  |
| 139816129437696:28:4:2:139816016600896 |           43260 | Birds   | PRIMARY | 'Buzzard'  | S,REC_NOT_GAP | GRANTED | RECORD |
| 139816129437696:27:4:2:139816016601240 |           43260 | Animals | PRIMARY | 'Aardvark' | X,REC_NOT_GAP | WAITING | RECORD |
| 139816129436888:1187:139816016597712   | 421291106147544 | Animals | NULL    | NULL       | IS            | GRANTED | TABLE  |
| 139816129436888:27:4:2:139816016594720 | 421291106147544 | Animals | PRIMARY | 'Aardvark' | S,REC_NOT_GAP | GRANTED | RECORD |
+----------------------------------------+-----------------+---------+---------+------------+---------------+---------+--------+
6 rows in set (0.00 sec)

当事务尝试修改数据库时,InnoDB 仅使用顺序事务 id。因此,之前的只读事务 id 从 421291106148352 变为 43260。

如果客户端 A 同时尝试更新 Birds 表中的一行,这将导致死锁:

mysql> UPDATE Birds SET value=40 WHERE name='Buzzard';
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

InnoDB 回滚导致死锁的事务。现在,来自客户端 B 的第一个更新可以继续进行。

信息模式包含死锁数量:

mysql> SELECT `count` FROM INFORMATION_SCHEMA.INNODB_METRICS
          WHERE NAME="lock_deadlocks";
+-------+
| count |
+-------+
|     1 |
+-------+
1 row in set (0.00 sec)

InnoDB 状态包含有关死锁和事务的以下信息。它还显示只读事务 id 421291106147544 变为顺序事务 id 43261。

mysql> SHOW ENGINE INNODB STATUS;
------------------------
LATEST DETECTED DEADLOCK
------------------------
2022-11-25 15:58:22 139815661168384
*** (1) TRANSACTION:
TRANSACTION 43260, ACTIVE 186 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1128, 2 row lock(s)
MySQL thread id 19, OS thread handle 139815619204864, query id 143 localhost u2 updating
UPDATE Animals SET value=30 WHERE name='Aardvark'

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 28 page no 4 n bits 72 index PRIMARY of table `test`.`Birds` trx id 43260 lock mode S locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 7; hex 42757a7a617264; asc Buzzard;;
 1: len 6; hex 00000000a8fb; asc       ;;
 2: len 7; hex 82000000e40110; asc        ;;
 3: len 4; hex 80000014; asc     ;;

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 27 page no 4 n bits 72 index PRIMARY of table `test`.`Animals` trx id 43260 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 8; hex 416172647661726b; asc Aardvark;;
 1: len 6; hex 00000000a8f9; asc       ;;
 2: len 7; hex 82000000e20110; asc        ;;
 3: len 4; hex 8000000a; asc     ;;

*** (2) TRANSACTION:
TRANSACTION 43261, ACTIVE 209 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1128, 2 row lock(s)
MySQL thread id 18, OS thread handle 139815618148096, query id 146 localhost u1 updating
UPDATE Birds SET value=40 WHERE name='Buzzard'

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 27 page no 4 n bits 72 index PRIMARY of table `test`.`Animals` trx id 43261 lock mode S locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 8; hex 416172647661726b; asc Aardvark;;
 1: len 6; hex 00000000a8f9; asc       ;;
 2: len 7; hex 82000000e20110; asc        ;;
 3: len 4; hex 8000000a; asc     ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 28 page no 4 n bits 72 index PRIMARY of table `test`.`Birds` trx id 43261 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 7; hex 42757a7a617264; asc Buzzard;;
 1: len 6; hex 00000000a8fb; asc       ;;
 2: len 7; hex 82000000e40110; asc        ;;
 3: len 4; hex 80000014; asc     ;;

*** WE ROLL BACK TRANSACTION (2)
------------
TRANSACTIONS
------------
Trx id counter 43262
Purge done for trx's n:o < 43256 undo n:o < 0 state: running but idle
History list length 0
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421291106147544, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 421291106146736, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 421291106145928, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 43260, ACTIVE 219 sec
4 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1
MySQL thread id 19, OS thread handle 139815619204864, query id 143 localhost u2

错误日志包含有关事务和锁的信息:

mysql> SELECT @@log_error;
+---------------------+
| @@log_error         |
+---------------------+
| /var/log/mysqld.log |
+---------------------+
1 row in set (0.00 sec)

TRANSACTION 43260, ACTIVE 186 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1128, 2 row lock(s)
MySQL thread id 19, OS thread handle 139815619204864, query id 143 localhost u2 updating
UPDATE Animals SET value=30 WHERE name='Aardvark'
RECORD LOCKS space id 28 page no 4 n bits 72 index PRIMARY of table `test`.`Birds` trx id 43260 lock mode S locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 7; hex 42757a7a617264; asc Buzzard;;
 1: len 6; hex 00000000a8fb; asc       ;;
 2: len 7; hex 82000000e40110; asc        ;;
 3: len 4; hex 80000014; asc     ;;

RECORD LOCKS space id 27 page no 4 n bits 72 index PRIMARY of table `test`.`Animals` trx id 43260 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 8; hex 416172647661726b; asc Aardvark;;
 1: len 6; hex 00000000a8f9; asc       ;;
 2: len 7; hex 82000000e20110; asc        ;;
 3: len 4; hex 8000000a; asc     ;;

TRANSACTION 43261, ACTIVE 209 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1128, 2 row lock(s)
MySQL thread id 18, OS thread handle 139815618148096, query id 146 localhost u1 updating
UPDATE Birds SET value=40 WHERE name='Buzzard'
RECORD LOCKS space id 27 page no 4 n bits 72 index PRIMARY of table `test`.`Animals` trx id 43261 lock mode S locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 8; hex 416172647661726b; asc Aardvark;;
 1: len 6; hex 00000000a8f9; asc       ;;
 2: len 7; hex 82000000e20110; asc        ;;
 3: len 4; hex 8000000a; asc     ;;

RECORD LOCKS space id 28 page no 4 n bits 72 index PRIMARY of table `test`.`Birds` trx id 43261 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 7; hex 42757a7a617264; asc Buzzard;;
 1: len 6; hex 00000000a8fb; asc       ;;
 2: len 7; hex 82000000e40110; asc        ;;
 3: len 4; hex 80000014; asc     ;;

原文:dev.mysql.com/doc/refman/8.0/en/innodb-deadlock-detection.html

17.7.5.2 死锁检测

当启用死锁检测(默认情况下),InnoDB会自动检测事务死锁并回滚一个或多个事务以打破死锁。InnoDB尝试选择要回滚的小事务,事务的大小由插入、更新或删除的行数确定。

innodb_table_locks = 1(默认)且autocommit = 0时,InnoDB会意识到表锁,并且 MySQL 层知道行级锁。否则,InnoDB无法检测到由 MySQL LOCK TABLES语句设置的表锁或由InnoDB之外的存储引擎设置的锁所涉及的死锁。通过设置innodb_lock_wait_timeout系统变量的值来解决这些情况。

如果InnoDB监视器输出的LATEST DETECTED DEADLOCK部分包含一条消息,指出在锁表等待图中搜索过深或过长,我们将回滚以下事务,则表示等待列表上的事务数量已达到 200 的限制。超过 200 个事务的等待列表被视为死锁,并且试图检查等待列表的事务将被回滚。如果锁定线程必须查看等待列表上的超过 1,000,000 个事务拥有的锁,则也可能发生相同的错误。

有关组织数据库操作以避免死锁的技术,请参阅第 17.7.5 节,“InnoDB 中的死锁”。

禁用死锁检测

在高并发系统中,死锁检测可能会导致大量线程等待同一锁时出现减速。有时,禁用死锁检测并依赖于innodb_lock_wait_timeout设置在死锁发生时进行事务回滚可能更有效。可以使用innodb_deadlock_detect变量禁用死锁检测。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks-handling.html

17.7.5.3 如何最小化和处理死锁

本节建立在关于死锁的概念信息之上,详见第 17.7.5.2 节,“死锁检测”。它解释了如何组织数据库操作以最小化死锁,并说明了应用程序中所需的后续错误处理。

死锁是事务性数据库中的经典问题,但除非它们频繁到您根本无法运行某些事务,否则它们并不危险。通常,您必须编写应用程序,以便它们始终准备好在由于死锁而回滚事务时重新发出该事务。

InnoDB使用自动的行级锁定。即使是仅插入或删除单行的事务,也可能发生死锁。这是因为这些操作并不真正“原子化”;它们会自动在插入或删除的行的(可能是多个)索引记录上设置锁。

您可以通过以下技术来应对死锁并减少其发生的可能性:

  • 随时使用SHOW ENGINE INNODB STATUS来确定最近死锁的原因。这可以帮助您调整应用程序以避免死锁。

  • 如果频繁的死锁警告引起关注,可以通过启用innodb_print_all_deadlocks变量来收集更广泛的调试信息。每个死锁的信息,而不仅仅是最新的那个,都记录在 MySQL 的错误日志中。调试完成后请禁用此选项。

  • 如果由于死锁而事务失败,请随时准备重新发出该事务。死锁并不危险。只需再试一次。

  • 保持事务小且持续时间短,以减少碰撞的可能性。

  • 在进行一组相关更改后立即提交事务,以减少碰撞的可能性。特别是,不要将一个交互式mysql会话保持打开很长时间且有未提交的事务。

  • 如果使用锁定读取(SELECT ... FOR UPDATESELECT ... FOR SHARE),请尝试使用较低的隔离级别,如READ COMMITTED

  • 在事务中修改多个表或同一表中的不同行集时,每次都以一致的顺序执行这些操作。然后事务形成明确定义的队列,不会发生死锁。例如,将数据库操作组织成应用程序中的函数,或调用存储过程,而不是在不同位置编写多个类似的INSERTUPDATEDELETE语句序列。

  • 为表添加精心选择的索引,以便查询扫描更少的索引记录并设置更少的锁。使用EXPLAIN SELECT来确定 MySQL 服务器认为哪些索引对您的查询最合适。

  • 减少锁定。如果您可以允许SELECT从旧快照返回数据,请不要为其添加FOR UPDATEFOR SHARE子句。在这里使用READ COMMITTED隔离级别是很好的,因为同一事务内的每个一致读取都从自己的新快照中读取。

  • 如果没有其他办法,使用表级锁串行化您的事务。在使用事务表(如InnoDB表)时,正确使用LOCK TABLES的方法是以SET autocommit = 0(而不是START TRANSACTION)开始事务,然后跟随LOCK TABLES,直到显式提交事务前不调用UNLOCK TABLES。例如,如果您需要写入表t1并从表t2读取,可以这样做:

    SET autocommit=0;
    LOCK TABLES t1 WRITE, t2 READ, ...;
    *... do something with tables t1 and t2 here ...* COMMIT;
    UNLOCK TABLES;
    

    表级锁可以防止对表的并发更新,在繁忙系统中避免死锁,但会降低系统的响应速度。

  • 另一种串行化事务的方法是创建一个仅包含单行的辅助“信号量”表。让每个事务在访问其他表之前更新该行。这样,所有事务都以串行方式发生。请注意,InnoDB即时死锁检测算法在这种情况下也适用,因为串行化锁是行级锁。对于 MySQL 表级锁,必须使用超时方法来解决死锁。

17.7.6 事务调度

原文:dev.mysql.com/doc/refman/8.0/en/innodb-transaction-scheduling.html

InnoDB使用 Contenion-Aware Transaction Scheduling (CATS)算法来优先处理等待锁的事务。当多个事务等待同一对象上的锁时,CATS 算法确定哪个事务首先获得锁。

CATS 算法通过分配调度权重来优先处理等待事务,该权重是基于事务阻塞的事务数量计算的。例如,如果两个事务正在等待同一对象上的锁,那么阻塞最多事务的事务将被分配更大的调度权重。如果权重相等,则优先考虑等待时间最长的事务。

注意

在 MySQL 8.0.20 之前,InnoDB还使用先进先出(FIFO)算法来调度事务,并且 CATS 算法仅在锁争用严重时使用。MySQL 8.0.20 中的 CATS 算法增强使 FIFO 算法变得多余,允许其移除。MySQL 8.0.20 之后,由 FIFO 算法执行的事务调度由 CATS 算法执行。在某些情况下,此更改可能会影响事务获得锁的顺序。

您可以通过查询信息模式INNODB_TRX表中的TRX_SCHEDULE_WEIGHT列来查看事务调度权重。权重仅针对等待事务计算。等待事务是指处于LOCK WAIT事务执行状态的事务,如TRX_STATE列所报告的那样。不等待锁的事务报告 NULL 的TRX_SCHEDULE_WEIGHT值。

INNODB_METRICS提供了用于监视代码级事务调度事件的计数器。有关使用INNODB_METRICS计数器的信息,请参见第 17.15.6 节,“InnoDB INFORMATION_SCHEMA Metrics Table”。

  • lock_rec_release_attempts

    释放记录锁的尝试次数。一次尝试可能导致释放零个或多个记录锁,因为单个结构中可能存在零个或多个记录锁。

  • lock_rec_grant_attempts

    尝试授予记录锁的次数。一次尝试可能导致授予零个或多个记录锁。

  • lock_schedule_refreshes

    分析等待图以更新调度事务权重的次数。

17.8 InnoDB 配置

原文:dev.mysql.com/doc/refman/8.0/en/innodb-configuration.html

17.8.1 InnoDB 启动配置

17.8.2 配置 InnoDB 为只读操作

17.8.3 InnoDB 缓冲池配置

17.8.4 配置 InnoDB 的线程并发性

17.8.5 配置后台 InnoDB I/O 线程数量

17.8.6 在 Linux 上使用异步 I/O

17.8.7 配置 InnoDB 的 I/O 容量

17.8.8 配置自旋锁轮询

17.8.9 清理配置

17.8.10 配置 InnoDB 的优化器统计信息

17.8.11 配置索引页的合并阈值

17.8.12 启用专用 MySQL 服务器的自动配置

本节提供了有关InnoDB初始化、启动以及各种组件和特性的配置信息和流程。有关优化InnoDB表的数据库操作的信息,请参阅 Section 10.5, “Optimizing for InnoDB Tables”。

17.8.1 InnoDB 启动配置

原文:dev.mysql.com/doc/refman/8.0/en/innodb-init-startup-configuration.html

关于 InnoDB 配置的首要决策涉及数据文件、日志文件、页面大小和内存缓冲区的配置,这些应在初始化 InnoDB 之前配置。在初始化 InnoDB 后修改配置可能涉及非平凡的程序。

本节提供了有关在配置文件中指定 InnoDB 设置、查看 InnoDB 初始化信息和重要存储考虑事项的信息。

  • 在 MySQL 选项文件中指定选项

  • 查看 InnoDB 初始化信息

  • 重要的存储考虑

  • 系统表空间数据文件配置

  • InnoDB 双写缓冲文件配置

  • 重做日志配置

  • 撤销表空间配置

  • 全局临时表空间配置

  • 会话临时表空间配置

  • 页面大小配置

  • 内存配置

在 MySQL 选项文件中指定选项

因为 MySQL 使用数据文件、日志文件和页面大小设置来初始化 InnoDB,建议您在 MySQL 在启动时读取的选项文件中定义这些设置,以便在初始化 InnoDB 之前。通常情况下,当 MySQL 服务器首次启动时会初始化 InnoDB

您可以将 InnoDB 选项放在服务器启动时读取的任何选项文件的 [mysqld] 组中。MySQL 选项文件的位置在 Section 6.2.2.2, “Using Option Files” 中有描述。

为了确保mysqld仅从特定文件(和mysqld-auto.cnf)中读取选项,请在启动服务器时将--defaults-file选项作为命令行中的第一个选项:

mysqld --defaults-file=*path_to_option_file*

查看 InnoDB 初始化信息

要查看启动期间的InnoDB初始化信息,请从命令提示符启动mysqld,这会将初始化信息打印到控制台。

例如,在 Windows 上,如果mysqld位于C:\Program Files\MySQL\MySQL Server 8.0\bin,可以这样启动 MySQL 服务器:

C:\> "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysqld" --console

在类 Unix 系统上,mysqld位于 MySQL 安装目录的bin目录中:

$> bin/mysqld --user=mysql &

如果您没有将服务器输出发送到控制台,请在启动后检查错误日志,查看在启动过程中打印的InnoDB初始化信息。

有关使用其他方法启动 MySQL 的信息,请参见 Section 2.9.5, “Starting and Stopping MySQL Automatically”。

注意

InnoDB在启动时不会打开所有用户表和相关数据文件。但是,InnoDB会检查数据字典中引用的表空间文件是否存在。如果找不到表空间文件,InnoDB会记录错误并继续启动序列。重做日志中引用的表空间文件可能在崩溃恢复期间打开以进行重做应用。

重要的存储考虑事项

在继续进行启动配置之前,请查看以下与存储相关的考虑事项。

  • 在某些情况下,通过将数据和日志文件放在不同的物理磁盘上,可以提高数据库性能。您还可以为InnoDB数据文件使用原始磁盘分区(原始设备),这可能加快 I/O 速度。请参阅 Using Raw Disk Partitions for the System Tablespace。

  • InnoDB是一个支持事务的(符合 ACID 标准)存储引擎,具有提交、回滚和崩溃恢复功能,以保护用户数据。但是,如果底层操作系统或硬件不按照宣传的方式工作,它就无法做到。许多操作系统或磁盘子系统可能会延迟或重新排序写操作以提高性能。在某些操作系统上,应该等待直到文件的所有未写入数据都已刷新的fsync()系统调用实际上可能会在数据刷新到稳定存储之前返回。由于这个原因,操作系统崩溃或停电可能会破坏最近提交的数据,或者在最坏的情况下,甚至损坏数据库,因为写操作已经被重新排序。如果数据完整性对您很重要,请在生产环境中使用任何内容之前执行“拔插头”测试。在 macOS 上,InnoDB使用一种特殊的fcntl()文件刷新方法。在 Linux 下,建议禁用写回缓存

    在 ATA/SATA 硬盘驱动器上,像hdparm -W0 /dev/hda这样的命令可能可以禁用写回缓存。请注意,一些驱动器或磁盘控制器可能无法禁用写回缓存。

  • 关于保护用户数据的InnoDB恢复能力,InnoDB使用一种涉及名为双写缓冲区的结构的文件刷新技术,默认情况下启用(innodb_doublewrite=ON)。双写缓冲区在意外退出或停电后的恢复过程中增加了安全性,并通过减少大多数 Unix 系统上fsync()操作的需求来提高性能。如果您关心数据完整性或可能的故障,请保持innodb_doublewrite选项启用。有关双写缓冲区的信息,请参阅第 17.11.1 节,“InnoDB 磁盘 I/O”。

  • 在使用InnoDB与 NFS 之前,请查看使用 NFS 与 MySQL 中概述的潜在问题。

系统表空间数据文件配置

innodb_data_file_path选项定义了InnoDB系统表空间数据文件的名称、大小和属性。如果在初始化 MySQL 服务器之前未配置此选项,则默认行为是创建一个稍大于 12MB 的单个自动扩展数据文件,名称为ibdata1

mysql> SHOW VARIABLES LIKE 'innodb_data_file_path';
+-----------------------+------------------------+
| Variable_name         | Value                  |
+-----------------------+------------------------+
| innodb_data_file_path | ibdata1:12M:autoextend |
+-----------------------+------------------------+

完整的数据文件规范语法包括文件名、文件大小、autoextend属性和max属性:

*file_name*:*file_size*[:autoextend[:max:*max_file_size*]]

文件大小以 KB、MB 或 GB 为单位指定,通过在大小值后附加KMG来实现。如果以 KB 为单位指定数据文件大小,请以 1024 的倍数进行。否则,KB 值将四舍五入到最近的兆字节(MB)边界。文件大小之和必须至少略大于 12MB。

您可以使用分号分隔的列表指定多个数据文件。例如:

[mysqld]
innodb_data_file_path=ibdata1:50M;ibdata2:50M:autoextend

autoextendmax属性只能用于指定的最后一个数据文件。

当指定autoextend属性时,数据文件会根据需要自动增加 64MB 的增量。innodb_autoextend_increment变量控制增量大小。

要为自动扩展数据文件指定最大大小,请在autoextend属性后使用max属性。只有在约束磁盘使用量至关重要的情况下才使用max属性。以下配置允许ibdata1增长到 500MB 的限制:

[mysqld]
innodb_data_file_path=ibdata1:12M:autoextend:max:500M

第一个系统表空间数据文件强制执行最小文件大小,以确保有足够的空间用于双写缓冲区页。以下表显示了每个InnoDB页大小的最小文件大小。默认的InnoDB页大小为 16384(16KB)。

页大小(innodb_page_size) 最小文件大小
16384 (16KB) 或更少 3MB
32768 (32KB) 6MB
65536 (64KB) 12MB

如果您的磁盘已满,可以在另一个磁盘上添加数据文件。有关说明,请参阅调整系统表空间大小。

个别文件的大小限制由您的操作系统确定。在支持大文件的操作系统上,您可以将文件大小设置为超过 4GB。您还可以将原始磁盘分区用作数据文件。请参阅使用原始磁盘分区作为系统表空间。

InnoDB 不了解文件系统的最大文件大小,因此在最大文件大小为 2GB 等较小值的文件系统上要小心。

默认情况下,系统表空间文件在数据目录中创建(datadir)。要指定替代位置,请使用innodb_data_home_dir选项。例如,要在名为myibdata的目录中创建系统表空间数据文件,请使用以下配置:

[mysqld]
innodb_data_home_dir = /myibdata/
innodb_data_file_path=ibdata1:50M:autoextend

在为innodb_data_home_dir指定值时,需要添加尾随斜杠。InnoDB不会创建目录,因此请确保在启动服务器之前指定的目录存在。还要确保 MySQL 服务器具有在目录中创建文件的适当访问权限。

InnoDB通过将innodb_data_home_dir的值与数据文件名文本连接来形成每个数据文件的目录路径。如果未定义innodb_data_home_dir,默认值为“./”,即数据目录。(当 MySQL 服务器开始执行时,它会将当前工作目录更改为数据目录。)

或者,您可以为系统表空间数据文件指定绝对路径。以下配置与前述配置等效:

[mysqld]
innodb_data_file_path=/myibdata/ibdata1:50M:autoextend

当您为innodb_data_file_path指定绝对路径时,设置不会与innodb_data_home_dir设置连接。系统表空间文件将在指定的绝对路径中创建。在启动服务器之前,指定的目录必须存在。

InnoDB 双写缓冲区文件配置

自 MySQL 8.0.20 起,双写缓冲区存储区域位于双写文件中,这提供了关于双写页存储位置的灵活性。在先前的版本中,双写缓冲区存储区域位于系统表空间中。innodb_doublewrite_dir变量定义了InnoDB在启动时创建双写文件的目录。如果未指定目录,则双写文件将在innodb_data_home_dir目录中创建,如果未指定,则默认为数据目录。

若要在innodb_data_home_dir目录之外的位置创建双写文件,请配置innodb_doublewrite_dir变量。例如:

innodb_doublewrite_dir=*/path/to/doublewrite_directory*

其他双写缓冲区变量允许定义双写文件数量、每个线程的页面数量以及双写批量大小。有关双写缓冲区配置的更多信息,请参阅第 17.6.4 节,“双写缓冲区”。

重做日志配置

从 MySQL 8.0.30 开始,重做日志文件占用的磁盘空间量由innodb_redo_log_capacity变量控制,该变量可以在启动时或运行时设置;例如,要在选项文件中将变量设置为 8GB,请添加以下条目:

[mysqld]
innodb_redo_log_capacity = 8589934592

有关在运行时配置重做日志容量的信息,请参阅配置重做日志容量(MySQL 8.0.30 或更高版本)。

innodb_redo_log_capacity变量取代了已弃用的innodb_log_file_sizeinnodb_log_files_in_group变量。当定义了innodb_redo_log_capacity设置时,将忽略innodb_log_file_sizeinnodb_log_files_in_group设置;否则,这些设置用于计算innodb_redo_log_capacity设置(innodb_log_files_in_group * innodb_log_file_size = innodb_redo_log_capacity)。如果没有设置这些变量中的任何一个,innodb_redo_log_capacity将设置为默认值,即 104857600 字节(100MB)。最大设置为 128GB。

从 MySQL 8.0.30 开始,InnoDB尝试维护 32 个重做日志文件,每个文件大小为 1/32 * innodb_redo_log_capacity。重做日志文件位于数据目录中的#innodb_redo目录中,除非通过innodb_log_group_home_dir变量指定了不同的目录。如果定义了innodb_log_group_home_dir,则重做日志文件位于该目录中的#innodb_redo目录中。有关更多信息,请参见第 17.6.5 节,“重做日志”。

在 MySQL 8.0.30 之前,InnoDB默认在数据目录中创建两个 5MB 的重做日志文件,分别命名为ib_logfile0ib_logfile1。您可以在初始化 MySQL 服务器实例时通过配置innodb_log_files_in_groupinnodb_log_file_size变量来定义不同数量和大小的重做日志文件。

  • innodb_log_files_in_group定义了日志组中日志文件的数量。默认值和推荐值为 2。

  • innodb_log_file_size定义了日志组中每个日志文件的大小(以字节为单位)。组合日志文件大小(innodb_log_file_size * innodb_log_files_in_group)不能超过最大值,该值略小于 512GB。例如,一对 255GB 的日志文件接近极限但不超过。默认的日志文件大小为 48MB。通常,日志文件的组合大小应足够大,以使服务器能够平滑处理工作负载活动中的高峰和低谷,这通常意味着有足够的重做日志空间来处理超过一个小时的写入活动。较大的日志文件大小意味着在缓冲池中减少检查点刷新活动,从而减少磁盘 I/O。有关更多信息,请参见第 10.5.4 节,“优化 InnoDB 重做日志记录”。

innodb_log_group_home_dir定义了InnoDB日志文件的目录路径。您可以使用此选项将InnoDB重做日志文件放置在与InnoDB数据文件不同的物理存储位置,以避免潜在的 I/O 资源冲突;例如:

[mysqld]
innodb_log_group_home_dir = /dr3/iblogs

注意

InnoDB不会创建目录,因此在启动服务器之前确保日志目录存在。使用 Unix 或 DOS 的mkdir命令创建任何必要的目录。

确保 MySQL 服务器具有在日志目录中创建文件的适当访问权限。更一般地,服务器必须在需要创建文件的任何目录中具有访问权限。

撤销表空间配置

默认情况下,撤销日志驻留在 MySQL 实例初始化时创建的两个撤销表空间中。

innodb_undo_directory变量定义了InnoDB创建默认撤销表空间的路径。如果该变量未定义,则默认的撤销表空间将在数据目录中创建。innodb_undo_directory变量不是动态的。配置它需要重新启动服务器。

撤销日志的 I/O 模式使撤销表空间成为 SSD 存储的良好选择。

有关配置额外撤销表空间的信息,请参见第 17.6.3.4 节,“撤销表空间”。

全局临时表空间配置

全局临时表空间存储对用户创建的临时表所做更改的回滚段。

默认情况下,在innodb_data_home_dir目录中有一个名为ibtmp1的单个自动扩展全局临时表空间数据文件。初始文件大小略大于 12MB。

innodb_temp_data_file_path 选项指定了全局临时表空间数据文件的路径、文件名和文件大小。文件大小通过在大小值后附加 K、M 或 G 来指定为 KB、MB 或 GB。文件大小或组合文件大小必须略大于 12MB。

要指定全局临时表空间数据文件的替代位置,请在启动时配置 innodb_temp_data_file_path 选项。

会话临时表空间配置

在 MySQL 8.0.15 及更早版本中,会话临时表空间存储用户创建的临时表和由优化器创建的内部临时表,当 InnoDB 被配置为内部临时表的磁盘存储引擎时(internal_tmp_disk_storage_engine=InnoDB)。从 MySQL 8.0.16 开始,InnoDB 总是被用作内部临时表的磁盘存储引擎。

innodb_temp_tablespaces_dir 变量定义了 InnoDB 创建会话临时表空间的位置。默认位置是数据目录中的 #innodb_temp 目录。

要指定会话临时表空间的替代位置,请在启动时配置 innodb_temp_tablespaces_dir 变量。允许使用完全限定路径或相对于数据目录的路径。

页面大小配置

innodb_page_size 选项指定了 MySQL 实例中所有 InnoDB 表空间的页面大小。此值在实例创建时设置,并在此后保持不变。有效值为 64KB、32KB、16KB(默认值)、8KB 和 4KB。另外,您也可以按字节指定页面大小(65536、32768、16384、8192、4096)。

默认的 16KB 页面大小适用于各种工作负载,特别是涉及表扫描和涉及大量更新的 DML 操作的查询。对于涉及许多小写入的 OLTP 工作负载,较小的页面大小可能更有效,当单个页面包含许多行时,争用可能是一个问题。较小的页面对于通常使用小块大小的 SSD 存储设备也可能更有效。保持 InnoDB 页面大小接近存储设备块大小可以最小化被重写到磁盘的未更改数据量。

重要提示

innodb_page_size 只能在初始化数据目录时设置。有关此变量的更多信息,请参阅其描述。

内存配置

MySQL 为各种缓存和缓冲区分配内存以提高数据库操作的性能。在为InnoDB分配内存时,始终考虑操作系统所需的内存、分配给其他应用程序的内存以及为其他 MySQL 缓冲区和缓存分配的内存。例如,如果您使用MyISAM表,请考虑为键缓冲区(key_buffer_size)分配的内存量。有关 MySQL 缓冲区和缓存的概述,请参阅第 10.12.3.1 节,“MySQL 如何使用内存”。

InnoDB特定的缓冲区使用以下参数进行配置:

  • innodb_buffer_pool_size 定义了缓冲池的大小,这是一个内存区域,用于保存InnoDB表、索引和其他辅助缓冲区的缓存数据。缓冲池的大小对系统性能很重要,通常建议将innodb_buffer_pool_size 配置为系统内存的 50%到 75%。默认的缓冲池大小为 128MB。有关更多指导,请参阅第 10.12.3.1 节,“MySQL 如何使用内存”。有关如何配置InnoDB缓冲池大小的信息,请参阅第 17.8.3.1 节,“配置 InnoDB 缓冲池大小”。缓冲池大小可以在启动时或动态配置。

    在具有大量内存的系统上,可以通过将缓冲池划分为多个缓冲池实例来提高并发性。缓冲池实例的数量由innodb_buffer_pool_instances 选项控制。默认情况下,InnoDB创建一个缓冲池实例。缓冲池实例的数量可以在启动时配置。有关更多信息,请参阅第 17.8.3.2 节,“配置多个缓冲池实例”。

  • innodb_log_buffer_size 定义了InnoDB用于向磁盘上的日志文件写入的缓冲区大小。默认大小为 16MB。较大的日志缓冲区使得大型事务在提交之前可以运行而不必将日志写入磁盘。如果您有更新、插入或删除许多行的事务,可以考虑增加日志缓冲区的大小以节省磁盘 I/O。innodb_log_buffer_size 可以在启动时配置。有关相关信息,请参阅第 10.5.4 节,“优化 InnoDB 重做日志记录”。

警告

在 32 位 GNU/Linux x86 上,如果内存使用量设置过高,glibc可能允许进程堆增长超过线程堆栈,导致服务器失败。如果为mysqld进程分配的全局和每个线程的缓冲区和缓存接近或超过 2GB,这是一个风险。

可以使用类似于以下计算 MySQL 全局和每个线程内存分配的公式来估算 MySQL 内存使用量。您可能需要修改公式以考虑您的 MySQL 版本和配置中的缓冲区和缓存。有关 MySQL 缓冲区和缓存的概述,请参见第 10.12.3.1 节,“MySQL 如何使用内存”。

innodb_buffer_pool_size
+ key_buffer_size
+ max_connections*(sort_buffer_size+read_buffer_size+binlog_cache_size)
+ max_connections*2MB

每个线程使用一个堆栈(通常为 2MB,但由 Oracle Corporation 提供的 MySQL 二进制文件中仅为 256KB),在最坏的情况下还会使用sort_buffer_size + read_buffer_size额外的内存。

在 Linux 上,如果内核启用了大页支持,InnoDB可以使用大页来为其缓冲池分配内存。参见第 10.12.3.3 节,“启用大页支持”。

17.8.2 配置 InnoDB 为只读操作

原文:dev.mysql.com/doc/refman/8.0/en/innodb-read-only-instance.html

通过在服务器启动时启用--innodb-read-only配置选项,可以查询InnoDB表,其中 MySQL 数据目录位于只读介质上。

如何启用

为了准备一个实例进行只读操作,在将其存储在只读介质上之前,确保所有必要的信息都被刷新到数据文件中。运行服务器时禁用更改缓冲(innodb_change_buffering=0),并进行慢关闭。

要为整个 MySQL 实例启用只读模式,请在服务器启动时指定以下配置选项:

  • --innodb-read-only=1

  • 如果实例在只读介质上,如 DVD 或 CD,或者/var目录不可被所有用户写入:--pid-file=*path_on_writeable_media*--event-scheduler=disabled

  • --innodb-temp-data-file-path。此选项指定了InnoDB临时表空间数据文件的路径、文件名和文件大小。默认设置为ibtmp1:12M:autoextend,这将在数据目录中创建ibtmp1临时表空间数据文件。为了准备一个实例进行只读操作,将innodb_temp_data_file_path设置为数据目录之外的位置。路径必须相对于数据目录。例如:

    --innodb-temp-data-file-path=../../../tmp/ibtmp1:12M:autoextend
    

从 MySQL 8.0 开始,启用innodb_read_only会阻止所有存储引擎的表创建和删除操作。这些操作修改了mysql系统数据库中的数据字典表,但这些表使用了InnoDB存储引擎,当启用innodb_read_only时无法修改。相同的限制也适用于任何修改数据字典表的操作,如ANALYZE TABLEALTER TABLE *tbl_name* ENGINE=*engine_name*

此外,MySQL 8.0 中的mysql系统数据库中的其他表使用InnoDB存储引擎。将这些表设置为只读会导致对修改它们的操作的限制。例如,在只读模式下不允许CREATE USERGRANTREVOKEINSTALL PLUGIN操作。

使用场景

这种操作模式适用于以下情况:

  • 在只读存储介质(如 DVD 或 CD)上分发 MySQL 应用程序或一组 MySQL 数据。

  • 多个同时查询相同数据目录的 MySQL 实例,通常在数据仓库配置中。您可以使用这种技术来避免在负载较重的 MySQL 实例中可能出现的瓶颈,或者您可以为各个实例使用不同的配置选项,为每个实例调整特定类型的查询。

  • 查询已被放入只读状态以确保安全性或数据完整性的数据,例如已存档的备份数据。

注意

此功能主要用于在分发和部署方面提供灵活性,而不是基于只读方面的原始性能。请参阅第 10.5.3 节,“优化 InnoDB 只读事务”以了解如何调整只读查询的性能,这不需要使整个服务器变为只读。

工作原理

当服务器通过--innodb-read-only选项以只读模式运行时,某些InnoDB功能和组件会减少或完全关闭:

  • 不进行更改缓冲,特别是不进行来自更改缓冲区的合并。为确保在准备实例进行只读操作时更改缓冲区为空,请禁用更改缓冲(innodb_change_buffering=0)并首先进行慢关闭。

  • 启动时没有崩溃恢复阶段。实例必须在被置于只读状态之前执行慢关闭。

  • 因为重做日志在只读操作中不被使用,您可以在将实例设置为只读之前将innodb_log_file_size设置为可能的最小大小(1 MB)。

  • 大多数后台线程被关闭。I/O 读线程保持开启,以及 I/O 写线程和用于写入临时文件的页面刷新协调器线程,这在只读模式下是允许的。一个缓冲池调整线程也保持活动状态,以便在线调整缓冲池大小。

  • 关于死锁、监视器输出等信息不会写入临时文件。因此,SHOW ENGINE INNODB STATUS不会产生任何输出。

  • 当服务器处于只读模式时,通常会更改写操作行为的配置选项设置更改不会产生任何效果。

  • 用于执行隔离级别的 MVCC 处理被关闭。所有查询读取记录的最新版本,因为更新和删除是不可能的。

  • 撤销日志未被使用。禁用innodb_undo_tablespacesinnodb_undo_directory配置选项的任何设置。

17.8.3 InnoDB 缓冲池配置

原文:dev.mysql.com/doc/refman/8.0/en/innodb-performance-buffer-pool.html

17.8.3.1 配置 InnoDB 缓冲池大小

17.8.3.2 配置多个缓冲池实例

17.8.3.3 使缓冲池具有扫描抵抗性

17.8.3.4 配置 InnoDB 缓冲池预取(预读)

17.8.3.5 配置缓冲池刷新

17.8.3.6 保存和恢复缓冲池状态

17.8.3.7 排除核心文件中的缓冲池页面

本节提供了InnoDB缓冲池的配置和调优信息。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool-resize.html

17.8.3.1 配置 InnoDB 缓冲池大小

您可以在离线或服务器运行时配置InnoDB缓冲池大小。本节描述的行为适用于两种方法。有关在线配置缓冲池大小的更多信息,请参见在线配置 InnoDB 缓冲池大小。

当增加或减少innodb_buffer_pool_size时,操作是以块为单位进行的。块大小由innodb_buffer_pool_chunk_size配置选项定义,默认值为128M。有关更多信息,请参见配置 InnoDB 缓冲池块大小。

缓冲池大小必须始终等于或是innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances的倍数。如果您将innodb_buffer_pool_size配置为不等于或不是innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances的倍数的值,缓冲池大小将自动调整为等于或是innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances的倍数的值。

在以下示例中,innodb_buffer_pool_size设置为8Ginnodb_buffer_pool_instances设置为16innodb_buffer_pool_chunk_size128M,这是默认值。

8G是一个有效的innodb_buffer_pool_size值,因为8Ginnodb_buffer_pool_instances=16 * innodb_buffer_pool_chunk_size=128M的倍数,即2G

$> mysqld --innodb-buffer-pool-size=8G --innodb-buffer-pool-instances=16
mysql> SELECT @@innodb_buffer_pool_size/1024/1024/1024;
+------------------------------------------+
| @@innodb_buffer_pool_size/1024/1024/1024 |
+------------------------------------------+
|                           8.000000000000 |
+------------------------------------------+

在这个例子中,innodb_buffer_pool_size 设置为 9G,而 innodb_buffer_pool_instances 设置为 16innodb_buffer_pool_chunk_size128M,这是默认值。在这种情况下,9G 不是 innodb_buffer_pool_instances=16 * innodb_buffer_pool_chunk_size=128M 的倍数,因此 innodb_buffer_pool_size 被调整为 10G,这是 innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances 的倍数。

$> mysqld --innodb-buffer-pool-size=9G --innodb-buffer-pool-instances=16
mysql> SELECT @@innodb_buffer_pool_size/1024/1024/1024;
+------------------------------------------+
| @@innodb_buffer_pool_size/1024/1024/1024 |
+------------------------------------------+
|                          10.000000000000 |
+------------------------------------------+
配置 InnoDB 缓冲池块大小

innodb_buffer_pool_chunk_size 可以以 1MB(1048576 字节)为单位增加或减少,但只能在启动时修改,在命令行字符串或 MySQL 配置文件中。

命令行:

$> mysqld --innodb-buffer-pool-chunk-size=134217728

配置文件:

[mysqld]
innodb_buffer_pool_chunk_size=134217728

当修改 innodb_buffer_pool_chunk_size 时,以下条件适用:

  • 如果新的 innodb_buffer_pool_chunk_size 值 * innodb_buffer_pool_instances 大于当前缓冲池大小在缓冲池初始化时,innodb_buffer_pool_chunk_size 被截断为 innodb_buffer_pool_size / innodb_buffer_pool_instances

    例如,如果缓冲池初始化大小为 2GB(2147483648 字节),有 4 个缓冲池实例,以及 1GB(1073741824 字节)的块大小,块大小被截断为等于 innodb_buffer_pool_size / innodb_buffer_pool_instances 的值,如下所示:

    $> mysqld --innodb-buffer-pool-size=2147483648 --innodb-buffer-pool-instances=4
    --innodb-buffer-pool-chunk-size=1073741824;
    
    mysql> SELECT @@innodb_buffer_pool_size;
    +---------------------------+
    | @@innodb_buffer_pool_size |
    +---------------------------+
    |                2147483648 |
    +---------------------------+
    
    mysql> SELECT @@innodb_buffer_pool_instances;
    +--------------------------------+
    | @@innodb_buffer_pool_instances |
    +--------------------------------+
    |                              4 |
    +--------------------------------+
    
    # Chunk size was set to 1GB (1073741824 bytes) on startup but was
    # truncated to innodb_buffer_pool_size / innodb_buffer_pool_instances
    
    mysql> SELECT @@innodb_buffer_pool_chunk_size;
    +---------------------------------+
    | @@innodb_buffer_pool_chunk_size |
    +---------------------------------+
    |                       536870912 |
    +---------------------------------+
    
  • 缓冲池大小必须始终等于或是innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances的倍数。如果您更改innodb_buffer_pool_chunk_sizeinnodb_buffer_pool_size会自动调整为等于或是innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances的值。此调整发生在缓冲池初始化时。以下示例演示了这种行为:

    # The buffer pool has a default size of 128MB (134217728 bytes)
    
    mysql> SELECT @@innodb_buffer_pool_size;
    +---------------------------+
    | @@innodb_buffer_pool_size |
    +---------------------------+
    |                 134217728 |
    +---------------------------+
    
    # The chunk size is also 128MB (134217728 bytes)
    
    mysql> SELECT @@innodb_buffer_pool_chunk_size;
    +---------------------------------+
    | @@innodb_buffer_pool_chunk_size |
    +---------------------------------+
    |                       134217728 |
    +---------------------------------+
    
    # There is a single buffer pool instance
    
    mysql> SELECT @@innodb_buffer_pool_instances;
    +--------------------------------+
    | @@innodb_buffer_pool_instances |
    +--------------------------------+
    |                              1 |
    +--------------------------------+
    
    # Chunk size is decreased by 1MB (1048576 bytes) at startup
    # (134217728 - 1048576 = 133169152):
    
    $> mysqld --innodb-buffer-pool-chunk-size=133169152
    
    mysql> SELECT @@innodb_buffer_pool_chunk_size;
    +---------------------------------+
    | @@innodb_buffer_pool_chunk_size |
    +---------------------------------+
    |                       133169152 |
    +---------------------------------+
    
    # Buffer pool size increases from 134217728 to 266338304
    # Buffer pool size is automatically adjusted to a value that is equal to
    # or a multiple of innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances
    
    mysql> SELECT @@innodb_buffer_pool_size;
    +---------------------------+
    | @@innodb_buffer_pool_size |
    +---------------------------+
    |                 266338304 |
    +---------------------------+
    

    该示例演示了具有多个缓冲池实例的相同行为:

    # The buffer pool has a default size of 2GB (2147483648 bytes)
    
    mysql> SELECT @@innodb_buffer_pool_size;
    +---------------------------+
    | @@innodb_buffer_pool_size |
    +---------------------------+
    |                2147483648 |
    +---------------------------+
    
    # The chunk size is .5 GB (536870912 bytes)
    
    mysql> SELECT @@innodb_buffer_pool_chunk_size;
    +---------------------------------+
    | @@innodb_buffer_pool_chunk_size |
    +---------------------------------+
    |                       536870912 |
    +---------------------------------+
    
    # There are 4 buffer pool instances
    
    mysql> SELECT @@innodb_buffer_pool_instances;
    +--------------------------------+
    | @@innodb_buffer_pool_instances |
    +--------------------------------+
    |                              4 |
    +--------------------------------+
    
    # Chunk size is decreased by 1MB (1048576 bytes) at startup
    # (536870912 - 1048576 = 535822336):
    
    $> mysqld --innodb-buffer-pool-chunk-size=535822336
    
    mysql> SELECT @@innodb_buffer_pool_chunk_size;
    +---------------------------------+
    | @@innodb_buffer_pool_chunk_size |
    +---------------------------------+
    |                       535822336 |
    +---------------------------------+
    
    # Buffer pool size increases from 2147483648 to 4286578688
    # Buffer pool size is automatically adjusted to a value that is equal to
    # or a multiple of innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances
    
    mysql> SELECT @@innodb_buffer_pool_size;
    +---------------------------+
    | @@innodb_buffer_pool_size |
    +---------------------------+
    |                4286578688 |
    +---------------------------+
    

    当更改innodb_buffer_pool_chunk_size时需要小心,因为更改此值可能会增加缓冲池的大小,如上面的示例所示。在更改innodb_buffer_pool_chunk_size之前,请计算对innodb_buffer_pool_size的影响,以确保最终的缓冲池大小是可接受的。

注意

为避免潜在的性能问题,块数(innodb_buffer_pool_size / innodb_buffer_pool_chunk_size)不应超过 1000。

在线配置 InnoDB 缓冲池大小

innodb_buffer_pool_size配置选项可以使用SET语句动态设置,允许您调整缓冲池的大小而无需重新启动服务器。例如:

mysql> SET GLOBAL innodb_buffer_pool_size=402653184;

注意

缓冲池大小必须等于或是innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances的倍数。更改这些变量设置需要重新启动服务器。

在调整缓冲池大小之前,应完成通过 InnoDB API 执行的活动事务和操作。在启动调整操作时,操作直到所有活动事务完成后才开始。一旦调整操作正在进行中,需要访问缓冲池的新事务和操作必须等待调整操作完成。唯一的例外是,在减小缓冲池大小时,允许并发访问缓冲池,当缓冲池碎片整理并在减小缓冲池大小时撤回页面时。允许并发访问的缺点是,在页面被撤回时可能导致可用页面的暂时短缺。

注意

如果在缓冲池调整操作开始后启动嵌套事务,可能会失败。

监视在线缓冲池调整进度

Innodb_buffer_pool_resize_status 变量报告一个字符串值,指示缓冲池调整的进度;例如:

mysql> SHOW STATUS WHERE Variable_name='InnoDB_buffer_pool_resize_status';
+----------------------------------+----------------------------------+
| Variable_name                    | Value                            |
+----------------------------------+----------------------------------+
| Innodb_buffer_pool_resize_status | Resizing also other hash tables. |
+----------------------------------+----------------------------------+

从 MyQL 8.0.31 开始,您还可以使用 Innodb_buffer_pool_resize_status_codeInnodb_buffer_pool_resize_status_progress 状态变量监视在线缓冲池调整操作,这些变量报告数值,适合程序化监控。

Innodb_buffer_pool_resize_status_code 状态变量报告一个状态码,指示在线缓冲池调整操作的阶段。状态码包括:

  • 0: 没有正在进行的调整操作

  • 1: 开始调整大小

  • 2: 禁用 AHI(自适应哈希索引)

  • 3: 撤回块

  • 4: 获取全局锁

  • 5: 调整池

  • 6: 调整哈希

  • 7: 调整失败

Innodb_buffer_pool_resize_status_progress 状态变量报告一个百分比值,指示每个阶段的进度。百分比值在处理每个缓冲池实例后更新。当状态(由 Innodb_buffer_pool_resize_status_code 报告)从一个状态变为另一个状态时,百分比值将重置为 0。

以下查询返回一个字符串值,指示缓冲池调整的进度,一个代码,指示操作的当前阶段,以及该阶段的当前进度,表示为百分比值:

SELECT variable_name, variable_value 
 FROM performance_schema.global_status 
 WHERE LOWER(variable_name) LIKE "innodb_buffer_pool_resize%";

缓冲池调整进度也可以在服务器错误日志中看到。此示例显示了在增加缓冲池大小时记录的注释:

[Note] InnoDB: Resizing buffer pool from 134217728 to 4294967296. (unit=134217728)
[Note] InnoDB: disabled adaptive hash index.
[Note] InnoDB: buffer pool 0 : 31 chunks (253952 blocks) was added.
[Note] InnoDB: buffer pool 0 : hash tables were resized.
[Note] InnoDB: Resized hash tables at lock_sys, adaptive hash index, dictionary.
[Note] InnoDB: completed to resize buffer pool from 134217728 to 4294967296.
[Note] InnoDB: re-enabled adaptive hash index.

此示例显示了在减小缓冲池大小时记录的注释:

[Note] InnoDB: Resizing buffer pool from 4294967296 to 134217728. (unit=134217728)
[Note] InnoDB: disabled adaptive hash index.
[Note] InnoDB: buffer pool 0 : start to withdraw the last 253952 blocks.
[Note] InnoDB: buffer pool 0 : withdrew 253952 blocks from free list. tried to relocate 
0 pages. (253952/253952)
[Note] InnoDB: buffer pool 0 : withdrawn target 253952 blocks.
[Note] InnoDB: buffer pool 0 : 31 chunks (253952 blocks) was freed.
[Note] InnoDB: buffer pool 0 : hash tables were resized.
[Note] InnoDB: Resized hash tables at lock_sys, adaptive hash index, dictionary.
[Note] InnoDB: completed to resize buffer pool from 4294967296 to 134217728.
[Note] InnoDB: re-enabled adaptive hash index.

从 MySQL 8.0.31 开始,使用--log-error-verbosity=3启动服务器时,在在线缓冲池调整操作期间向错误日志记录额外信息。额外信息包括由Innodb_buffer_pool_resize_status_code报告的状态代码和由Innodb_buffer_pool_resize_status_progress报告的百分比进度值。

[Note] [MY-012398] [InnoDB] Requested to resize buffer pool. (new size: 1073741824 bytes)
[Note] [MY-013954] [InnoDB] Status code 1: Resizing buffer pool from 134217728 to 1073741824
(unit=134217728).
[Note] [MY-013953] [InnoDB] Status code 1: 100% complete
[Note] [MY-013952] [InnoDB] Status code 1: Completed
[Note] [MY-013954] [InnoDB] Status code 2: Disabling adaptive hash index.
[Note] [MY-011885] [InnoDB] disabled adaptive hash index.
[Note] [MY-013953] [InnoDB] Status code 2: 100% complete
[Note] [MY-013952] [InnoDB] Status code 2: Completed
[Note] [MY-013954] [InnoDB] Status code 3: Withdrawing blocks to be shrunken.
[Note] [MY-013953] [InnoDB] Status code 3: 100% complete
[Note] [MY-013952] [InnoDB] Status code 3: Completed
[Note] [MY-013954] [InnoDB] Status code 4: Latching whole of buffer pool.
[Note] [MY-013953] [InnoDB] Status code 4: 14% complete
[Note] [MY-013953] [InnoDB] Status code 4: 28% complete
[Note] [MY-013953] [InnoDB] Status code 4: 42% complete
[Note] [MY-013953] [InnoDB] Status code 4: 57% complete
[Note] [MY-013953] [InnoDB] Status code 4: 71% complete
[Note] [MY-013953] [InnoDB] Status code 4: 85% complete
[Note] [MY-013953] [InnoDB] Status code 4: 100% complete
[Note] [MY-013952] [InnoDB] Status code 4: Completed
[Note] [MY-013954] [InnoDB] Status code 5: Starting pool resize
[Note] [MY-013954] [InnoDB] Status code 5: buffer pool 0 : resizing with chunks 1 to 8.
[Note] [MY-011891] [InnoDB] buffer pool 0 : 7 chunks (57339 blocks) were added.
[Note] [MY-013953] [InnoDB] Status code 5: 100% complete
[Note] [MY-013952] [InnoDB] Status code 5: Completed
[Note] [MY-013954] [InnoDB] Status code 6: Resizing hash tables.
[Note] [MY-011892] [InnoDB] buffer pool 0 : hash tables were resized.
[Note] [MY-013953] [InnoDB] Status code 6: 100% complete
[Note] [MY-013954] [InnoDB] Status code 6: Resizing also other hash tables.
[Note] [MY-011893] [InnoDB] Resized hash tables at lock_sys, adaptive hash index, dictionary.
[Note] [MY-011894] [InnoDB] Completed to resize buffer pool from 134217728 to 1073741824.
[Note] [MY-011895] [InnoDB] Re-enabled adaptive hash index.
[Note] [MY-013952] [InnoDB] Status code 6: Completed
[Note] [MY-013954] [InnoDB] Status code 0: Completed resizing buffer pool at 220826  6:25:46.
[Note] [MY-013953] [InnoDB] Status code 0: 100% complete
在线缓冲池调整内部机制

调整操作由后台线程执行。当增加缓冲池大小时,调整操作:

  • (块大小由innodb_buffer_pool_chunk_size定义)添加页面

  • 将哈希表、列表和指针转换为在内存中使用新地址

  • 将新页面添加到空闲列表中

在进行这些操作时,其他线程被阻止访问缓冲池。

当缩小缓冲池大小时,调整操作:

  • 对缓冲池进行碎片整理并撤回(释放)页面

  • (块大小由innodb_buffer_pool_chunk_size定义)移除页面

  • 将哈希表、列表和指针转换为在内存中使用新地址

在这些操作中,只有对缓冲池进行碎片整理和撤回页面允许其他线程同时访问缓冲池。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-multiple-buffer-pools.html

17.8.3.2 配置多个缓冲池实例

对于具有多吉字节范围缓冲池的系统,将缓冲池划分为单独的实例可以提高并发性,通过减少不同线程读取和写入缓存页面时的争用。此功能通常适用于具有多吉字节范围缓冲池大小的系统。可以使用innodb_buffer_pool_instances配置选项配置多个缓冲池实例,并且您可能还需要调整innodb_buffer_pool_size的值。

InnoDB缓冲池很大时,许多数据请求可以通过从内存中检索来满足。您可能会遇到多个线程同时尝试访问缓冲池而导致瓶颈。您可以启用多个缓冲池以最小化此争用。存储在缓冲池中或从缓冲池中读取的每个页面都随机分配给其中一个缓冲池,使用哈希函数。每个缓冲池管理其自己的空闲列表、刷新列表、LRU 列表和所有与缓冲池相关的其他数据结构。在 MySQL 8.0 之前,每个缓冲池都由其自己的缓冲池互斥锁保护。在 MySQL 8.0 及更高版本中,缓冲池互斥锁被几个列表和哈希保护互斥锁所取代,以减少争用。

要启用多个缓冲池实例,请将innodb_buffer_pool_instances配置选项设置为大于 1(默认值)至 64(最大值)。仅当您将innodb_buffer_pool_size设置为 1GB 或更大时,此选项才会生效。您指定的总大小将分配给所有缓冲池。为了获得最佳效率,请指定innodb_buffer_pool_instancesinnodb_buffer_pool_size的组合,以便每个缓冲池实例至少为 1GB。

要了解如何修改InnoDB缓冲池大小的信息,请参阅第 17.8.3.1 节,“配置 InnoDB 缓冲池大小”。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-performance-midpoint_insertion.html

17.8.3.3 使缓冲池具有扫描抵抗力

InnoDB不使用严格的 LRU 算法,而是使用一种技术来最小化带入缓冲池但从未再次访问的数据量。目标是确保频繁访问的(“热”)页面保留在缓冲池中,即使预读取和全表扫描带入可能或可能不会在之后访问的新块。

新读取的块被插入 LRU 列表的中间。所有新读取的页面默认情况下插入到 LRU 列表尾部的3/8位置。当它们在缓冲池中第一次被访问时,页面被移动到列表的前端(最近使用的端)。因此,从未被访问的页面永远不会进入 LRU 列表的前部,比严格的 LRU 方法更快地“老化”。这种安排将 LRU 列表分为两个部分,插入点后面的页面被视为“旧”,是 LRU 驱逐的理想受害者。

有关InnoDB缓冲池的内部工作原理和 LRU 算法的具体信息,请参见第 17.5.1 节,“缓冲池”。

您可以控制 LRU 列表中的插入点,并选择InnoDB是否将相同的优化应用于通过表或索引扫描带入缓冲池的块。配置参数innodb_old_blocks_pct控制 LRU 列表中“旧”块的百分比。innodb_old_blocks_pct的默认值为37,对应于原始的固定比率 3/8。值范围为5(缓冲池中的新页面很快就会老化)到95(只有 5%的缓冲池用于热页面,使算法接近熟悉的 LRU 策略)。

通过优化,可以避免缓冲池由于预读取而产生类似的问题,这可以避免由于表格或索引扫描而导致的问题。在这些扫描中,数据页通常会被快速连续访问几次,然后再也不会被访问。配置参数innodb_old_blocks_time指定了在第一次访问页面后的时间窗口(以毫秒为单位),在此期间可以访问该页面而无需将其移动到 LRU 列表的前端(最近使用的端)。innodb_old_blocks_time的默认值为1000。增加此值会使更多的块更有可能从缓冲池中更快地过期。

innodb_old_blocks_pctinnodb_old_blocks_time都可以在 MySQL 选项文件(my.cnfmy.ini)中指定,或者使用SET GLOBAL语句在运行时更改。在运行时更改值需要具有足够权限设置全局系统变量。请参见 Section 7.1.9.1,“系统变量权限”。

为了帮助您评估设置这些参数的效果,SHOW ENGINE INNODB STATUS命令会报告缓冲池的统计信息。详情请参见使用 InnoDB 标准监视器监视缓冲池。

由于这些参数的效果可能根据硬件配置、数据和工作负载的细节而有很大差异,所以在更改这些设置之前,始终要进行基准测试以验证效果,尤其是在任何性能关键或生产环境中。

在混合工作负载中,大部分活动是 OLTP 类型,定期批量报告查询导致大规模扫描时,设置innodb_old_blocks_time的值可以帮助保持正常工作负载的工作集在缓冲池中。

当扫描无法完全适应缓冲池的大型表格时,将innodb_old_blocks_pct设置为一个较小的值,可以避免只读取一次的数据占用缓冲池的大部分空间。例如,设置innodb_old_blocks_pct=5将限制只读取一次的数据占缓冲池的 5%。

当扫描适应内存的小型表格时,在缓冲池内移动页面的开销较小,因此可以将innodb_old_blocks_pct保持在默认值,甚至更高,例如innodb_old_blocks_pct=50

innodb_old_blocks_time参数的影响比innodb_old_blocks_pct参数更难预测,影响相对较小,并且随着工作负载的变化更大。如果调整innodb_old_blocks_pct未能带来性能改善,建议进行自己的基准测试以确定最佳值。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-performance-read_ahead.html

17.8.3.4 配置 InnoDB 缓冲池预取(预读取)

一个预读取请求是一个异步的 I/O 请求,用于预取缓冲池中多个页面,以期望未来需要这些页面。这些请求一次带入一个 extent 中的所有页面。InnoDB使用两种预读取算法来提高 I/O 性能:

线性预读取是一种技术,根据缓冲池中按顺序访问的页面来预测哪些页面可能很快会被需要。您可以通过调整配置参数innodb_read_ahead_threshold来控制InnoDB何时执行预读取操作,该参数表示触发异步读取请求所需的连续页面访问次数。在添加此参数之前,InnoDB只会在读取当前 extent 的最后一页时计算是否发出整个下一个 extent 的异步预取请求。

配置参数innodb_read_ahead_threshold控制InnoDB在检测连续页面访问模式时的敏感度。如果从一个 extent 中连续读取的页面数量大于或等于innodb_read_ahead_thresholdInnoDB会启动整个后续 extent 的异步预读取操作。innodb_read_ahead_threshold的值可以设置为 0-64 之间的任何值。默认值为 56。值越高,访问模式检查越严格。例如,如果将值设置为 48,InnoDB仅在当前 extent 中连续访问了 48 页时才触发线性预读取请求。如果值为 8,即使在 extent 中连续访问了仅有 8 页,InnoDB也会触发异步预读取。您可以在 MySQL 的配置文件中设置此参数的值,或使用SET GLOBAL语句动态更改该值,这需要足够的权限来设置全局系统变量。参见 Section 7.1.9.1, “System Variable Privileges”。

随机预读是一种技术,根据缓冲池中已经存在的页面,预测哪些页面可能很快就会被需要,而不考虑这些页面的读取顺序。如果在缓冲池中找到来自同一范围的连续 13 个页面,InnoDB会异步发出请求,预取该范围的其余页面。要启用此功能,请将配置变量innodb_random_read_ahead设置为ON

SHOW ENGINE INNODB STATUS 命令显示统计信息,帮助您评估预读算法的有效性。统计信息包括以下全局状态变量的计数器信息:

  • Innodb_buffer_pool_read_ahead

  • Innodb_buffer_pool_read_ahead_evicted

  • Innodb_buffer_pool_read_ahead_rnd

当微调innodb_random_read_ahead设置时,这些信息可能会有用。

有关 I/O 性能的更多信息,请参阅第 10.5.8 节,“优化 InnoDB 磁盘 I/O” 和 第 10.12.1 节,“优化磁盘 I/O”。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool-flushing.html

17.8.3.5 配置缓冲池刷新

InnoDB在后台执行某些任务,包括从缓冲池中刷新脏页。脏页是已经被修改但尚未写入磁盘上的数据文件的页面。

在 MySQL 8.0 中,缓冲池刷新由页面清理线程执行。页面清理线程的数量由innodb_page_cleaners变量控制,默认值为 4。但是,如果页面清理线程的数量超过缓冲池实例的数量,innodb_page_cleaners会自动设置为与innodb_buffer_pool_instances相同的值。

当脏页的百分比达到由innodb_max_dirty_pages_pct_lwm变量定义的低水位值时,会启动缓冲池刷新。默认的低水位标记是缓冲池页面的 10%。innodb_max_dirty_pages_pct_lwm值为 0 会禁用这种早期刷新行为。

innodb_max_dirty_pages_pct_lwm阈值的目的是控制缓冲池中的脏页百分比,并防止脏页数量达到由innodb_max_dirty_pages_pct变量定义的阈值,其默认值为 90。如果缓冲池中脏页的百分比达到innodb_max_dirty_pages_pct阈值,InnoDB会积极刷新缓冲池页面。

在配置innodb_max_dirty_pages_pct_lwm时,该值应始终低于innodb_max_dirty_pages_pct值。

其他变量允许对缓冲池刷新行为进行微调:

  • innodb_flush_neighbors变量定义了是否刷新缓冲池中的页面也会刷新同一范围内的其他脏页。

    • 默认设置为 0 会禁用innodb_flush_neighbors。同一范围内的脏页不会被刷新。这个设置适用于非旋转存储(SSD)设备,其中寻道时间不是一个重要因素。

    • 设置为 1 会刷新同一范围内的连续脏页。

    • 设置为 2 会刷新同一范围内的脏页。

    当表数据存储在传统的 HDD 存储设备上时,一次刷新相邻页面可以减少 I/O 开销(主要是磁盘寻道操作)相对于在不同时间刷新单个页面。对于存储在 SSD 上的表数据,寻道时间不是一个重要因素,您可以禁用此设置以分散写操作。

  • innodb_lru_scan_depth变量指定了每个缓冲池实例中,页面清理线程扫描缓冲池 LRU 列表以查找要刷新的脏页的深度。这是一个后台操作,由页面清理线程每秒执行一次。

    一般来说,比默认值小的设置对大多数工作负载都是合适的。如果值显著高于必要值,可能会影响性能。只有在典型工作负载下有多余的 I/O 容量时才考虑增加该值。相反,如果写入密集型工作负载使您的 I/O 容量饱和,减少该值,特别是在大缓冲池的情况下。

    在调整innodb_lru_scan_depth时,从一个较低值开始,并将设置向上配置,目的是很少看到零空闲页面。此外,在更改缓冲池实例数量时,考虑调整innodb_lru_scan_depth,因为innodb_lru_scan_depth * innodb_buffer_pool_instances定义了每秒页清理线程执行的工作量。

innodb_flush_neighborsinnodb_lru_scan_depth变量主要用于写入密集型工作负载。在大量 DML 活动中,如果刷新不够积极,刷新可能会滞后,或者如果刷新过于积极,磁盘写入可能会饱和 I/O 容量。理想的设置取决于您的工作负载、数据访问模式和存储配置(例如,数据存储在 HDD 还是 SSD 设备上)。

自适应刷新

InnoDB使用自适应刷新算法动态调整刷新速率,根据重做日志生成的速度和当前刷新速率。其目的是通过确保刷新活动跟上当前工作负载的步伐来平滑整体性能。自动调整刷新速率有助于避免由于缓冲池刷新导致的 I/O 活动突然下降,从而影响了用于普通读写活动的 I/O 容量。

尖锐的检查点通常与产生大量重做条目的写入密集型工作负载相关联,例如可能导致吞吐量突然变化。尖锐的检查点发生在InnoDB想要重用日志文件的一部分时。在这样做之前,必须刷新该日志文件部分中具有重做条目的所有脏页。如果日志文件变满,将发生尖锐的检查点,导致暂时的吞吐量降低。即使未达到innodb_max_dirty_pages_pct阈值,也可能发生这种情况。

自适应刷新算法通过跟踪缓冲池中脏页的数量以及重做日志记录生成的速率来避免这种情况。根据这些信息,它决定每秒从缓冲池中刷新多少脏页,从而使其能够管理工作负载的突然变化。

innodb_adaptive_flushing_lwm变量定义了重做日志容量的低水位标记。当超过该阈值时,即使innodb_adaptive_flushing变量被禁用,自适应刷新也会被启用。

内部基准测试显示,该算法不仅可以随时间保持吞吐量,还可以显著提高总体吞吐量。然而,自适应刷新可能会显著影响工作负载的 I/O 模式,并且在所有情况下可能并不适用。当重做日志面临填满的危险时,它会带来最大的好处。如果自适应刷新不适合您的工作负载特征,您可以禁用它。自适应刷新由innodb_adaptive_flushing变量控制,默认情况下启用。

innodb_flushing_avg_loops定义了InnoDB保持先前计算的刷新状态快照的迭代次数,控制自适应刷新对前台工作负载变化的快速响应。较高的innodb_flushing_avg_loops值意味着InnoDB会保持先前计算的快照时间更长,因此自适应刷新的响应速度更慢。设置较高的值时,重要的是确保重做日志利用率不会达到 75%(异步刷新开始的硬编码限制),并且innodb_max_dirty_pages_pct阈值保持脏页数量适合工作负载的水平。

对于工作负载一致、日志文件大小较大(innodb_log_file_size)且不会达到 75% 日志空间利用率的系统,应该使用较高的 innodb_flushing_avg_loops 值,以尽可能平滑地进行刷新。对于负载波动极大或日志文件提供的空间不多的系统,较小的值可以使刷新紧密跟踪工作负载变化,并有助于避免达到 75% 日志空间利用率。

请注意,如果刷新落后,缓冲池刷新速率可能超过 InnoDB 可用的 I/O 容量,即由 innodb_io_capacity 设置定义。innodb_io_capacity_max 值在这种情况下定义了 I/O 容量的上限,以防止 I/O 活动的激增消耗服务器的整个 I/O 容量。

innodb_io_capacity 设置适用于所有缓冲池实例。当脏页被刷新时,I/O 容量在缓冲池实例之间均等分配。

限制空闲期间的缓冲区刷新

截至 MySQL 8.0.18 版本,您可以使用 innodb_idle_flush_pct 变量来限制在空闲期间缓冲池刷新的速率,空闲期间是指数据库页面未被修改的时间段。innodb_idle_flush_pct 值是 innodb_io_capacity 设置的百分比,该设置定义了每秒可用于 InnoDB 的 I/O 操作数。默认的 innodb_idle_flush_pct 值为 100,即 innodb_io_capacity 设置的 100%。为了在空闲期间限制刷新,请定义一个小于 100 的 innodb_idle_flush_pct 值。

在空闲期间限制页面刷新可以延长固态存储设备的寿命。限制空闲期间页面刷新的副作用可能包括在长时间空闲期之后更长的关闭时间,以及在发生服务器故障时更长的恢复时间。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-preload-buffer-pool.html

17.8.3.6 保存和恢复缓冲池状态

为了在服务器重新启动后减少热身期,InnoDB 在服务器关闭时保存每个缓冲池中最近使用的页面的一定百分比,并在服务器启动时恢复这些页面。存储的最近使用页面的百分比由 innodb_buffer_pool_dump_pct 配置选项定义。

重启繁忙的服务器后,通常会有一个热身期,吞吐量稳步增加,因为缓冲池中的磁盘页被重新加载到内存中(当相同数据被查询、更新等时)。在启动时恢复缓冲池的能力通过重新加载重启前在缓冲池中的磁盘页,缩短了热身期,而不是等待 DML 操作访问相应的行。此外,I/O 请求可以批量执行,使整体 I/O 更快。页面加载在后台进行,不会延迟数据库启动。

除了在关闭时保存缓冲池状态并在启动时恢复它之外,您还可以在服务器运行时的任何时候保存和恢复缓冲池状态。例如,在稳定的工作负载下达到稳定吞吐量后,您可以保存缓冲池的状态。您还可以在运行报告或将数据页带入缓冲池的维护作业后,或在运行其他非典型工作负载后,恢复先前的缓冲池状态。

即使缓冲池的大小可能达到几个千兆字节,但相比之下,InnoDB 保存到磁盘的缓冲池数据非常小。只保存用于定位适当页面的表空间 ID 和页面 ID。此信息来自 INNODB_BUFFER_PAGE_LRU INFORMATION_SCHEMA 表。默认情况下,表空间 ID 和页面 ID 数据保存在名为 ib_buffer_pool 的文件中,该文件保存在 InnoDB 数据目录中。文件名和位置可以使用 innodb_buffer_pool_filename 配置参数进行修改。

因为数据像常规数据库操作一样在缓冲池中缓存并被淘汰,所以如果磁盘页最近被更新,或者 DML 操作涉及尚未加载的数据,都不会有问题。加载机制会跳过不再存在的请求页面。

底层机制涉及一个后台线程,用于执行转储和加载操作。

来自压缩表的磁盘页面以压缩形式加载到缓冲池中。在进行 DML 操作期间访问页面内容时,页面将像往常一样解压缩。因为解压缩页面是一个消耗 CPU 的过程,所以为了并发性能更高,最好在连接线程中执行操作,而不是在执行缓冲池恢复操作的单个线程中执行操作。

有关保存和恢复缓冲池状态的操作描述在以下主题中:

  • 配置缓冲池页面转储百分比

  • 在关闭时保存缓冲池状态并在启动时恢复

  • 在线保存和恢复缓冲池状态

  • 显示缓冲池转储进度

  • 显示缓冲池加载进度

  • 中止缓冲池加载操作

  • 使用性能模式监视缓冲池加载进度

配置缓冲池页面转储百分比

在从缓冲池转储页面之前,您可以通过设置innodb_buffer_pool_dump_pct选项来配置要转储的最近使用的缓冲池页面的百分比。如果您计划在服务器运行时转储缓冲池页面,可以动态配置该选项:

SET GLOBAL innodb_buffer_pool_dump_pct=40;

如果您计划在服务器关闭时转储缓冲池页面,请在配置文件中设置innodb_buffer_pool_dump_pct

[mysqld]
innodb_buffer_pool_dump_pct=40

innodb_buffer_pool_dump_pct的默认值为 25(转储最近使用的页面的 25%)。

在关闭时保存缓冲池状态并在启动时恢复

在关闭服务器之前,请在关闭服务器之前发出以下语句以保存缓冲池状态:

SET GLOBAL innodb_buffer_pool_dump_at_shutdown=ON;

innodb_buffer_pool_dump_at_shutdown默认启用。

在服务器启动时恢复缓冲池状态,请在启动服务器时指定--innodb-buffer-pool-load-at-startup选项:

mysqld --innodb-buffer-pool-load-at-startup=ON;

innodb_buffer_pool_load_at_startup默认启用。

在线保存和恢复缓冲池状态

要在 MySQL 服务器运行时保存缓冲池状态,请执行以下语句:

SET GLOBAL innodb_buffer_pool_dump_now=ON;

在 MySQL 运行时恢复缓冲池状态,请执行以下语句:

SET GLOBAL innodb_buffer_pool_load_now=ON;
显示缓冲池转储进度

要在将缓冲池状态保存到磁盘时显示进度,请执行以下语句:

SHOW STATUS LIKE 'Innodb_buffer_pool_dump_status';

如果操作尚未开始,则返回“未开始”。如果操作已完成,则打印完成时间(例如 完成于 110505 12:18:02)。如果操作正在进行中,则提供状态信息(例如 正在转储缓冲池 5/7,页码 237/2873)。

显示缓冲池加载进度

要在加载缓冲池时显示进度,请执行以下语句:

SHOW STATUS LIKE 'Innodb_buffer_pool_load_status';

如果操作尚未开始,则返回“未开始”。如果操作已完成,则打印完成时间(例如 完成于 110505 12:23:24)。如果操作正在进行中,则提供状态信息(例如 已加载 123/22301 页)。

中止缓冲池加载操作

要中止缓冲池加载操作,请执行以下语句:

SET GLOBAL innodb_buffer_pool_load_abort=ON;
使用性能模式监视缓冲池加载进度

您可以使用性能模式监视缓冲池加载进度。

以下示例演示了如何启用stage/innodb/buffer pool load阶段事件工具和相关的消费者表以监视缓冲池加载进度。

有关本示例中使用的缓冲池转储和加载过程的信息,请参阅第 17.8.3.6 节,“保存和恢复缓冲池状态”。有关性能模式阶段事件工具和相关消费者的信息,请参阅第 29.12.5 节,“性能模式阶段事件表”。

  1. 启用stage/innodb/buffer pool load工具:

    mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' 
           WHERE NAME LIKE 'stage/innodb/buffer%';
    
  2. 启用阶段事件消费者表,包括events_stages_currentevents_stages_historyevents_stages_history_long

    mysql> UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' 
           WHERE NAME LIKE '%stages%';
    
  3. 通过启用innodb_buffer_pool_dump_now来转储当前缓冲池状态。

    mysql> SET GLOBAL innodb_buffer_pool_dump_now=ON;
    
  4. 检查缓冲池转储状态以确保操作已完成。

    mysql> SHOW STATUS LIKE 'Innodb_buffer_pool_dump_status'\G
    *************************** 1\. row ***************************
    Variable_name: Innodb_buffer_pool_dump_status
            Value: Buffer pool(s) dump completed at 150202 16:38:58
    
  5. 通过启用innodb_buffer_pool_load_now来加载缓冲池:

    mysql> SET GLOBAL innodb_buffer_pool_load_now=ON;
    
  6. 通过查询性能模式events_stages_current表,检查缓冲池加载操作的当前状态。WORK_COMPLETED列显示加载的缓冲池页面数。WORK_ESTIMATED列提供剩余工作的估计,以页面为单位。

    mysql> SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED
           FROM performance_schema.events_stages_current;
    +-------------------------------+----------------+----------------+
    | EVENT_NAME                    | WORK_COMPLETED | WORK_ESTIMATED |
    +-------------------------------+----------------+----------------+
    | stage/innodb/buffer pool load |           5353 |           7167 |
    +-------------------------------+----------------+----------------+
    

    如果缓冲池加载操作已完成,则events_stages_current表将返回一个空集。在这种情况下,您可以查询events_stages_history表查看已完成事件的数据。例如:

    mysql> SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED 
           FROM performance_schema.events_stages_history;
    +-------------------------------+----------------+----------------+
    | EVENT_NAME                    | WORK_COMPLETED | WORK_ESTIMATED |
    +-------------------------------+----------------+----------------+
    | stage/innodb/buffer pool load |           7167 |           7167 |
    +-------------------------------+----------------+----------------+
    

注意

在启动时使用innodb_buffer_pool_load_at_startup加载缓冲池时,您还可以使用性能模式监视缓冲池加载进度。在这种情况下,必须在启动时启用stage/innodb/buffer pool load工具和相关消费者。有关更多信息,请参见第 29.3 节,“性能模式启动配置”。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool-in-core-file.html

17.8.3.7 排除核心文件中的缓冲池页面

核心文件记录了运行进程的状态和内存映像。由于缓冲池位于主内存中,并且运行进程的内存映像被转储到核心文件中,当mysqld进程死机时,具有大缓冲池的系统可能会产生大型核心文件。

大型核心文件可能会带来许多问题,包括编写它们所需的时间、它们占用的磁盘空间以及传输大文件所面临的挑战。

为了减小核心文件大小,您可以禁用innodb_buffer_pool_in_core_file变量,以在核心转储中省略缓冲池页面。innodb_buffer_pool_in_core_file变量在 MySQL 8.0.14 中引入,并默认启用。

如果您担心将数据库页面转储到可能在组织内外共享用于调试目的的核心文件中,从安全角度考虑��除缓冲池页面可能也是值得的。

注意

mysqld进程死机时,访问缓冲池页面中的数据可能在某些调试场景中很有益。如果不确定是否包含或排除缓冲池页面,请咨询 MySQL 支持。

禁用innodb_buffer_pool_in_core_file仅在启用core_file变量且操作系统支持MADV_DONTDUMP非 POSIX 扩展到madvise()系统调用时生效,该扩展在 Linux 3.4 及更高版本中受支持。MADV_DONTDUMP扩展导致指定范围内的页面被排除在核心转储之外。

假设操作系统支持MADV_DONTDUMP扩展,请使用--core-file--innodb-buffer-pool-in-core-file=OFF选项启动服务器,以生成不包含缓冲池页面的核心文件。

$> mysqld --core-file --innodb-buffer-pool-in-core-file=OFF

core_file变量是只读的,默认情况下禁用。通过在启动时指定--core-file选项来启用它。innodb_buffer_pool_in_core_file变量是动态的。它可以在启动时指定,也可以使用SET语句在运行时配置。

mysql> SET GLOBAL innodb_buffer_pool_in_core_file=OFF;

如果禁用了innodb_buffer_pool_in_core_file变量,但操作系统不支持MADV_DONTDUMP,或者madvise()失败,则会向 MySQL 服务器错误日志写入警告,并禁用core_file变量以防止意外包含缓冲池页的核心文件写入。如果只读的core_file变量被禁用,则必须重新启动服务器才能再次启用它。

以下表格显示了确定是否生成核心文件以及是否包含缓冲池页的配置和MADV_DONTDUMP支持方案。

表 17.4 核心文件配置方案

core_file变量 innodb_buffer_pool_in_core_file变量 madvise() MADV_DONTDUMP 支持 结果
关(默认) 与结果无关 与结果无关 不生成核心文件
开(默认) 与结果无关 核心文件生成时包含缓冲池页
生成的核心文件不包含缓冲池页
不生成核心文件,core_file被禁用,并且向服务器错误日志写入警告

通过禁用innodb_buffer_pool_in_core_file变量来减小核心文件大小取决于缓冲池的大小,但也受到InnoDB页面大小的影响。较小的页面大小意味着相同数据量需要更多的页面,而更多的页面意味着更多的页面元数据。以下表格提供了对于具有不同页面大小的 1GB 缓冲池可能看到的大小减小示例。

表 17.5 包含和不包含缓冲池页的核心文件大小

innodb_page_size设置 包含缓冲池页(innodb_buffer_pool_in_core_file=ON 不包含缓冲池页(innodb_buffer_pool_in_core_file=OFF
4KB 2.1GB 0.9GB
64KB 1.7GB 0.7GB

17.8.4 为 InnoDB 配置线程并发性

原文:dev.mysql.com/doc/refman/8.0/en/innodb-performance-thread_concurrency.html

InnoDB使用操作系统线程来处理来自用户事务的请求。(事务在提交或回滚之前可能向InnoDB发出许多请求。)在具有多核处理器的现代操作系统和服务器上,其中上下文切换效率高,大多数工作负载在没有对并发线程数量设置任何限制的情况下运行良好。

在需要最小化线程之间上下文切换的情况下,InnoDB可以使用多种技术来限制同时执行的操作系统线程数量(因此同时处理的请求数量)。当InnoDB从用户会话接收到一个新请求时,如果同时执行的线程数量达到预定义的限制,新请求会在再次尝试之前短暂休眠一段时间。等待锁的线程不计入同时执行的线程数量。

您可以通过设置配置参数innodb_thread_concurrency来限制并发线程的数量。一旦执行线程数量达到此限制,额外的线程会在被放入队列之前休眠一段由配置参数innodb_thread_sleep_delay设置的微秒数。

您可以将配置选项innodb_adaptive_max_sleep_delay设置为您允许的最高值,以及InnoDB会根据当前线程调度活动自动调整innodb_thread_sleep_delay的值。这种动态调整有助于在线程调度机制在系统轻载时和在接近满负荷运行时平稳工作。

在各个 MySQL 和InnoDB的发布版本中,innodb_thread_concurrency的默认值和隐含的并发线程数量限制已经发生了变化。innodb_thread_concurrency的默认值为0,因此默认情况下没有对同时执行的线程数量设置限制。

InnoDB只有在并发线程数量有限时才会使线程进入睡眠状态。当线程数量没有限制时,所有线程都会平等竞争调度。也就是说,如果innodb_thread_concurrency0,则innodb_thread_sleep_delay的值会被忽略。

当线程数量有限时(当innodb_thread_concurrency > 0 时),InnoDB通过允许在执行单个 SQL 语句期间发出的多个请求进入InnoDB来减少上下文切换开销,而无需遵守innodb_thread_concurrency设置的限制。由于一个 SQL 语句(如连接)可能包含InnoDB内的多个行操作,InnoDB会分配一定数量的“票据”,允许线程以最小的开销重复调度。

当一个新的 SQL 语句开始执行时,线程没有任何票据,必须遵守innodb_thread_concurrency。一旦线程有资格进入InnoDB,它将被分配一定数量的票据,用于随后进入InnoDB执行行操作。如果票据用完,线程将被驱逐,并且会再次观察innodb_thread_concurrency,这可能会将线程重新放入等待线程的先进先出队列中。当线程再次有资格进入InnoDB时,将再次分配票据。分配的票据数量由全局选项innodb_concurrency_tickets指定,默认值为 5000。等待锁的线程在锁可用时会获得一个票据。

这些变量的正确值取决于您的环境和工作负载。尝试一系列不同的值,以确定哪个值适用于您的应用程序。在限制并发执行线程数量之前,请查看可能改善多核和多处理器计算机上InnoDB性能的配置选项,例如innodb_adaptive_hash_index

关于 MySQL 线程处理的一般性性能信息,请参见第 7.1.12.1 节,“连接接口”。

17.8.5 配置后台 InnoDB I/O 线程的数量

原文:dev.mysql.com/doc/refman/8.0/en/innodb-performance-multiple_io_threads.html

InnoDB使用后台线程来处理各种类型的 I/O 请求。您可以使用innodb_read_io_threadsinnodb_write_io_threads配置参数来配置服务数据页读取和写入 I/O 的后台线程数量。这些参数分别表示用于读取和写入请求的后台线程数量。它们在所有支持的平台上都有效。您可以在 MySQL 选项文件(my.cnfmy.ini)中为这些参数设置值;您不能动态更改值。这些参数的默认值为4,允许的值范围为1-64

这些配置选项的目的是使InnoDB在高端系统上更具可扩展性。每个后台线程可以处理多达 256 个待处理的 I/O 请求。后台 I/O 的一个主要来源是预读取请求。InnoDB试图平衡传入请求的负载,使大多数后台线程平均分担工作。InnoDB还尝试将来自同一范围的读取请求分配给同一线程,以增加合并请求的机会。如果您拥有高端 I/O 子系统,并且在SHOW ENGINE INNODB STATUS输出中看到超过 64 × innodb_read_io_threads个待处理读取请求,您可以通过增加innodb_read_io_threads的值来提高性能。

在 Linux 系统上,默认情况下,InnoDB使用异步 I/O 子系统执行数据文件页的预读取和写入请求,这改变了InnoDB后台线程处理这些类型 I/O 请求的方式。有关更多信息,请参阅第 17.8.6 节,“在 Linux 上使用异步 I/O”。

有关InnoDB I/O 性能的更多信息,请参阅第 10.5.8 节,“优化 InnoDB 磁盘 I/O”。

17.8.6 在 Linux 上使用异步 I/O

原文:dev.mysql.com/doc/refman/8.0/en/innodb-linux-native-aio.html

InnoDB在 Linux 上使用异步 I/O 子系统(本机 AIO)执行数据文件页的预读和写请求。此行为由innodb_use_native_aio配置选项控制,仅适用于 Linux 系统,并且默认启用。在其他类 Unix 系统上,InnoDB仅使用同步 I/O。从历史上看,InnoDB仅在 Windows 系统上使用异步 I/O。在 Linux 上使用异步 I/O 子系统需要libaio库。

使用同步 I/O 时,查询线程排队 I/O 请求,InnoDB后台线程逐个检索排队的请求,为每个请求发出同步 I/O 调用。当 I/O 请求完成并且 I/O 调用返回时,处理请求的InnoDB后台线程调用 I/O 完成例程并返回以处理下一个请求。可以并行处理的请求数量为n,其中nInnoDB后台线程的数量。InnoDB后台线程的数量由innodb_read_io_threadsinnodb_write_io_threads控制。参见第 17.8.5 节,“配置后台 InnoDB I/O 线程数量”。

使用本机 AIO,查询线程直接将 I/O 请求分派给操作系统,从而消除了后台线程数量的限制。InnoDB后台线程等待 I/O 事件来标志完成的请求。当请求完成时,后台线程调用 I/O 完成例程并恢复等待 I/O 事件。

本机 AIO 的优势在于对于通常在SHOW ENGINE INNODB STATUS\G输出中显示许多待处理读/写操作的重度 I/O 绑定系统的可伸缩性。使用本机 AIO 时的并行处理增加意味着 I/O 性能受到 I/O 调度程序类型或磁盘阵列控制器属性的更大影响。

对于重度 I/O 绑定系统,本机 AIO 的一个潜在缺点是无法控制一次性发送到操作系统的 I/O 写请求数量。在某些情况下,一次性发送太多 I/O 写请求到操作系统进行并行处理可能导致 I/O 读取饥饿,这取决于 I/O 活动量和系统能力。

如果操作系统中异步 I/O 子系统出现问题导致InnoDB无法启动,您可以使用innodb_use_native_aio=0选项启动服务器。在启动过程中,如果InnoDB检测到潜在问题,比如tmpdir位置、tmpfs文件系统和 Linux 内核的组合不支持在tmpfs上进行异步 I/O,该选项也可能会被自动禁用。

17.8.7 配置 InnoDB I/O 容量

原文:dev.mysql.com/doc/refman/8.0/en/innodb-configuring-io-capacity.html

InnoDB主线程和其他线程在后台执行各种任务,其中大部分与 I/O 相关,例如从缓冲池中刷新脏页并将更改从更改缓冲区写入适当的辅助索引。InnoDB试图以不会对服务器正常工作产生不利影响的方式执行这些任务。它试图估计可用的 I/O 带宽,并调整其活动以利用可用容量。

innodb_io_capacity变量定义了InnoDB可用的整体 I/O 容量。它应该设置为系统每秒可以执行的 I/O 操作数(IOPS)的大致数量。当设置了innodb_io_capacity时,InnoDB根据设置的值估计可用于后台任务的 I/O 带宽。

你可以将innodb_io_capacity设置为 100 或更高的值。默认值为200。通常,约 100 左右的值适用于消费级存储设备,例如转速为 7200 转/分的硬盘。速度更快的硬盘、RAID 配置和固态硬盘(SSD)受益于更高的值。

理想情况下,保持设置尽可能低,但不要太低以至于后台活动落后。如果值设置过高,数据会从缓冲池和更改缓冲区中被过快地移除,使得缓存无法提供显著的好处。对于能够处理更高 I/O 速率的繁忙系统,你可以设置更高的值来帮助服务器处理与高频率行更改相关的后台维护工作。通常,你可以根据用于InnoDB I/O 的驱动器数量的函数增加该值。例如,你可以在使用多个磁盘或 SSD 的系统上增加该值。

默认设置的 200 通常对于低端 SSD 足够。对于高端总线连接的 SSD,考虑更高的设置,例如 1000。对于使用单独的 5400 转/分或 7200 转/分驱动器的系统,你可能会将值降低到 100,这代表了每秒 I/O 操作(IOPS)的估计比例,适用于可以执行约 100 IOPS 的旧一代磁盘驱动器。

虽然你可以指定一个很高的值,比如一百万,但实际上这样大的值很少有好处。通常,建议不要将值设定为 20000 以上,除非你确定较低的值对你的工作负载不足。

在调整innodb_io_capacity时考虑写入工作负载。具有大量写入工作负载的系统可能会受益于更高的设置。对于写入工作负载较小的系统,较低的设置可能已经足够。

innodb_io_capacity设置不是每个缓冲池实例的设置。可用的 I/O 容量在刷新活动中均匀分配给缓冲池实例。

您可以在 MySQL 选项文件(my.cnfmy.ini)中设置innodb_io_capacity的值,或者使用SET GLOBAL语句在运行时修改它,这需要足够的权限来设置全局系统变量。请参阅第 7.1.9.1 节,“系统变量权限”。

在检查点忽略 I/O 容量

innodb_flush_sync变量默认启用,导致在发生 I/O 活动突发时,会忽略innodb_io_capacity设置,这种活动发生在检查点。要遵守innodb_io_capacity设置定义的 I/O 速率,请禁用innodb_flush_sync

您可以在 MySQL 选项文件(my.cnfmy.ini)中设置innodb_flush_sync的值,或者使用SET GLOBAL语句在运行时修改它,这需要足够的权限来设置全局系统变量。请参阅第 7.1.9.1 节,“系统变量权限”。

配置 I/O 容量最大值

如果刷新活动落后,InnoDB可以以比innodb_io_capacity变量定义的更高的 IOPS(每秒 I/O 操作数)更积极地刷新。innodb_io_capacity_max变量定义了InnoDB在这种情况下执行的后台任务的最大 IOPS 数量。

如果在启动时指定了innodb_io_capacity设置,但未为innodb_io_capacity_max指定值,则innodb_io_capacity_max默认为innodb_io_capacity值的两倍或 2000,以较大值为准。

在配置innodb_io_capacity_max时,通常将innodb_io_capacity的两倍作为起点是一个不错的选择。默认值为 2000 适用于使用 SSD 或多个常规磁盘驱动器的工作负载。对于不使用 SSD 或多个磁盘驱动器的工作负载,设置为 2000 可能过高,可能会导致过多的刷新。对于单个常规磁盘驱动器,建议设置在 200 到 400 之间。对于高端、总线连接的 SSD,考虑设置更高的值,如 2500。与innodb_io_capacity设置一样,保持设置尽可能低,但不要太低以至于InnoDB无法足够扩展 IOPS 的速率超过innodb_io_capacity设置。

在调整innodb_io_capacity_max时,请考虑写入工作负载。具有大量写入工作负载的系统可能会受益于更高的设置。对于具有较小写入工作负载的系统,较低的设置可能足够。

innodb_io_capacity_max不能设置为低于innodb_io_capacity值。

使用SET语句将innodb_io_capacity_max设置为DEFAULTSET GLOBAL innodb_io_capacity_max=DEFAULT)将innodb_io_capacity_max设置为最大值。

innodb_io_capacity_max 限制适用于所有缓冲池实例。这不是每个缓冲池实例的设置。

17.8.8 配置自旋锁轮询

原文:dev.mysql.com/doc/refman/8.0/en/innodb-performance-spin_lock_polling.html

InnoDB 互斥锁和读写锁通常保留在短时间间隔内。在多核系统上,一个线程在睡眠之前连续检查是否可以在一段时间内获取互斥锁或读写锁可能更有效。如果在此期间互斥锁或读写锁变为可用,线程可以立即继续,在同一时间片内。然而,多个线程频繁轮询共享对象(如互斥锁或读写锁)可能导致“缓存乒乓”,这会导致处理器使彼此的缓存部分失效。InnoDB通过强制在轮询活动之间引入随机延迟来最小化此问题。随机延迟实现为自旋等待循环。

自旋等待循环的持续时间取决于循环中发生的 PAUSE 指令数量。该数字是通过随机选择一个整数,范围从 0 到innodb_spin_wait_delay值,然后将该值乘以 50 来生成的。(在 MySQL 8.0.16 之前,乘数值 50 是硬编码的,在此之后可配置。)例如,对于innodb_spin_wait_delay设置为 6,会随机选择以下范围内的一个整数:

{0,1,2,3,4,5}

所选整数乘以 50,得到六个可能的 PAUSE 指令值之一:

{0,50,100,150,200,250}

对于这组值,250 是自旋等待循环中可能发生的 PAUSE 指令的最大数量。当innodb_spin_wait_delay设置为 5 时,会得到一组五个可能的值{0,50,100,150,200},其中 200 是 PAUSE 指令的最大数量,依此类推。这样,innodb_spin_wait_delay设置控制着自旋锁轮询之间的最大延迟。

在所有处理器核心共享快速缓存内存的系统上,您可以通过设置innodb_spin_wait_delay=0来减少最大延迟或完全禁用忙等待循环。在具有多个处理器芯片的系统上,缓存失效的影响可能更显著,您可能需要增加最大延迟。

在 100MHz 奔腾时代,一个innodb_spin_wait_delay单位被校准为相当于一微秒。那个时间等价性并不成立,但PAUSE指令持续时间相对于其他 CPU 指令的处理器周期保持相对稳定,直到 Skylake 处理器的引入,这些处理器具有相对较长的PAUSE指令。innodb_spin_wait_pause_multiplier变量在 MySQL 8.0.16 中引入,以提供一种解决PAUSE指令持续时间差异的方法。

innodb_spin_wait_pause_multiplier变量控制PAUSE指令值的大小。例如,假设innodb_spin_wait_delay设置为 6,将innodb_spin_wait_pause_multiplier值从 50(默认值和以前硬编码值)减少到 5 会生成一组较小的PAUSE指令值:

{0,5,10,15,20,25}

增加或减少PAUSE指令值的能力允许对不同处理器架构进行微调InnoDB。较小的PAUSE指令值适用于具有相对较长PAUSE指令的处理器架构,例如。

innodb_spin_wait_delayinnodb_spin_wait_pause_multiplier变量是动态的。它们可以在 MySQL 选项文件中指定,或者使用SET GLOBAL语句在运行时进行修改。在运行时修改变量需要足够设置全局系统变量的权限。参见第 7.1.9.1 节,“系统变量权限”。

17.8.9 清理配置

原文:dev.mysql.com/doc/refman/8.0/en/innodb-purge-configuration.html

当您使用 SQL 语句删除行时,InnoDB 不会立即从数据库中物理删除行。只有在 InnoDB 丢弃为删除编写的撤销日志记录时,行及其索引记录才会被物理删除。此删除操作仅在行不再需要用于多版本并发控制(MVCC)或回滚后发生,称为清理。

清理定期运行。它解析并处理来自历史列表的撤销日志页,历史列表是由 InnoDB 事务系统维护的已提交事务的撤销日志页列表。清理在处理完撤销日志页后会释放它们。

配置清理线程

清理操作由一个或多个清理线程在后台执行。清理线程的数量由innodb_purge_threads 变量控制。默认值为 4。

如果 DML 操作集中在单个表上,则该表的清理操作由单个清理线程执行,这可能导致清理操作减慢、清理延迟增加,并且如果 DML 操作涉及大对象值,则表空间文件大小会增加。从 MySQL 8.0.26 开始,如果超过了innodb_max_purge_lag 设置,清理工作会自动在可用的清理线程之间重新分配。在这种情况下,过多的活动清理线程可能会与用户线程发生争用,因此请相应地管理innodb_purge_threads 设置。innodb_max_purge_lag 变量默认设置为 0,这意味着默认情况下没有最大清理延迟。

如果 DML 操作集中在少数表上,请将innodb_purge_threads 设置较低,以便线程不会因争夺对繁忙表的访问而相互竞争。如果 DML 操作分布在许多表上,请考虑设置更高的innodb_purge_threads。清理线程的最大数量为 32。

innodb_purge_threads 设置是允许的最大清理线程数。清理系统会自动调整使用的清理线程数。

配置清理批量大小

innodb_purge_batch_size变量定义了清除器在一个批处理中解析和处理的撤销日志页数。默认值为 300。在多线程清除配置中,协调员清除线程将innodb_purge_batch_size除以innodb_purge_threads,并将该数量的页分配给每个清除线程。

清除系统还释放不再需要的撤销日志页。它每 128 次迭代通过撤销日志这样做。除了定义在批处理中解析和处理的撤销日志页数外,innodb_purge_batch_size变量还定义了在每 128 次迭代中清除系统释放的撤销日志页数。

innodb_purge_batch_size变量用于高级性能调优和实验。大多数用户不需要更改innodb_purge_batch_size的默认值。

配置最大清除延迟

innodb_max_purge_lag变量定义了期望的最大清除延迟。当清除延迟超过innodb_max_purge_lag阈值时,将对INSERTUPDATEDELETE操作施加延迟,以便清除操作赶上。默认值为 0,表示没有最大清除延迟和延迟。

InnoDB事务系统维护一个由UPDATEDELETE操作删除标记的索引记录的事务列表。列表的长度即为清除延迟。在 MySQL 8.0.14 之前,清除延迟延迟是通过以下公式计算的,结果是最小延迟为 5000 微秒:

(purge lag/innodb_max_purge_lag - 0.5) * 10000

从 MySQL 8.0.14 开始,清除延迟延迟是通过以下修订后的公式计算的,将最小延迟减少到 5 微秒。5 微秒的延迟对于现代系统更为合适。

(purge_lag/innodb_max_purge_lag - 0.9995) * 10000

延迟是在清除批处理的开始时计算的。

对于有问题的工作负载,典型的innodb_max_purge_lag设置可能是 1000000(100 万),假设事务很小,只有 100 字节大小,并且可以有 100MB 的未清除表行。

清除延迟显示为TRANSACTIONS部分中的History list length值,在SHOW ENGINE INNODB STATUS输出中。

mysql> SHOW ENGINE INNODB STATUS;
...
------------
TRANSACTIONS
------------
Trx id counter 0 290328385
Purge done for trx's n:o < 0 290315608 undo n:o < 0 17
History list length 20

History list length 通常是一个较低的值,通常少于几千,但是写入密集的工作负载或长时间运行的事务可能会导致其增加,即使是只读事务也可能会增加。长时间运行的事务可能会导致 History list length 增加的原因是,在诸如 REPEATABLE READ 这样的一致性读事务隔离级别下,事务必须返回与创建该事务的读视图时相同的结果。因此,InnoDB 多版本并发控制(MVCC)系统必须保留数据的副本在撤销日志中,直到所有依赖于该数据的事务完成。以下是可能导致 History list length 增加的长时间运行事务的示例:

  • 当存在大量并发的 DML 时,执行一个使用 --single-transaction 选项的 mysqldump 操作。

  • 在禁用 autocommit 后运行 SELECT 查询,并忘记发出显式的 COMMITROLLBACK

为了防止在极端情况下出现过大的清理延迟,您可以通过设置 innodb_max_purge_lag_delay 变量来限制延迟。innodb_max_purge_lag_delay 变量指定了当超过 innodb_max_purge_lag 阈值时施加的延迟的最大延迟时间(以微秒为单位)。指定的 innodb_max_purge_lag_delay 值是由 innodb_max_purge_lag 公式计算的延迟时间的上限。

清理和撤销表空间截断

清理系统还负责截断撤销表空间。您可以配置 innodb_purge_rseg_truncate_frequency 变量来控制清理系统查找要截断的撤销表空间的频率。有关更多信息,请参见 截断撤销表空间。

17.8.10 为 InnoDB 配置优化器统计信息

原文:dev.mysql.com/doc/refman/8.0/en/innodb-performance-optimizer-statistics.html

17.8.10.1 配置持久性优化器统计参数

17.8.10.2 配置非持久性优化器统计参数

17.8.10.3 为 InnoDB 表估算 ANALYZE TABLE 复杂度

本节描述了如何为InnoDB表配置持久性和非持久性优化器统计信息。

持久性优化器统计信息在服务器重启后保留,可以实现更高的计划稳定性和更一致的查询性能。持久性优化器统计信息还提供了以下额外的控制和灵活性:

  • 你可以使用innodb_stats_auto_recalc配置选项来控制在表发生重大更改后是否自动更新统计信息。

  • 你可以在CREATE TABLEALTER TABLE语句中使用STATS_PERSISTENTSTATS_AUTO_RECALCSTATS_SAMPLE_PAGES子句来为单个表配置优化器统计信息。

  • 你可以在mysql.innodb_table_statsmysql.innodb_index_stats表中查询优化器统计数据。

  • 你可以查看mysql.innodb_table_statsmysql.innodb_index_stats表的last_update列,以查看统计信息上次更新的时间。

  • 你可以手动修改mysql.innodb_table_statsmysql.innodb_index_stats表,以强制执行特定的查询优化计划或测试替代计划,而不修改数据库。

持久性优化器统计功能默认启用(innodb_stats_persistent=ON)。

非持久性优化器统计信息在每次服务器重启和其他一些操作后被清除,并在下一次访问表时重新计算。因此,在重新计算统计信息时可能会产生不同的估计值,导致执行计划的不同选择和查询性能的变化。

本节还提供了关于估算ANALYZE TABLE复杂度的信息,这在尝试在准确统计信息和ANALYZE TABLE执行时间之间取得平衡时可能会有用。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-persistent-stats.html

17.8.10.1 配置持久性优化器统计参数

持久性优化器统计功能通过将统计数据存储到磁盘并使其在服务器重新启动时持久化,从而改善了计划稳定性,使得优化器更有可能为给定查询每次做出一致的选择。

innodb_stats_persistent=ON或当单独的表被定义为STATS_PERSISTENT=1时,优化器统计数据会持久化到磁盘。innodb_stats_persistent默认启用。

以前,当重新启动服务器和执行其他某些类型的操作时,优化器统计数据会被清除,并在下一次访问表时重新计算。因此,在重新计算统计数据时可能会产生不同的估计值,导致查询执行计划中的不同选择和查询性能的变化。

持久性统计数据存储在mysql.innodb_table_statsmysql.innodb_index_stats表中。请参阅第 17.8.10.1.5 节,“InnoDB 持久性统计表”。

如果您不希望将优化器统计数据持久化到磁盘,请参阅第 17.8.10.2 节,“配置非持久性优化器统计参数”

17.8.10.1.1 配置持久性优化器统计的自动计算

默认启用的innodb_stats_auto_recalc变量控制当表的行数变化超过 10%时是否自动计算统计数据。您还可以通过在创建或更改表时指定STATS_AUTO_RECALC子句来为单独的表配置自动统计数据重新计算。

由于自动统计重新计算的异步性质,即使启用了innodb_stats_auto_recalc,在运行影响表超过 10%的 DML 操作后,统计数据可能不会立即重新计算。在某些情况下,统计数据重新计算可能会延迟几秒钟。如果需要立即更新的统计数据,请运行ANALYZE TABLE来启动同步(前台)重新计算统计数据。

如果禁用了innodb_stats_auto_recalc,您可以通过在对索引列进行重大更改后执行ANALYZE TABLE语句来确保优化器统计的准确性。您还可以考虑将ANALYZE TABLE添加到在加载数据后运行的设置脚本中,并在低活动时间定期运行ANALYZE TABLE

当向现有表添加索引或添加或删除列时,无论innodb_stats_auto_recalc的值如何,都会计算索引统计信息并将其添加到innodb_index_stats表中。

17.8.10.1.2 为单个表配置优化器统计参数

innodb_stats_persistentinnodb_stats_auto_recalc,和innodb_stats_persistent_sample_pages是全局变量。要覆盖这些系统范围的设置,并为单个表配置优化器统计参数,您可以在CREATE TABLEALTER TABLE语句中定义STATS_PERSISTENTSTATS_AUTO_RECALCSTATS_SAMPLE_PAGES子句。

  • STATS_PERSISTENT指定是否为InnoDB表启用持久统计信息。值DEFAULT导致表的持久统计设置由innodb_stats_persistent设置确定。值为1时,为表启用持久统计信息,值为0时禁用该功能。在为单个表启用持久统计信息后,使用ANALYZE TABLE在加载表数据后计算统计信息。

  • STATS_AUTO_RECALC指定是否自动重新计算持久统计信息。值DEFAULT导致表的持久统计设置由innodb_stats_auto_recalc设置确定。值为1时,当表数据变化了 10%时重新计算统计信息。值为0时,阻止对表进行自动重新计算。当使用值为 0 时,使用ANALYZE TABLE在对表进行重大更改后重新计算统计信息。

  • STATS_SAMPLE_PAGES 指定了在对索引列进行统计计算时,通过 ANALYZE TABLE 操作采样的索引页面数量,例如。

所有三个子句在以下 CREATE TABLE 示例中指定:

CREATE TABLE `t1` (
`id` int(8) NOT NULL auto_increment,
`data` varchar(255),
`date` datetime,
PRIMARY KEY  (`id`),
INDEX `DATE_IX` (`date`)
) ENGINE=InnoDB,
  STATS_PERSISTENT=1,
  STATS_AUTO_RECALC=1,
  STATS_SAMPLE_PAGES=25;
17.8.10.1.3 配置 InnoDB 优化器统计信息的采样页面数

优化器使用关于键分布的估计 统计信息 来选择执行计划的索引,基于索引的相对 选择性。诸如 ANALYZE TABLE 等操作会导致 InnoDB 从表中的每个索引中随机采样页面,以估算索引的 基数。这种采样技术被称为 随机潜水。

innodb_stats_persistent_sample_pages 控制了采样页面的数量。您可以在运行时调整此设置以管理优化器使用的统计估算的质量。默认值为 20。在遇到以下问题时考虑修改此设置:

  1. 统计数据不够准确且优化器选择了次优执行计划,如 EXPLAIN 输出所示。您可以通过比较索引的实际基数(通过在索引列上运行 SELECT DISTINCT 确定)与 mysql.innodb_index_stats 表中的估算值来检查统计数据的准确性。

    如果确定统计数据不够准确,则应增加 innodb_stats_persistent_sample_pages 的值,直到统计估算足够准确。然而,增加 innodb_stats_persistent_sample_pages 太多可能会导致 ANALYZE TABLE 运行缓慢。

  2. ANALYZE TABLE 运行太慢。在这种情况下,应将 innodb_stats_persistent_sample_pages 减少,直到 ANALYZE TABLE 的执行时间可接受。然而,将值减少太多可能会导致统计不准确和查询执行计划不佳的第一个问题。

    如果无法在准确统计和ANALYZE TABLE执行时间之间取得平衡,请考虑减少表中索引列的数量或限制分区数量以减少ANALYZE TABLE的复杂性。还需要考虑表主键中的列数,因为主键列会附加到每个非唯一索引上。

    有关相关信息,请参阅第 17.8.10.3 节,“InnoDB 表的 ANALYZE TABLE 复杂性估算”。

17.8.10.1.4 在持久统计计算中包含已标记删除的记录

默认情况下,InnoDB在计算统计信息时会读取未提交的数据。在未提交事务删除表中的行的情况下,计算行估计和索引统计时会排除已标记删除的记录,这可能导致使用除READ UNCOMMITTED之外的事务隔离级别并行操作表的其他事务出现非最佳执行计划。为避免这种情况,可以启用innodb_stats_include_delete_marked以确保在计算持久性优化器统计信息时包含已标记删除的记录。

当启用innodb_stats_include_delete_marked时,ANALYZE TABLE在重新计算统计信息时会考虑已标记删除的记录。

innodb_stats_include_delete_marked是一个影响所有InnoDB表的全局设置,仅适用于持久性优化器统计信息。

17.8.10.1.5 InnoDB 持久性统计表

持久性统计功能依赖于mysql数据库中的内部管理表,名称为innodb_table_statsinnodb_index_stats。这些表会在所有安装、升级和源代码构建过程中自动设置。

表 17.6 innodb_table_stats 的列

列名 描述
database_name 数据库名称
table_name 表名、分区名或子分区名
last_update 指示InnoDB上次更新此行的时间戳
n_rows 表中的行数
clustered_index_size 主索引的大小,以页为单位
sum_of_other_index_sizes 其他(非主键)索引的总大小,以页为单位

表 17.7 innodb_index_stats 的列

列名 描述
database_name 数据库名称
table_name 表名、分区名或子分区名
index_name 索引名称
last_update 指示上次更新行的时间戳
stat_name 统计信息的名称,其值在stat_value列中报告
stat_value stat_name列中命名的统计信息的值
sample_size 用于stat_value列中提供的估计值的页面样本数
stat_description stat_name列中命名的统计信息的描述

innodb_table_statsinnodb_index_stats表包括一个last_update列,显示索引统计信息上次更新的时间:

mysql> SELECT * FROM innodb_table_stats \G
*************************** 1\. row ***************************
           database_name: sakila
              table_name: actor
             last_update: 2014-05-28 16:16:44
                  n_rows: 200
    clustered_index_size: 1
sum_of_other_index_sizes: 1
...
mysql> SELECT * FROM innodb_index_stats \G
*************************** 1\. row ***************************
   database_name: sakila
      table_name: actor
      index_name: PRIMARY
     last_update: 2014-05-28 16:16:44
       stat_name: n_diff_pfx01
      stat_value: 200
     sample_size: 1
     ...

innodb_table_statsinnodb_index_stats表可以手动更新,这样可以强制执行特定的查询优化计划或测试替代计划而不修改数据库。如果手动更新统计信息,请使用FLUSH TABLE *tbl_name*语句加载更新后的统计信息。

持久统计被视为本地信息,因为它们与服务器实例相关。因此,在自动统计信息重新计算时,innodb_table_statsinnodb_index_stats表不会被复制。如果运行ANALYZE TABLE来启动统计信息的同步重新计算,该语句会被复制(除非你禁止了它的日志记录),并且重新计算会在副本上进行。

17.8.10.1.6 InnoDB 持久统计表示例

innodb_table_stats表中包含每个表的一行。以下示例演示了收集的数据类型。

t1包含一个主索引(列ab),一个次要索引(列cd),和一个唯一索引(列ef):

CREATE TABLE t1 (
a INT, b INT, c INT, d INT, e INT, f INT,
PRIMARY KEY (a, b), KEY i1 (c, d), UNIQUE KEY i2uniq (e, f)
) ENGINE=INNODB;

插入五行样本数据后,表t1如下所示:

mysql> SELECT * FROM t1;
+---+---+------+------+------+------+
| a | b | c    | d    | e    | f    |
+---+---+------+------+------+------+
| 1 | 1 |   10 |   11 |  100 |  101 |
| 1 | 2 |   10 |   11 |  200 |  102 |
| 1 | 3 |   10 |   11 |  100 |  103 |
| 1 | 4 |   10 |   12 |  200 |  104 |
| 1 | 5 |   10 |   12 |  100 |  105 |
+---+---+------+------+------+------+

要立即更新统计信息,运行ANALYZE TABLE(如果innodb_stats_auto_recalc已启用,则假定在几秒钟内自动更新统计信息,假设已达到更改表行的 10%阈值):

mysql> ANALYZE TABLE t1;
+---------+---------+----------+----------+
| Table   | Op      | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.t1 | analyze | status   | OK       |
+---------+---------+----------+----------+

t1的表统计信息显示了InnoDB上次更新表统计信息的时间(2014-03-14 14:36:34),表中的行数(5),聚集索引大小(1页),以及其他索引的组合大小(2页)。

mysql> SELECT * FROM mysql.innodb_table_stats WHERE table_name like 't1'\G
*************************** 1\. row ***************************
           database_name: test
              table_name: t1
             last_update: 2014-03-14 14:36:34
                  n_rows: 5
    clustered_index_size: 1
sum_of_other_index_sizes: 2

innodb_index_stats表对每个索引包含多行。innodb_index_stats表中的每一行提供与stat_name列中命名的特定索引统计信息相关的数据,并在stat_description列中描述。例如:

mysql> SELECT index_name, stat_name, stat_value, stat_description
       FROM mysql.innodb_index_stats WHERE table_name like 't1';
+------------+--------------+------------+-----------------------------------+
| index_name | stat_name    | stat_value | stat_description                  |
+------------+--------------+------------+-----------------------------------+
| PRIMARY    | n_diff_pfx01 |          1 | a                                 |
| PRIMARY    | n_diff_pfx02 |          5 | a,b                               |
| PRIMARY    | n_leaf_pages |          1 | Number of leaf pages in the index |
| PRIMARY    | size         |          1 | Number of pages in the index      |
| i1         | n_diff_pfx01 |          1 | c                                 |
| i1         | n_diff_pfx02 |          2 | c,d                               |
| i1         | n_diff_pfx03 |          2 | c,d,a                             |
| i1         | n_diff_pfx04 |          5 | c,d,a,b                           |
| i1         | n_leaf_pages |          1 | Number of leaf pages in the index |
| i1         | size         |          1 | Number of pages in the index      |
| i2uniq     | n_diff_pfx01 |          2 | e                                 |
| i2uniq     | n_diff_pfx02 |          5 | e,f                               |
| i2uniq     | n_leaf_pages |          1 | Number of leaf pages in the index |
| i2uniq     | size         |          1 | Number of pages in the index      |
+------------+--------------+------------+-----------------------------------+

stat_name列显示以下类型的统计信息:

  • size:当stat_name=size时,stat_value列显示索引中的总页数。

  • n_leaf_pages:当stat_name=n_leaf_pages时,stat_value列显示索引中叶子页的数量。

  • n_diff_pfx*NN*:当stat_name=n_diff_pfx01时,stat_value列显示索引的第一列中不同值的数量。当stat_name=n_diff_pfx02时,stat_value列显示索引的前两列中不同值的数量,依此类推。当stat_name=n_diff_pfx*NN*时,stat_description列显示一个逗号分隔的计数索引列的列表。

为了进一步说明提供基数数据的n_diff_pfx*NN*统计信息,再次考虑之前介绍的t1表示例。如下所示,t1表创建了一个主索引(列ab)、一个次要索引(列cd)和一个唯一索引(列ef):

CREATE TABLE t1 (
  a INT, b INT, c INT, d INT, e INT, f INT,
  PRIMARY KEY (a, b), KEY i1 (c, d), UNIQUE KEY i2uniq (e, f)
) ENGINE=INNODB;

插入五行样本数据后,表t1如下所示:

mysql> SELECT * FROM t1;
+---+---+------+------+------+------+
| a | b | c    | d    | e    | f    |
+---+---+------+------+------+------+
| 1 | 1 |   10 |   11 |  100 |  101 |
| 1 | 2 |   10 |   11 |  200 |  102 |
| 1 | 3 |   10 |   11 |  100 |  103 |
| 1 | 4 |   10 |   12 |  200 |  104 |
| 1 | 5 |   10 |   12 |  100 |  105 |
+---+---+------+------+------+------+

当查询index_namestat_namestat_valuestat_description,其中stat_name LIKE 'n_diff%'时,将返回以下结果集:

mysql> SELECT index_name, stat_name, stat_value, stat_description
       FROM mysql.innodb_index_stats
       WHERE table_name like 't1' AND stat_name LIKE 'n_diff%';
+------------+--------------+------------+------------------+
| index_name | stat_name    | stat_value | stat_description |
+------------+--------------+------------+------------------+
| PRIMARY    | n_diff_pfx01 |          1 | a                |
| PRIMARY    | n_diff_pfx02 |          5 | a,b              |
| i1         | n_diff_pfx01 |          1 | c                |
| i1         | n_diff_pfx02 |          2 | c,d              |
| i1         | n_diff_pfx03 |          2 | c,d,a            |
| i1         | n_diff_pfx04 |          5 | c,d,a,b          |
| i2uniq     | n_diff_pfx01 |          2 | e                |
| i2uniq     | n_diff_pfx02 |          5 | e,f              |
+------------+--------------+------------+------------------+

对于PRIMARY索引,有两个n_diff%行。行数等于索引中的列数。

注意

对于非唯一索引,InnoDB会附加主键的列。

  • index_name=PRIMARYstat_name=n_diff_pfx01时,stat_value1,表示索引的第一列(列a)中有一个单独的值。通过查看表t1中列a中的数据,确认列a中的不同值的数量,其中有一个单独的值(1)。计数的列(a)显示在结果集的stat_description列中。

  • index_name=PRIMARYstat_name=n_diff_pfx02时,stat_value5,表示索引的两列(a,b)中有五个不同的值。通过查看表t1中列ab中的数据,确认列ab中的不同值的数量,其中有五个不同的值:(1,1)、(1,2)、(1,3)、(1,4)和(1,5)。计数的列(a,b)显示在结果集的stat_description列中。

对于次要索引(i1),有四个n_diff%行。次要索引(c,d)仅定义了两列,但有四个n_diff%行,因为InnoDB会在所有非唯一索引后缀主键。因此,有四个n_diff%行,而不是两个,以考虑次要索引列(c,d)和主键列(a,b)。

  • index_name=i1stat_name=n_diff_pfx01时,stat_value1,表示索引的第一列(列c)中有一个单独的值。通过查看表t1中列c中的数据,确认列c中的不同值的数量,其中有一个单独的值:(10)。计数的列(c)显示在结果集的stat_description列中。

  • index_name=i1stat_name=n_diff_pfx02 时,stat_value2,表示索引(c,d)的前两列中有两个不同的值。在表 t1 中查看列 cd 的数据,可以确认有两个不同的值:(10,11) 和 (10,12)。计数的列(c,d)显示在结果集的 stat_description 列中。

  • index_name=i1stat_name=n_diff_pfx03 时,stat_value2,表示索引(c,d,a)的前三列中有两个不同的值。在表 t1 中查看列 cda 的数据,可以确认有两个不同的值:(10,11,1) 和 (10,12,1)。计数的列(c,d,a)显示在结果集的 stat_description 列中。

  • index_name=i1stat_name=n_diff_pfx04 时,stat_value5,表示索引(c,d,a,b)的四列中有五个不同的值。在表 t1 中查看列 cdab 的数据,可以确认有五个不同的值:(10,11,1,1)、(10,11,1,2)、(10,11,1,3)、(10,12,1,4) 和 (10,12,1,5)。计数的列(c,d,a,b)显示在结果集的 stat_description 列中。

对于唯一索引(i2uniq),有两行 n_diff%

  • index_name=i2uniqstat_name=n_diff_pfx01 时,stat_value2,表示索引(列 e)的第一列中有两个不同的值。在表 t1 中查看列 e 的数据,可以确认有两个不同的值:(100) 和 (200)。计数的列(e)显示在结果集的 stat_description 列中。

  • index_name=i2uniqstat_name=n_diff_pfx02 时,stat_value5,表示索引(e,f)的两列中有五个不同的值。在表 t1 中查看列 ef 的数据,可以确认有五个不同的值:(100,101)、(200,102)、(100,103)、(200,104) 和 (100,105)。计数的列(e,f)显示在结果集的 stat_description 列中。

17.8.10.1.7 使用 innodb_index_stats 表检索索引大小

您可以使用 innodb_index_stats 表检索表、分区或子分区的索引大小。在下面的示例中,检索了表 t1 的索引大小。有关表 t1 和相应索引统计信息的定义,请参见第 17.8.10.1.6 节,“InnoDB 持久性统计表示例”。

mysql> SELECT SUM(stat_value) pages, index_name,
       SUM(stat_value)*@@innodb_page_size size
       FROM mysql.innodb_index_stats WHERE table_name='t1'
       AND stat_name = 'size' GROUP BY index_name;
+-------+------------+-------+
| pages | index_name | size  |
+-------+------------+-------+
|     1 | PRIMARY    | 16384 |
|     1 | i1         | 16384 |
|     1 | i2uniq     | 16384 |
+-------+------------+-------+

对于分区或子分区,您可以使用相同的查询,只需修改WHERE子句以检索索引大小。例如,以下查询检索表t1的分区索引大小:

mysql> SELECT SUM(stat_value) pages, index_name,
       SUM(stat_value)*@@innodb_page_size size
       FROM mysql.innodb_index_stats WHERE table_name like 't1#P%'
       AND stat_name = 'size' GROUP BY index_name;

原文:dev.mysql.com/doc/refman/8.0/en/innodb-statistics-estimation.html

17.8.10.2 配置非持久性优化器统计参数

本节描述了如何配置非持久性优化器统计。当innodb_stats_persistent=OFF或当使用STATS_PERSISTENT=0创建或更改单个表时,优化器统计不会持久保存到磁盘。相反,统计信息存储在内存中,在服务器关闭时丢失。统计信息也会定期由某些操作和在某些条件下更新。

优化器统计默认持久保存到磁盘,由innodb_stats_persistent配置选项启用。有关持久性优化器统计的信息,请参见第 17.8.10.1 节,“配置持久性优化器统计参数”。

优化器统计更新

当非持久性优化器统计更新时:

  • 运行ANALYZE TABLE

  • 运行SHOW TABLE STATUSSHOW INDEX,或使用启用innodb_stats_on_metadata选项查询信息模式TABLESSTATISTICS表。

    innodb_stats_on_metadata的默认设置为OFF。启用innodb_stats_on_metadata可能会降低具有大量表或索引的模式的访问速度,并降低涉及InnoDB表的查询的执行计划的稳定性。通过使用SET语句在全局配置innodb_stats_on_metadata

    SET GLOBAL innodb_stats_on_metadata=ON
    

    注意

    当优化器统计配置为非持久性时,innodb_stats_on_metadata才适用(当禁用innodb_stats_persistent时)。

  • 使用启用了--auto-rehash选项的mysql客户端,这是默认设置。auto-rehash选项会导致所有InnoDB表被打开,并且打开表操作会导致统计信息被重新计算。

    为了提高mysql客户端的启动时间和更新统计信息,您可以使用--disable-auto-rehash选项关闭auto-rehashauto-rehash功能使交互用户可以自动完成数据库、表和列名的名称补全。

  • 首先打开一个表。

  • InnoDB检测到自上次更新统计信息以来表的 1/16 已被修改。

配置采样页面数

MySQL 查询优化器使用关于键分布的估计统计信息来选择执行计划的索引,基于索引的相对选择性。当InnoDB更新优化器统计信息时,它会从表的每个索引中随机采样页面,以估算索引的基数。(这种技术被称为随机潜水。)

为了让您控制统计信息估计的质量(从而为查询优化器提供更好的信息),您可以使用参数innodb_stats_transient_sample_pages更改采样页面数。默认的采样页面数为 8,这可能不足以产生准确的估算,导致查询优化器做出错误的索引选择。这种技术对于大表和用于连接的表尤为重要。对于这些表进行不必要的全表扫描可能会成为一个重大的性能问题。请参阅 Section 10.2.1.23, “避免全表扫描”以获取调整此类查询的提示。innodb_stats_transient_sample_pages是一个可以在运行时设置的全局参数。

innodb_stats_persistent=0时,innodb_stats_transient_sample_pages的值会影响所有InnoDB表和索引的索引采样。在更改索引采样大小时,请注意以下可能产生重大影响的情况:

  • 像 1 或 2 这样的小值可能导致基数的估计不准确。

  • 增加innodb_stats_transient_sample_pages的值可能需要更多的磁盘读取。比 8 大得多的值(比如 100),可能会导致打开表或执行SHOW TABLE STATUS的时间显著减慢。

  • 优化器可能会根据不同的索引选择性估计选择非常不同的查询计划。

无论对于系统来说,innodb_stats_transient_sample_pages的哪个值效果最好,都要设置该选项并将其保持在该值。选择一个能够为数据库中所有表提供合理准确估计而又不需要过多 I/O 的值。因为统计数据会在执行ANALYZE TABLE之外的各种时间自动重新计算,所以增加索引样本大小,运行ANALYZE TABLE,然后再减小样本大小是没有意义的。

较小的表通常需要比较少的索引样本。如果您的数据库有许多大表,考虑使用比大多数较小表更高的值innodb_stats_transient_sample_pages

原文:dev.mysql.com/doc/refman/8.0/en/innodb-analyze-table-complexity.html

17.8.10.3 估计 InnoDB 表的 ANALYZE TABLE 复杂性

ANALYZE TABLE复杂性对InnoDB表取决于:

  • 采样的页面数,由innodb_stats_persistent_sample_pages定义。

  • 表中索引列的数量

  • 分区的数量。如果表没有分区,则分区的数量被视为 1。

使用这些参数,估计ANALYZE TABLE复杂性的近似公式将是:

innodb_stats_persistent_sample_pages的值 * 表中索引列的数量 * 表中分区的数量

通常,结果值越大,执行ANALYZE TABLE所需的时间越长。

注意

innodb_stats_persistent_sample_pages定义了在全局级别采样的页面数。要为单个表设置采样页面数,请在CREATE TABLEALTER TABLE中使用STATS_SAMPLE_PAGES选项。更多信息,请参见第 17.8.10.1 节,“配置持久性优化器统计参数”。

如果innodb_stats_persistent=OFF,则采样的页面数由innodb_stats_transient_sample_pages定义。有关更多信息,请参见第 17.8.10.2 节,“配置非持久性优化器统计参数”。

要更深入地估计ANALYZE TABLE复杂性,请考虑以下示例。

大 O 符号中,ANALYZE TABLE的复杂性描述为:

 O(n_sample
  * (n_cols_in_uniq_i
     + n_cols_in_non_uniq_i
     + n_cols_in_pk * (1 + n_non_uniq_i))
  * n_part)

其中:

  • n_sample是采样的页面数(由innodb_stats_persistent_sample_pages定义)

  • n_cols_in_uniq_i是所有唯一索引中所有列的总数(不包括主键列)

  • n_cols_in_non_uniq_i是所有非唯一索引中所有列的总数

  • n_cols_in_pk是主键中的列数(如果未定义主键,则InnoDB在内部创建单列主键)

  • n_non_uniq_i是表中非唯一索引的数量

  • n_part是分区的数量。如果未定义分区,则认为表是单个分区。

现在,考虑以下表(表t),它具有主键(2 列),唯一索引(2 列)和两个非唯一索引(每个两列):

CREATE TABLE t (
  a INT,
  b INT,
  c INT,
  d INT,
  e INT,
  f INT,
  g INT,
  h INT,
  PRIMARY KEY (a, b),
  UNIQUE KEY i1uniq (c, d),
  KEY i2nonuniq (e, f),
  KEY i3nonuniq (g, h)
);

对于上述算法所需的列和索引数据,请查询表tmysql.innodb_index_stats持久性索引统计表。n_diff_pfx%统计数据显示了为每个索引计算的列。例如,主键索引计算了列ab。对于非唯一索引,除了用户定义的列外,还计算了主键列(a,b)。

注意

有关InnoDB持久性统计表的其他信息,请参见第 17.8.10.1 节,“配置持久性优化器统计参数”

mysql> SELECT index_name, stat_name, stat_description
       FROM mysql.innodb_index_stats WHERE
       database_name='test' AND
       table_name='t' AND
       stat_name like 'n_diff_pfx%';
 +------------+--------------+------------------+
 | index_name | stat_name    | stat_description |
 +------------+--------------+------------------+
 | PRIMARY    | n_diff_pfx01 | a                |
 | PRIMARY    | n_diff_pfx02 | a,b              |
 | i1uniq     | n_diff_pfx01 | c                |
 | i1uniq     | n_diff_pfx02 | c,d              |
 | i2nonuniq  | n_diff_pfx01 | e                |
 | i2nonuniq  | n_diff_pfx02 | e,f              |
 | i2nonuniq  | n_diff_pfx03 | e,f,a            |
 | i2nonuniq  | n_diff_pfx04 | e,f,a,b          |
 | i3nonuniq  | n_diff_pfx01 | g                |
 | i3nonuniq  | n_diff_pfx02 | g,h              |
 | i3nonuniq  | n_diff_pfx03 | g,h,a            |
 | i3nonuniq  | n_diff_pfx04 | g,h,a,b          |
 +------------+--------------+------------------+

根据上述索引统计数据和表定义,可以确定以下值:

  • n_cols_in_uniq_i,所有唯一索引中所有列的总数,不包括主键列,为 2(cd

  • n_cols_in_non_uniq_i,所有非唯一索引中所有列的总数为 4(efgh

  • n_cols_in_pk,主键中的列数为 2(ab

  • n_non_uniq_i,表中非唯一索引的数量为 2(i2nonuniqi3nonuniq

  • n_part,分区的数量为 1。

现在,您可以计算innodb_stats_persistent_sample_pages *(2 + 4 + 2 (1 + 2)) 1 来确定扫描的叶页数。假设innodb_stats_persistent_sample_pages设置为默认值20,默认页面大小为 16 KiBinnodb_page_size=16384),则可以估计对表t读取了 20 * 12 * 16384 bytes,约为 4 MiB

注意

可能并非从磁盘读取全部 4 MiB,因为一些叶页可能已经缓存在缓冲池中。

17.8.11 配置索引页面的合并阈值

原文:dev.mysql.com/doc/refman/8.0/en/index-page-merge-threshold.html

您可以配置索引页面的MERGE_THRESHOLD值。如果索引页面的“page-full”百分比低于MERGE_THRESHOLD值,当删除行或通过UPDATE操作缩短行时,InnoDB会尝试将索引页面与相邻的索引页面合并。默认的MERGE_THRESHOLD值为 50,这是先前硬编码的值。最小的MERGE_THRESHOLD值为 1,最大值为 50。

当索引页面的“page-full”百分比低于默认的MERGE_THRESHOLD设置值 50%时,InnoDB会尝试将索引页面与相邻页面合并。如果两个页面都接近 50%满,页面合并后很快可能会发生页面分裂。如果此合并-分裂行为频繁发生,可能会对性能产生不利影响。为避免频繁的合并-分裂,您可以降低MERGE_THRESHOLD值,以便InnoDB在较低的“page-full”百分比下尝试页面合并。在较低的页面满百分比下合并页面会在索引页面中留下更多空间,并有助于减少合并-分裂行为。

可以为表或单个索引定义索引页面的MERGE_THRESHOLD。为单个索引定义的MERGE_THRESHOLD值优先于为表定义的MERGE_THRESHOLD值。如果未定义,MERGE_THRESHOLD值默认为 50。

为表设置MERGE_THRESHOLD

您可以使用CREATE TABLE语句的table_option COMMENT子句为表设置MERGE_THRESHOLD值。例如:

CREATE TABLE t1 (
   id INT,
  KEY id_index (id)
) COMMENT='MERGE_THRESHOLD=45';

您还可以使用ALTER TABLEtable_option COMMENT子句为现有表设置MERGE_THRESHOLD值:

CREATE TABLE t1 (
   id INT,
  KEY id_index (id)
);

ALTER TABLE t1 COMMENT='MERGE_THRESHOLD=40';

为单个索引设置MERGE_THRESHOLD

要为单个索引设置MERGE_THRESHOLD值,可以使用CREATE TABLEALTER TABLECREATE INDEX中的index_option COMMENT子句,如下例所示:

  • 使用CREATE TABLE为单个索引设置MERGE_THRESHOLD

    CREATE TABLE t1 (
       id INT,
      KEY id_index (id) COMMENT 'MERGE_THRESHOLD=40'
    );
    
  • 使用ALTER TABLE为单个索引设置MERGE_THRESHOLD

    CREATE TABLE t1 (
       id INT,
      KEY id_index (id)
    );
    
    ALTER TABLE t1 DROP KEY id_index;
    ALTER TABLE t1 ADD KEY id_index (id) COMMENT 'MERGE_THRESHOLD=40';
    
  • 使用CREATE INDEX为单个索引设置MERGE_THRESHOLD

    CREATE TABLE t1 (id INT);
    CREATE INDEX id_index ON t1 (id) COMMENT 'MERGE_THRESHOLD=40';
    

注意

无法修改GEN_CLUST_INDEX的索引级别的MERGE_THRESHOLD值,GEN_CLUST_INDEXInnoDB在创建没有主键或唯一键索引的InnoDB表时创建的聚簇索引。只能通过为表设置MERGE_THRESHOLD来修改GEN_CLUST_INDEXMERGE_THRESHOLD值。

查询索引的MERGE_THRESHOLD

可通过查询INNODB_INDEXES表来获取索引的当前MERGE_THRESHOLD值。例如:

mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_INDEXES WHERE NAME='id_index' \G
*************************** 1\. row ***************************
       INDEX_ID: 91
           NAME: id_index
       TABLE_ID: 68
           TYPE: 0
       N_FIELDS: 1
        PAGE_NO: 4
          SPACE: 57
MERGE_THRESHOLD: 40

您可以使用SHOW CREATE TABLE来查看表的MERGE_THRESHOLD值,如果使用table_option COMMENT子句明确定义:

mysql> SHOW CREATE TABLE t2 \G
*************************** 1\. row ***************************
       Table: t2
Create Table: CREATE TABLE `t2` (
  `id` int(11) DEFAULT NULL,
  KEY `id_index` (`id`) COMMENT 'MERGE_THRESHOLD=40'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

注意

在索引级别定义的MERGE_THRESHOLD值优先于为表定义的MERGE_THRESHOLD值。如果未定义,MERGE_THRESHOLD默认为 50%(MERGE_THRESHOLD=50,这是先前硬编码的值。

同样,您可以使用SHOW INDEX来查看索引的MERGE_THRESHOLD值,如果使用index_option COMMENT子句明确定义:

mysql> SHOW INDEX FROM t2 \G
*************************** 1\. row ***************************
        Table: t2
   Non_unique: 1
     Key_name: id_index
 Seq_in_index: 1
  Column_name: id
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE
      Comment:
Index_comment: MERGE_THRESHOLD=40

测量MERGE_THRESHOLD设置的效果

INNODB_METRICS表提供两个计数器,可用于衡量MERGE_THRESHOLD设置对索引页面合并的影响。

mysql> SELECT NAME, COMMENT FROM INFORMATION_SCHEMA.INNODB_METRICS
       WHERE NAME like '%index_page_merge%';
+-----------------------------+----------------------------------------+
| NAME                        | COMMENT                                |
+-----------------------------+----------------------------------------+
| index_page_merge_attempts   | Number of index page merge attempts    |
| index_page_merge_successful | Number of successful index page merges |
+-----------------------------+----------------------------------------+

降低MERGE_THRESHOLD值时的目标是:

  • 较少数量的页面合并尝试和成功的页面合并

  • 一样数量的页面合并尝试和成功的页面合并

如果MERGE_THRESHOLD设置过小,可能会导致数据文件过大,因为空页面空间过多。

有关使用INNODB_METRICS计数器的信息,请参阅第 17.15.6 节,“InnoDB INFORMATION_SCHEMA Metrics Table”。

17.8.12 启用专用 MySQL 服务器的自动配置

原文:dev.mysql.com/doc/refman/8.0/en/innodb-dedicated-server.html

当启用innodb_dedicated_server时,InnoDB会自动配置以下变量:

  • innodb_buffer_pool_size

  • innodb_redo_log_capacity或在 MySQL 8.0.30 之前,innodb_log_file_sizeinnodb_log_files_in_group

    注意

    innodb_log_file_sizeinnodb_log_files_in_group在 MySQL 8.0.30 中已弃用。这些变量已被innodb_redo_log_capacity变量取代。

  • innodb_flush_method

只有在 MySQL 实例驻留在可以使用所有可用系统资源的专用服务器上时才考虑启用innodb_dedicated_server。例如,如果在仅运行 MySQL 的 Docker 容器或专用 VM 中运行 MySQL 服务器,则考虑启用innodb_dedicated_server。如果 MySQL 实例与其他应用程序共享系统资源,则不建议启用innodb_dedicated_server

接下���的信息描述了每个变量如何自动配置。

  • innodb_buffer_pool_size

    缓冲池大小根据服务器检测到的内存量进行配置。

    表 17.8 自动配置的缓冲池大小

    检测到的服务器内存 缓冲池大小
    小于 1GB 128MB(默认值)
    1GB 至 4GB 检测到的服务器内存 * 0.5
    大于 4GB 检测到的服务器内存 * 0.75
  • innodb_redo_log_capacity

    重做日志容量根据服务器检测到的内存量进行配置,并且在某些情况下,根据是否显式配置innodb_buffer_pool_size。如果没有显式配置innodb_buffer_pool_size,则假定使用默认值。

    警告

    如果innodb_buffer_pool_size设置为大于检测到的服务器内存量的值,则自动重做日志容量配置行为是未定义的。

    表 17.9 自动配置的日志文件大小

    检测到的服务器内存 缓冲池大小 重做日志容量
    小于 1GB 未配置 100MB
    小于 1GB 小于 1GB 100MB
    1GB 至 2GB 不适用 100MB
    2GB 至 4GB 未配置 1GB
    2GB 到 4GB 任何配置的值 round(0.5 * 检测到的服务器内存 in GB) * 0.5 GB
    4GB 到 10.66GB 不适用 round(0.75 * 检测到的服务器内存 in GB) * 0.5 GB
    10.66GB 到 170.66GB 不适用 round(0.5625 * 检测到的服务器内存 in GB) * 1 GB
    大于 170.66GB 不适用 128GB
  • innodb_log_file_size(在 MySQL 8.0.30 中已弃用)

    日志文件大小根据自动配置的缓冲池大小进行配置。

    表 17.10 自动配置的日志文件大小

    缓冲池大小 日志文件大小
    小于 8GB 512MB
    8GB 到 128GB 1024MB
    大于 128GB 2048MB
  • innodb_log_files_in_group(在 MySQL 8.0.30 中已弃用)

    日志文件数量根据自动配置的缓冲池大小进行配置。MySQL 8.0.14 中添加了自动配置innodb_log_files_in_group变量。

    表 17.11 自动配置的日志文件数量

    缓冲池大小 ��志文件数量
    小于 8GB round(缓冲池大小)
    8GB 到 128GB round(缓冲池大小 * 0.75)
    大于 128GB 64

    注意

    如果四舍五入的缓冲池大小值小于 2GB,则强制执行最小的innodb_log_files_in_group值为 2。

  • innodb_flush_method

    当启用innodb_dedicated_server时,刷新方法设置为O_DIRECT_NO_FSYNC。如果O_DIRECT_NO_FSYNC设置不可用,则使用默认的innodb_flush_method设置。

    InnoDB在刷新 I/O 期间使用O_DIRECT,但在每次写入操作后跳过fsync()系统调用。

    警告

    在 MySQL 8.0.14 之前,此设置不适用于需要fsync()系统调用来同步文件系统元数据更改的文件系统,如 XFS 和 EXT4。

    从 MySQL 8.0.14 开始,在创建新文件、增加文件大小和关闭文件后会调用fsync(),以确保文件系统元数据更改被同步。在每次写入操作后仍然跳过fsync()系统调用。

    如果重做日志文件和数据文件位于不同存储设备上,并且在数据文件写入从未备份电池支持的设备缓存之前发生意外退出,则可能会发生数据丢失。如果您使用或打算使用不同的存储设备用于重做日志文件和数据文件,并且您的数据文件位于没有备电池支持的设备缓存上,请改用O_DIRECT

如果自动配置的选项在选项文件或其他地方明确配置,则使用明确指定的设置,并且类似于以下内容的启动警告将打印到stderr

[警告] [000000] InnoDB: 由于明确指定了 innodb_buffer_pool_size=134217728,因此 innodb_dedicated_server 选项对 innodb_buffer_pool_size 无效。

对一个选项的显式配置不会阻止其他选项的自动配置。

如果启用了innodb_dedicated_server,并且明确配置了innodb_buffer_pool_size,则基于缓冲池大小配置的变量将使用根据服务器上检测到的内存量计算的缓冲池大小值,而不是明确定义的缓冲池大小值。

每次启动 MySQL 服务器时,自动配置的设置都会被评估和重新配置。

17.9 InnoDB 表和页面压缩

原文:dev.mysql.com/doc/refman/8.0/en/innodb-compression.html

17.9.1 InnoDB 表压缩

17.9.2 InnoDB 页面压缩

本节提供关于InnoDB表压缩和InnoDB页面压缩功能的信息。页面压缩功能也被称为透明页面压缩。

使用InnoDB的压缩功能,您可以创建数据以压缩形式存储的表。压缩有助于提高原始性能和可伸缩性。压缩意味着在磁盘和内存之间传输的数据量更少,并且在磁盘和内存中占用的空间更少。对于具有二级索引的表,好处尤为明显,因为索引数据也被压缩。压缩对于 SSD 存储设备尤为重要,因为它们的容量往往比 HDD 设备低。

17.9.1 InnoDB 表压缩

原文:dev.mysql.com/doc/refman/8.0/en/innodb-table-compression.html

17.9.1.1 表压缩概述

17.9.1.2 创建压缩表

17.9.1.3 调整 InnoDB 表的压缩

17.9.1.4 监控运行时的 InnoDB 表压缩

17.9.1.5 InnoDB 表压缩的工作原理

17.9.1.6 用于 OLTP 工作负载的压缩

17.9.1.7 SQL 压缩语法警告和错误

本节描述了InnoDB表压缩,支持InnoDB表位于 file_per_table 表空间或 general tablespaces 中的情况。表压缩是通过在CREATE TABLEALTER TABLE中使用ROW_FORMAT=COMPRESSED属性来启用的。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-compression-background.html

17.9.1.1 表压缩概述

因为处理器和缓存内存的速度增加比磁盘存储设备更快,许多工作负载是磁盘限制的。数据压缩可以使数据库大小更小,减少 I/O,并提高吞吐量,但会增加 CPU 利用率。压缩对于读密集型应用程序特别有价值,在具有足够 RAM 的系统上,可以将经常使用的数据保留在内存中。

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

压缩页面大小是通过CREATE TABLEALTER TABLEKEY_BLOCK_SIZE参数指定的。不同的页面大小要求表必须放置在 file-per-table 表空间或 general tablespace 中,而不是在 system tablespace 中,因为系统表空间无法存储压缩表。有关更多信息,请参见 Section 17.6.3.2, “File-Per-Table Tablespaces”和 Section 17.6.3.3, “General Tablespaces”。

无论KEY_BLOCK_SIZE值如何,压缩级别都是相同的。当您为KEY_BLOCK_SIZE指定较小的值时,您将获得越来越小页面的 I/O 优势。但是,如果指定的值太小,当数据值无法压缩到足以适应每个页面的多行时,重新组织页面会增加额外的开销。对于每个索引的关键列的长度,表的KEY_BLOCK_SIZE可以有一个硬限制。指定一个太小的值,CREATE TABLEALTER TABLE语句将失败。

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

原文:dev.mysql.com/doc/refman/8.0/en/innodb-compression-usage.html

17.9.1.2 创建压缩表

可以在 file-per-table 表空间或通用表空间中创建压缩表。表压缩不适用于 InnoDB 系统表空间。系统表空间(空间 0,.ibdata 文件)可以包含用户创建的表,但也包含内部系统数据,这些数据永远不会被压缩。因此,压缩仅适用于存储在文件表表空间或通用表空间中的表(和索引)。

在文件表表空间中创建压缩表

要在文件表表空间中创建压缩表,必须启用innodb_file_per_table(默认情况下)。您可以在 MySQL 配置文件(my.cnfmy.ini)中设置此参数,也可以使用SET语句动态设置。

配置innodb_file_per_table选项后,在CREATE TABLEALTER TABLE语句中指定ROW_FORMAT=COMPRESSED子句或KEY_BLOCK_SIZE子句,或两者都指定,以在文件表表空间中创建一个压缩表。

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

SET GLOBAL innodb_file_per_table=1;
CREATE TABLE t1
 (c1 INT PRIMARY KEY)
 ROW_FORMAT=COMPRESSED
 KEY_BLOCK_SIZE=8;
在通用表空间中创建压缩表

要在通用表空间中创建压缩表,必须为通用表空间定义FILE_BLOCK_SIZE,这在创建表空间时指定。FILE_BLOCK_SIZE值必须是相对于innodb_page_size值的有效压缩页大小,并且由CREATE TABLEALTER TABLEKEY_BLOCK_SIZE子句定义的压缩表的页大小必须等于FILE_BLOCK_SIZE/1024。例如,如果innodb_page_size=16384FILE_BLOCK_SIZE=8192,则表的KEY_BLOCK_SIZE必须为 8。有关更多信息,请参见第 17.6.3.3 节,“通用表空间”。

以下示例演示了创建通用表空间并添加压缩表。该示例假定默认的innodb_page_size为 16K。FILE_BLOCK_SIZE为 8192 要求压缩表具有KEY_BLOCK_SIZE为 8。

mysql> CREATE TABLESPACE `ts2` ADD DATAFILE 'ts2.ibd' FILE_BLOCK_SIZE = 8192 Engine=InnoDB;

mysql> CREATE TABLE t4 (c1 INT PRIMARY KEY) TABLESPACE ts2 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
注意
  • 截至 MySQL 8.0,压缩表的表空间文件是使用物理页大小而不是InnoDB页大小创建的,这使得空压缩表的初始表空间文件大小比以前的 MySQL 版本更小。

  • 如果指定ROW_FORMAT=COMPRESSED,可以省略KEY_BLOCK_SIZEKEY_BLOCK_SIZE设置默认为innodb_page_size值的一半。

  • 如果指定了有效的KEY_BLOCK_SIZE值,可以省略ROW_FORMAT=COMPRESSED;压缩会自动启用。

  • 要确定KEY_BLOCK_SIZE的最佳值,通常需要创建几个具有不同此子句值的相同表的副本,然后测量生成的.ibd文件的大小,并查看每个在实际工作负载中的表现如何。对于一般表空间,请记住删除表不会减小一般表空间.ibd文件的大小,也不会将磁盘空间返回给操作系统。有关更多信息,请参见第 17.6.3.3 节,“一般表空间”。

  • KEY_BLOCK_SIZE值被视为提示;如果需要,InnoDB可以使用不同的大小。对于每个表的文件表空间,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设置。有关更多信息,请参见第 17.6.3.3 节,“一般表空间”。

  • InnoDB支持 32KB 和 64KB 页大小,但这些页大小不支持压缩。有关更多信息,请参阅innodb_page_size文档。

  • InnoDB数据页的默认未压缩大小为 16KB。根据选项值的组合,MySQL 使用 1KB、2KB、4KB、8KB 或 16KB 的页大小用于表空间数据文件(.ibd文件)。实际的压缩算法不受KEY_BLOCK_SIZE值的影响;该值确定每个压缩块的大小,进而影响可以打包到每个压缩页中的行数。

  • 在文件表表空间中创建压缩表时,将 KEY_BLOCK_SIZE 设置为 InnoDB 页大小 通常不会产生太多压缩效果。例如,设置 KEY_BLOCK_SIZE=16 通常不会产生太多压缩效果,因为正常的 InnoDB 页大小为 16KB。对于具有许多长 BLOBVARCHARTEXT 列的表,此设置仍然可能很有用,因为这些值通常可以很好地压缩,因此可能需要较少的溢出页,如 Section 17.9.1.5, “How Compression Works for InnoDB Tables” 中所述。对于通用表空间,不允许将 KEY_BLOCK_SIZE 值设置为 InnoDB 页大小。有关更多信息,请参见 Section 17.6.3.3, “General Tablespaces”。

  • 表的所有索引(包括聚簇索引 语句的输出中)。

  • 有关与性能相关的配置选项,请参见 Section 17.9.1.3, “Tuning Compression for InnoDB Tables”。

压缩表的限制
  • 压缩表不能存储在 InnoDB 系统表空间中。

  • 通用表空间可以包含多个表,但压缩表和非压缩表不能共存于同一通用表空间中。

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

  • InnoDB 不支持压缩临时表。当启用 innodb_strict_mode(默认情况下)时,如果指定了 ROW_FORMAT=COMPRESSEDKEY_BLOCK_SIZE,则 CREATE TEMPORARY TABLE 会返回错误。如果禁用了 innodb_strict_mode,则会发出警告,并且临时表将使用非压缩的行格式创建。对临时表的 ALTER TABLE 操作也适用相同的限制。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-compression-tuning.html

17.9.1.3 调整 InnoDB 表的压缩

大多数情况下,InnoDB 数据存储和压缩中描述的内部优化确保系统能够很好地运行压缩数据。然而,由于压缩的效率取决于您的数据性质,您可以做出影响压缩表性能的决策:

  • 哪些表需要压缩。

  • 使用何种压缩页大小。

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

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

使用本节中的指南来帮助做出这些架构和配置选择。当您准备进行长期测试并将压缩表投入生产时,请参阅第 17.9.1.4 节,“监视 InnoDB 表在运行时的压缩”,以验证这些选择在实际条件下的有效性。

何时使用压缩

一般来说,压缩最适合包含合理数量的字符列并且数据读取频率远远高于写入频率的表。因为没有确切的方法来预测压缩是否对特定情况有益,所以始终要使用特定的工作负载和数据集在代表性配置上进行测试。在决定哪些表需要压缩时,请考虑以下因素。

数据特征和压缩

压缩在减小数据文件大小方面的效率的一个关键因素是数据本身的性质。请记住,压缩通过识别数据块中重复的字节字符串来工作。完全随机化的数据是最糟糕的情况。典型数据通常具有重复值,因此可以有效地压缩。字符字符串通常压缩得很好,无论是在CHARVARCHARTEXT还是BLOB列中定义。另一方面,包含大部分二进制数据(整数或浮点数)或先前压缩的数据(例如JPEGPNG图像)的表通常可能不会有效地压缩,或者根本不会压缩。

你可以选择是否为每个 InnoDB 表启用压缩。一个表及其所有索引使用相同的(压缩的)页面大小。也许主键(聚簇)索引,其中包含表的所有列的数据,比次要索引更有效地压缩。对于那些存在长行的情况,使用压缩可能导致长列值被存储在“页外”,如 DYNAMIC 行格式中所讨论的那样。这些溢出页面可能会压缩得很好。考虑到这些因素,对于许多应用程序,一些表比其他表更有效地压缩,你可能会发现你的工作负载只有在一部分表被压缩时才能表现最佳。

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

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

USE test;
SET GLOBAL innodb_file_per_table=1;
SET GLOBAL autocommit=0;

-- Create an uncompressed table with a million or two rows.
CREATE TABLE big_table AS SELECT * FROM information_schema.columns;
INSERT INTO big_table SELECT * FROM big_table;
INSERT INTO big_table SELECT * FROM big_table;
INSERT INTO big_table SELECT * FROM big_table;
INSERT INTO big_table SELECT * FROM big_table;
INSERT INTO big_table SELECT * FROM big_table;
INSERT INTO big_table SELECT * FROM big_table;
INSERT INTO big_table SELECT * FROM big_table;
INSERT INTO big_table SELECT * FROM big_table;
INSERT INTO big_table SELECT * FROM big_table;
INSERT INTO big_table SELECT * FROM big_table;
COMMIT;
ALTER TABLE big_table ADD id int unsigned NOT NULL PRIMARY KEY auto_increment;

SHOW CREATE TABLE big_table\G

select count(id) from big_table;

-- Check how much space is needed for the uncompressed table.
\! ls -l data/test/big_table.ibd

CREATE TABLE key_block_size_4 LIKE big_table;
ALTER TABLE key_block_size_4 key_block_size=4 row_format=compressed;

INSERT INTO key_block_size_4 SELECT * FROM big_table;
commit;

-- Check how much space is needed for a compressed table
-- with particular compression settings.
\! ls -l data/test/key_block_size_4.ibd

这个实验产生了以下数字,当然这些数字可能会有很大的变化,取决于你的表结构和数据:

-rw-rw----  1 cirrus  staff  310378496 Jan  9 13:44 data/test/big_table.ibd
-rw-rw----  1 cirrus  staff  83886080 Jan  9 15:10 data/test/key_block_size_4.ibd

要查看压缩是否对你特定的工作负载有效:

  • 对于简单的测试,使用一个没有其他压缩表的 MySQL 实例,并运行查询来访问信息模式INNODB_CMP表。

  • 对涉及多个压缩表的更复杂测试,运行查询来访问信息模式INNODB_CMP_PER_INDEX表。因为INNODB_CMP_PER_INDEX表中的统计信息收集起来很昂贵,你必须在查询该表之前启用配置选项innodb_cmp_per_index_enabled,并且你可能会将这样的测试限制在开发服务器或非关键复制服务器上。

  • 运行一些典型的 SQL 语句来测试你正在测试的压缩表。

  • 通过查询INFORMATION_SCHEMA.INNODB_CMPINFORMATION_SCHEMA.INNODB_CMP_PER_INDEX来检查成功压缩操作与总体压缩操作的比率,并比较COMPRESS_OPSCOMPRESS_OPS_OK

  • 如果高比例的压缩操作成功完成,该表可能是压缩的一个良好候选。

  • 如果你得到高比例的压缩失败,你可以根据 17.9.1.6 节,“OLTP 工作负载的压缩”中描述的方式调整innodb_compression_levelinnodb_compression_failure_threshold_pctinnodb_compression_pad_pct_max 选项,并尝试进一步的测试。

数据库压缩与应用程序压缩

决定是在应用程序中压缩数据还是在表中压缩数据;不要同时对相同数据使用两种类型的压缩。当你在应用程序中压缩数据并将结果存储在压缩表中时,额外的空间节省是极不可能的,双重压缩只会浪费 CPU 周期。

在数据库中进行压缩

启用时,MySQL 表压缩是自动的,并适用于所有列和索引值。列仍然可以使用LIKE等运算符进行测试,排序操作仍然可以使用索引,即使索引值已经被压缩。由于索引通常占数据库总大小的相当大比例,压缩可能会在存储、I/O 或处理器时间上带来显著的节省。压缩和解压缩操作发生在数据库服务器上,该服务器可能是一个强大的系统,大小适合处理预期的负载。

在应用程序中进行压缩

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

混合方法

当然,也可以结合这些方法。对于某些应用程序,可能适合使用一些压缩表和一些未压缩表。最好是对一些数据进行外部压缩(并将其存储在未压缩表中),并允许 MySQL 在应用程序中压缩(部分)其他表。一如既往,提前设计和实际测试对于做出正确决策是有价值的。

工作负载特征和压缩

除了选择哪些表进行压缩(以及页面大小)之外,工作负载是性能的另一个关键决定因素。如果应用程序主要由读操作组成,而不是更新操作,那么在索引页用完每页“修改日志”空间后,需要重新组织和重新压缩的页面就会减少。如果更新主要更改非索引列或包含BLOB或大字符串的列(恰好存储在“页外”),则压缩的开销可能是可以接受的。如果对表的唯一更改是使用单调递增主键进行的INSERT,并且几乎没有次要索引,那么几乎没有必要重新组织和重新压缩索引页。由于 MySQL 可以通过修改未压缩数据“原地”在压缩页面上“标记删除”和删除行,对表进行的DELETE操作相对高效。

对于某些环境来说,加载数据所需的时间与运行时检索一样重要。特别是在数据仓库环境中,许多表可能是只读或读取频繁的。在这种情况下,除非减少磁盘读取或存储成本的节省显著,否则在加载时间上付出压缩的代价可能是可以接受的,也可能不可接受。

从根本上讲,压缩在 CPU 时间可用于压缩和解压数据时效果最佳。因此,如果您的工作负载受限于 I/O,而不是 CPU,您可能会发现压缩可以提高整体性能。在测试不同压缩配置的应用程序性能时,请在与生产系统计划配置相似的平台上进行测试。

配置特性和压缩

从磁盘读取和写入数据库页是系统性能中最慢的部分。压缩试图通过使用 CPU 时间来压缩和解压数据来减少 I/O,并且在 I/O 相对稀缺的情况下,与处理器周期相比,效果最佳。

当在具有快速、多核 CPU 的多用户环境中运行时,通常尤其如此。当压缩表的一页在内存中时,MySQL 通常会使用额外的内存,通常为 16KB,在缓冲池中存储一页的未压缩副本。自适应 LRU 算法试图在压缩和未压缩页之间平衡内存使用,考虑到工作负载是以 I/O 为限还是以 CPU 为限。然而,与内存高度受限的配置相比,将更多内存专用于缓冲池的配置在使用压缩表时往往运行得更好。

选择压缩页大小

压缩页大小的最佳设置取决于表及其索引包含的数据类型和分布。压缩页大小应始终大于最大记录大小,否则操作可能会失败,如压缩 B-Tree 页中所述。

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

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

原文:dev.mysql.com/doc/refman/8.0/en/innodb-compression-tuning-monitoring.html

17.9.1.4 在运行时监控 InnoDB 表压缩

整体应用性能、CPU 和 I/O 利用率以及磁盘文件大小是衡量压缩对应用效果的良好指标。本节基于第 17.9.1.3 节“调整 InnoDB 表压缩”中的性能调优建议,展示如何发现在初始测试中可能不会出现的问题。

要深入了解压缩表的性能考虑,您可以使用信息模式表在运行时监控压缩性能,详情请参阅示例 17.1“使用压缩信息模式表”。这些表反映了内存的内部使用和整体使用的压缩率。

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 树 节点,因此比较“成功”压缩操作的数量与总体操作数量。根据 INNODB_CMPINNODB_CMP_PER_INDEX 表中的信息以及整体应用程序性能和硬件资源利用情况,你可能需要调整硬件配置、调整缓冲池大小、选择不同的页面大小或选择不同的要压缩的表。

如果压缩和解压所需的 CPU 时间较长,更换更快或多核 CPU 可以帮助提高性能,同时保持相同的数据、应用工作负载和一组压缩表。增加缓冲池的大小也可能有助于性能,这样更多未压缩的页面可以保留在内存中,减少只存在于内存中的压缩页面的解压需求。

如果整体压缩操作的数量(与应用程序中的INSERTUPDATEDELETE操作以及数据库大小相比)较多,可能表明一些被压缩的表正在过度更新,以至于无法有效压缩。如果是这样,选择更大的页面大小,或者更加谨慎地选择要压缩的表。

如果“成功”压缩操作(COMPRESS_OPS_OK)的数量占总压缩操作(COMPRESS_OPS)的比例很高,那么系统可能表现良好。如果比例较低,那么 MySQL 可能比理想情况下更频繁地重新组织、重新压缩和拆分 B 树节点。在这种情况下,避免压缩一些表,或者增加一些被压缩表的KEY_BLOCK_SIZE。您可能会关闭一些表的压缩,如果这些表导致应用程序中“压缩失败”的数量超过总数的 1%或 2%。(在诸如数据加载之类的临时操作期间,这种失败比率可能是可以接受的)。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-compression-internals.html

17.9.1.5 InnoDB 表的压缩工作原理

本节描述了 InnoDB 表的压缩的一些内部实现细节。这里提供的信息可能有助于性能调优,但对于基本的压缩使用并非必须了解。

压缩算法

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

MySQL 借助著名的zlib 库实现了压缩,该库实现了 LZ77 压缩算法。这种压缩算法成熟、稳健,并且在 CPU 利用率和数据大小减少方面非常高效。该算法是“无损”的,因此始终可以从压缩形式重构原始未压缩数据。LZ77 压缩通过查找在待压缩数据中重复的数据序列来工作。数据中的值模式决定了它的压缩效果,但典型的用户数据通常可以压缩 50%或更多。

与应用程序执行的压缩或其他一些数据库管理系统的压缩功能不同,InnoDB 压缩同时适用于用户数据和索引。在许多情况下,索引可以占据总数据库大小的 40-50%甚至更多,因此这种差异是显著的。当压缩对数据集有效时,InnoDB 数据文件的大小(每表一个文件表空间或通用表空间 .ibd文件)为未压缩大小的 25%至 50%甚至更小。根据工作负载的不同,这种较小的数据库可以进一步减少 I/O,提高吞吐量,以较小的 CPU 利用率成本。您可以通过修改innodb_compression_level配置选项来调整压缩级别和 CPU 开销之间的平衡。

InnoDB 数据存储和压缩

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

InnoDB 表中的二级索引也是 B 树,包含值对:索引键和指向聚簇索引中行的指针。该指针实际上是表的主键值,用于访问聚簇索引,如果需要除索引键和主键之外的列。二级索引记录必须始终适合单个 B 树页面。

B 树节点(包括聚簇索引和二级索引)的压缩处理与用于存储长 VARCHARBLOBTEXT 列的溢出页的压缩处理不同,如下节所述。

B 树页面的压缩

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

MySQL 使用的一种技术是在未压缩形式的 B 树节点中维护一些系统信息,从而方便某些原地更新。例如,这允许行被标记为删除并删除,而无需进行任何压缩操作。

另外,MySQL 尝试在更改索引页面时避免不必要的解压缩和重新压缩。在每个 B 树页面中,系统保留一个未压缩的“修改日志”来记录对页面所做的更改。小记录的更新和插入可以写入此修改日志,而无需完全重建整个页面。

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

为了避免在写密集型工作负载中频繁出现压缩失败,例如 OLTP 应用程序,MySQL 有时会在页面中保留一些空间(填充),以便修改日志更快地填满,并在仍有足够空间避免分割页面时重新压缩页面。每个页面中留下的填充空间量因系统跟踪页面分割频率而异。在频繁向压缩表写入的繁忙服务器上,您可以调整innodb_compression_failure_threshold_pctinnodb_compression_pad_pct_max配置选项来微调此机制。

通常,MySQL 要求 InnoDB 表中的每个 B 树页面至少能容纳两条记录。对于压缩表,此要求已经放宽。B 树节点的叶子页面(无论是主键还是辅助索引)只需要容纳一条记录,但该记录必须以未压缩形式适合于每页修改日志。如果innodb_strict_modeON,MySQL 在CREATE TABLECREATE INDEX期间检查最大行大小。如果行不适合,将发出以下错误消息:ERROR HY000: Too big row

如果在innodb_strict_mode为 OFF 时创建表,并且随后的INSERTUPDATE语句尝试创建一个在压缩页面大小内不适合的索引条目,则操作将失败,并显示ERROR 42000: Row size too large。(此错误消息不会指定记录过大的索引,也不会提及索引记录的长度或特定索引页面上的最大记录大小。)要解决此问题,请使用ALTER TABLE重建表,并选择更大的压缩页面大小(KEY_BLOCK_SIZE),缩短任何列前缀索引,或完全禁用压缩,使用ROW_FORMAT=DYNAMICROW_FORMAT=COMPACT

innodb_strict_mode不适用于通用表空间,通用表空间也支持压缩表。通用表空间的表空间管理规则严格执行,与innodb_strict_mode独立执行。有关更多信息,请参见第 15.1.21 节,“CREATE TABLESPACE Statement”。

压缩 BLOB、VARCHAR 和 TEXT 列

在 InnoDB 表中,不是主键的BLOBVARCHARTEXT列可能存储在单独分配的溢出页上。我们将这些列称为页外列。它们的值存储在溢出页的单链表上。

对于使用ROW_FORMAT=DYNAMICROW_FORMAT=COMPRESSED创建的表,根据它们的长度和整行的长度,BLOBTEXTVARCHAR列的值可能完全存储在页外。对于存储在页外的列,聚集索引记录仅包含指向溢出页的 20 字节指针,每列一个。是否存储任何列在页外取决于页大小和整行的总大小。当整行长度过长无法完全适应聚集索引页时,MySQL 会选择最长的列进行页外存储,直到整行适应聚集索引页。如上所述,如果一行无法适应压缩页,将会出现错误。

注意

对于使用ROW_FORMAT=DYNAMICROW_FORMAT=COMPRESSED创建的表,长度小于或等于 40 字节的TEXTBLOB列始终存储在内部。

使用ROW_FORMAT=REDUNDANTROW_FORMAT=COMPACT的表将BLOBVARCHARTEXT列的前 768 字节存储在聚集索引记录中,以及主键。768 字节前缀后跟一个指向包含列其余部分值的溢出页的 20 字节指针。

当表处于COMPRESSED格式时,写入溢出页的所有数据都会被压缩“原样”;也就是说,MySQL 会对整个数据项应用 zlib 压缩算法。除数据外,压缩的溢出页包含一个未压缩的页头和页尾,其中包括页校验和指向下一个溢出页的链接,等等。因此,如果数据高度可压缩,通常情况下文本数据会获得非常显著的存储节省,对于较长的BLOBTEXTVARCHAR列。图像数据,如JPEG,通常已经被压缩,因此将其存储在压缩表中并不会带来太大好处;双重压缩可能会浪费 CPU 周期而几乎不节省空间。

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

对于 file-per-table 表空间,使用 16K 压缩页大小可以减少BLOBVARCHARTEXT列的存储和 I/O 成本,因为这些数据通常压缩效果良好,可能需要较少的溢出页,即使 B 树节点本身占用的页数与未压缩形式相同。通用表空间不支持 16K 压缩页大小(KEY_BLOCK_SIZE)。有关更多信息,请参见 Section 17.6.3.3,“通用表空间”。

压缩和 InnoDB 缓冲池

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

为了最小化 I/O 并减少解压缩页的需求,有时缓冲池同时包含数据库页的压缩和未压缩形式。为了为其他所需的数据库页腾出空间,MySQL 可以从缓冲池中驱逐一个未压缩页,同时保留压缩页在内存中。或者,如果某个页已经有一段时间没有被访问,该页的压缩形式可能会被写入磁盘,以释放其他数据的空间。因此,在任何给定时间,缓冲池可能同时包含页的压缩和未压缩形式,或仅包含页的压缩形式,或两者都不包含。

MySQL 使用最近最少使用(LRU)列表跟踪要保留在内存中的页面和要驱逐的页面,以便热(频繁访问)数据倾向于保留在内存中。当访问压缩表时,MySQL 使用自适应 LRU 算法在内存中实现压缩和未压缩页面的适当平衡。这种自适应算法对系统是否以 I/O 绑定或 CPU 绑定方式运行敏感。目标是在 CPU 繁忙时避免花费过多的处理时间解压页面,并在 CPU 有空闲周期可以用于解压已经在内存中的压缩页面时避免做过多的 I/O。当系统处于 I/O 绑定状态时,该算法更倾向于驱逐页面的未压缩副本而不是两者,以便为其他磁盘页面腾出更多的内存空间。当系统处于 CPU 绑定状态时,MySQL 更倾向于同时驱逐压缩和未压缩页面,以便更多的内存用于“热”页面,并减少仅以压缩形式在内存中解压数据的需求。

压缩和 InnoDB 重做日志文件

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

要在每个表的表空间中创建一个压缩表,必须启用innodb_file_per_table。在创建一个压缩表时,不依赖于innodb_file_per_table设置在一般表空间中。有关更多信息,请参见第 17.6.3.3 节,“一般表空间”。

原文:dev.mysql.com/doc/refman/8.0/en/innodb-performance-compression-oltp.html

17.9.1.6 OLTP 工作负载的压缩

传统上,InnoDB的压缩功能主要推荐用于只读或读最多的工作负载,例如在数据仓库配置中。随着 SSD 存储设备的兴起,这些设备速度快但相对较小且昂贵,使压缩对于OLTP工作负载也变得有吸引力:高流量、互动式网站可以通过使用频繁进行INSERTUPDATEDELETE操作的应用程序与压缩表来减少其存储需求和每秒的 I/O 操作(IOPS)。

这些配置选项允许您调整压缩方式,以适应特定的 MySQL 实例,重点放在写入密集型操作的性能和可伸缩性上:

  • innodb_compression_level 允许您调整压缩程度。较高的值可以让您在存储设备上容纳更多数据,但会增加压缩时的 CPU 开销。较低的值可以减少 CPU 开销,当存储空间不是关键问题,或者您预计数据不容易压缩时。

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

  • innodb_compression_pad_pct_max 允许您调整每个页内保留的空间量,用于记录压缩行的更改,而无需重新压缩整个页面。值越高,可以记录更多更改而无需重新压缩页面。当运行时指定的压缩操作“失败”达到指定百分比时,MySQL 为每个压缩表内的页面使用可变数量的空闲空间,需要昂贵的操作来拆分压缩页面。

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

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

译文:dev.mysql.com/doc/refman/8.0/en/innodb-compression-syntax-warnings.html

17.9.1.7 SQL 压缩语法警告和错误

本节描述了在使用表格压缩功能时可能遇到的语法警告和错误,其中包括 file-per-table 表空间和 general tablespaces。

文件-每表表空间的 SQL 压缩语法警告和错误

innodb_strict_mode被启用(默认情况下),在CREATE TABLEALTER TABLE语句中指定ROW_FORMAT=COMPRESSEDKEY_BLOCK_SIZE会产生以下错误,如果innodb_file_per_table被禁用。

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

注意

如果当前配置不允许使用压缩表,表格将不会被创建。

innodb_strict_mode被禁用时,在CREATE TABLEALTER TABLE语句中指定ROW_FORMAT=COMPRESSEDKEY_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.                          |
+---------+------+---------------------------------------------------------------+

注意

这些消息只是警告,而不是错误,表格是在没有压缩的情况下创建的,就好像没有指定选项一样。

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

要将转储文件导入新数据库,并使表格按照原始数据库中的存在方式重新创建,请确保服务器具有innodb_file_per_table配置参数的正确设置。

属性KEY_BLOCK_SIZE仅在指定为COMPRESSED或省略时才被允许。在任何其他ROW_FORMAT中指定KEY_BLOCK_SIZE会生成一个警告,您可以通过SHOW WARNINGS查看。但是,表格是非压缩的;指定的KEY_BLOCK_SIZE会被忽略。

等级 代码 消息
警告 1478 InnoDB: ignoring KEY_BLOCK_SIZE=*n* unless ROW_FORMAT=COMPRESSED.

如果您启用了innodb_strict_mode,将KEY_BLOCK_SIZE与除COMPRESSED之外的任何ROW_FORMAT组合会产生错误,而不是警告,并且表格不会被创建。

表 17.12,“ROW_FORMAT 和 KEY_BLOCK_SIZE 选项”概述了与CREATE TABLEALTER TABLE一起使用的ROW_FORMATKEY_BLOCK_SIZE选项。

表 17.12 ROW_FORMAT 和 KEY_BLOCK_SIZE 选项

选项 使用说明 描述
ROW_FORMAT=​REDUNDANT MySQL 5.0.3 之前使用的存储格式 ROW_FORMAT=COMPACT效率低;用于向后兼容性
ROW_FORMAT=​COMPACT MySQL 5.0.3 以来的默认存储格式 在聚簇索引页中存储长列值的前 768 字节,其余字节存储在溢出页中
ROW_FORMAT=​DYNAMIC 如果适合,则在聚簇索引页内存储值;如果不适合,则仅存储一个 20 字节指向溢出页的指针(无前缀)
ROW_FORMAT=​COMPRESSED 使用 zlib 对表和索引进行压缩
KEY_BLOCK_​SIZE=*n* 指定压缩页面大小为 1、2、4、8 或 16 千字节;意味着ROW_FORMAT=COMPRESSED。对于一般表空间,不允许KEY_BLOCK_SIZE值等于InnoDB页面大小。

表 17.13,“在关闭 InnoDB 严格模式时 CREATE/ALTER TABLE 的警告和错误”总结了在CREATE TABLEALTER TABLE语句中某些配置参数和选项的特定组合导致的错误条件,以及这些选项在SHOW TABLE STATUS输出中的显示方式。

innodb_strict_modeOFF时,MySQL 创建或修改表,但会忽略如下设置。您可以在 MySQL 错误日志中看到警告消息。当innodb_strict_modeON时,这些指定的选项组合会生成错误,并且表不会被创建或修改。要查看错误条件的完整描述,请发出SHOW ERRORS语句:例如:

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) |
+-------+------+-------------------------------------------+

表 17.13 在关闭 InnoDB 严格模式时 CREATE/ALTER TABLE 的警告和错误

语法 警告或错误条件 SHOW TABLE STATUS中显示的ROW_FORMAT结果
ROW_FORMAT=REDUNDANT REDUNDANT
ROW_FORMAT=COMPACT COMPACT
指定了ROW_FORMAT=COMPRESSEDROW_FORMAT=DYNAMICKEY_BLOCK_SIZE 除非启用了innodb_file_per_table,否则对于每个表的文件表空间被忽略。通用表空间支持所有行格式。请参见第 17.6.3.3 节,“General Tablespaces”。 文件表空间的默认行格式;通用表空间的指定行格式
指定了无效的KEY_BLOCK_SIZE(不是 1、2、4、8 或 16) KEY_BLOCK_SIZE被忽略 指定的行格式,或默认行格式
指定了ROW_FORMAT=COMPRESSED和有效的KEY_BLOCK_SIZE 无;使用指定的KEY_BLOCK_SIZE COMPRESSED
指定了KEY_BLOCK_SIZEREDUNDANTCOMPACTDYNAMIC行格式 KEY_BLOCK_SIZE被忽略 REDUNDANTCOMPACTDYNAMIC
ROW_FORMAT不是REDUNDANTCOMPACTDYNAMICCOMPRESSED之一 如果被 MySQL 解析器识别则被忽略。否则,会发出错误。 默认行格式或 N/A

innodb_strict_modeON时,MySQL 会拒绝无效的ROW_FORMATKEY_BLOCK_SIZE参数并发出错误。严格模式默认为ON。当innodb_strict_modeOFF时,MySQL 对被忽略的无效参数发出警告而不是错误。

使用SHOW TABLE STATUS无法查看所选的KEY_BLOCK_SIZE。语句SHOW CREATE TABLE显示KEY_BLOCK_SIZE(即使在创建表时被忽略)。MySQL 无法显示表的真实压缩页大小。

通用表空间的 SQL 压缩语法警告和错误
  • 如果在创建通用表空间时未定义FILE_BLOCK_SIZE,则该表空间不能包含压缩表。如果尝试添加压缩表,则会返回错误,如下例所示:

    mysql> CREATE TABLESPACE `ts1` ADD DATAFILE 'ts1.ibd' Engine=InnoDB;
    
    mysql> CREATE TABLE t1 (c1 INT PRIMARY KEY) TABLESPACE ts1 ROW_FORMAT=COMPRESSED
           KEY_BLOCK_SIZE=8;
    ERROR 1478 (HY000): InnoDB: Tablespace `ts1` cannot contain a COMPRESSED table
    
  • 尝试向通用表空间添加具有无效KEY_BLOCK_SIZE的表会返回错误,如下例所示:

    mysql> CREATE TABLESPACE `ts2` ADD DATAFILE 'ts2.ibd' FILE_BLOCK_SIZE = 8192 Engine=InnoDB;
    
    mysql> CREATE TABLE t2 (c1 INT PRIMARY KEY) TABLESPACE ts2 ROW_FORMAT=COMPRESSED
           KEY_BLOCK_SIZE=4;
    ERROR 1478 (HY000): InnoDB: Tablespace `ts2` 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 `ts3` ADD DATAFILE 'ts3.ibd' FILE_BLOCK_SIZE = 8192 Engine=InnoDB;
    
    mysql> CREATE TABLE t3 (c1 INT PRIMARY KEY) TABLESPACE ts3 ROW_FORMAT=COMPACT;
    ERROR 1478 (HY000): InnoDB: Tablespace `ts3` uses block size 8192 and cannot
    contain a table with physical page size 16384
    

innodb_strict_mode不适用于通用表空间。通用表空间的表空间管理规则严格执行,与innodb_strict_mode无关。有关更多信息,请参见第 15.1.21 节,“CREATE TABLESPACE Statement”。

有关在通用表空间中使用压缩表的更多信息,请参阅第 17.6.3.3 节,“通用表空间”。

17.9.2 InnoDB 页面压缩

原文:dev.mysql.com/doc/refman/8.0/en/innodb-page-compression.html

InnoDB支持位于 file-per-table 表空间中的表的页面级压缩。此功能称为透明页压缩。通过在CREATE TABLEALTER TABLE中指定COMPRESSION属性来启用页面压缩。支持的压缩算法包括ZlibLZ4

支持的平台

页面压缩需要稀疏文件和空洞打孔支持。在 Windows 上,页面压缩受 NTFS 支持,在以下 MySQL 支持的 Linux 平台子集上,内核级别提供空洞打孔支持:

  • RHEL 7 及使用内核版本 3.10.0-123 或更高的衍生发行版

  • OEL 5.10(UEK2)内核版本 2.6.39 或更高

  • OEL 6.5(UEK3)内核版本 3.8.13 或更高

  • OEL 7.0 内核版本 3.8.13 或更高

  • SLE11 内核版本 3.0-x

  • SLE12 内核版本 3.12-x

  • OES11 内核版本 3.0-x

  • Ubuntu 14.0.4 LTS 内核版本 3.13 或更高

  • Ubuntu 12.0.4 LTS 内核版本 3.2 或更高

  • Debian 7 内核版本 3.2 或更高

注意

给定 Linux 发行版的所有可用文件系统可能不支持空洞打孔。

页面压缩的工作原理

当写入页面时,使用指定的压缩算法对其进行压缩。压缩后的数据写入磁盘,其中空洞打孔机制释放页面末尾的空块。如果压缩失败,则按原样写出数据。

Linux 系统上的空洞打孔大小

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

Windows 系统上的空洞打孔大小

在 Windows 系统上,稀疏文件的基础架构基于 NTFS 压缩。空洞打孔大小是 NTFS 压缩单元,它是 NTFS 群集大小的 16 倍。群集大小及其压缩单元如下表所示:

表 17.14 Windows NTFS 群集大小和压缩单元

群集大小 压缩单元
512 字节 8 KB
1 KB 16 KB
2 KB 32 KB
4 KB 64 KB

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

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

要使页面压缩在 Windows 上工作,文件系统必须使用小于 4K 的簇大小,并且innodb_page_size必须至少是压缩单元大小的两倍。例如,要使页面压缩在 Windows 上工作,可以使用 512 字节的簇大小(具有 8KB 的压缩单元)构建文件系统,并使用大于 16K 的innodb_page_size值初始化InnoDB

启用页面压缩

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

CREATE TABLE t1 (c1 INT) COMPRESSION="zlib";

您还可以在ALTER TABLE语句中启用页面压缩。但是,ALTER TABLE ... COMPRESSION仅更新表空间压缩属性。设置新的压缩算法后对表空间的写入使用新设置,但要将新的压缩算法应用于现有页面,必须使用OPTIMIZE TABLE重建表。

ALTER TABLE t1 COMPRESSION="zlib";
OPTIMIZE TABLE t1;

禁用页面压缩

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

ALTER TABLE t1 COMPRESSION="None";
OPTIMIZE TABLE t1;

页面压缩元数据

页面压缩元数据位于信息模式INNODB_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)。一个表可能包含具有不同压缩设置的页面。

在以下示例中,从信息模式 INNODB_TABLESPACES 表中检索了员工表的页面压缩元数据。

# Create the employees table with Zlib page compression

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";

# Insert data (not shown)

# Query page compression metadata in INFORMATION_SCHEMA.INNODB_TABLESPACES

mysql> SELECT SPACE, NAME, FS_BLOCK_SIZE, FILE_SIZE, ALLOCATED_SIZE FROM
       INFORMATION_SCHEMA.INNODB_TABLESPACES WHERE NAME='employees/employees'\G
*************************** 1\. row ***************************
SPACE: 45
NAME: employees/employees
FS_BLOCK_SIZE: 4096
FILE_SIZE: 23068672
ALLOCATED_SIZE: 19415040

员工表的页面压缩元数据显示,表面文件大小为 23068672 字节,而实��文件大小(带有页面压缩)为 19415040 字节。文件系统块大小为 4096 字节,这是用于打孔的块大小。

识别使用页面压缩的表

要识别启用页面压缩的表,可以检查信息模式 TABLES 表的 CREATE_OPTIONS 列,查看定义了 COMPRESSION 属性的表:

mysql> SELECT TABLE_NAME, TABLE_SCHEMA, CREATE_OPTIONS FROM INFORMATION_SCHEMA.TABLES 
       WHERE CREATE_OPTIONS LIKE '%COMPRESSION=%';
+------------+--------------+--------------------+
| TABLE_NAME | TABLE_SCHEMA | CREATE_OPTIONS     |
+------------+--------------+--------------------+
| employees  | test         | COMPRESSION="zlib" |
+------------+--------------+--------------------+

SHOW CREATE TABLE 还显示了如果使用的话的 COMPRESSION 属性。

页面压缩的限制和使用注意事项

  • 如果文件系统块大小(或 Windows 上的压缩单元大小)* 2 > innodb_page_size,则禁用页面压缩。

  • 不支持位于共享表空间中的表进行页面压缩,这包括系统表空间、临时表空间和一般表空间。

  • 不支持撤销日志表空间进行页面压缩。

  • 不支持重做日志页面进行页面压缩。

  • 用于空间索引的 R 树页面不进行压缩。

  • 属于压缩表 (ROW_FORMAT=COMPRESSED) 的页面保持不变。

  • 在恢复期间,更新的页面以未压缩形式写出。

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

  • 在降级到不支持页面压缩的早期版本的 MySQL 之前,需要取消对使用页面压缩功能的表进行解压缩。要对表进行解压缩,运行 ALTER TABLE ... COMPRESSION=NoneOPTIMIZE TABLE

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

  • 将一个页面压缩的表空间文件从一个主机移动到另一个主机时,需要使用一个能保留稀疏文件的实用程序来保留页面压缩。

  • 在 Fusion-io 硬件上使用 NVMFS 可以实现更好的页面压缩效果,因为 NVMFS 设计用于利用打孔功能。

  • 使用具有较大InnoDB页面大小和相对较小文件系统块大小的页面压缩功能可能导致写放大。例如,具有 64KB 的最大InnoDB页面大小和 4KB 文件系统块大小可能会提高压缩率,但也可能增加对缓冲池的需求,导致增加的 I/O 和潜在的写放大。

17.10 InnoDB 行格式

原文:dev.mysql.com/doc/refman/8.0/en/innodb-row-format.html

表的行格式决定了其行是如何物理存储的,进而影响查询和 DML 操作的性能。当更多行适合存储在单个磁盘页中时,查询和索引查找可以更快地工作,缓冲池中需要更少的缓存内存,并且写出更新值所需的 I/O 也更少。

每个表中的数据被分成页。构成每个表的页被排列在一种称为 B 树索引的树数据结构中。表数据和辅助索引都使用这种类型的结构。代表整个表的 B 树索引被称为聚簇索引,根据主键列组织。聚簇索引数据结构的节点包含行中所有列的值。辅助索引结构的节点包含索引列和主键列的值。

变长列是存储在 B 树索引节点中的列值的一个例外。太长以至于无法适应 B 树页的变长列存储在称为溢出页的单独分配的磁盘页上。这样的列被称为离页列。离页列的值存储在溢出页的单链列表中,每个这样的列都有自己的一个或多个溢出页列表。根据列长度,变长列值的全部或前缀存储在 B 树中,以避免浪费存储空间并且需要读取一个单独的页。

InnoDB 存储引擎支持四种行格式:冗余紧凑动态压缩

表 17.15 InnoDB 行格式概述

行格式 紧凑存储特性 增强的可变长度列存储 大索引键前缀支持 压缩支持 支持的表空间类型
冗余 系统,每表一个文件,通用
紧凑 系统,每表一个文件,通用
动态 系统,每表一个文件,通用
压缩 每表一个文件,通用

接下来的主题描述了行格式存储特性以及如何定义和确定表的行格式。

  • 冗余行格式

  • 紧凑行格式

  • 动态行格式

  • 压缩行格式

  • 定义表的行格式

  • 确定表的行格式

REDUNDANT 行格式

REDUNDANT格式与 MySQL 的旧版本兼容。

使用REDUNDANT行格式的表将变长列值(VARCHARVARBINARYBLOBTEXT类型)的前 768 字节存储在 B 树节点内的索引记录中,其余部分存储在溢出页上。大于或等于 768 字节的固定长度列被编码为变长列,可以存储在页外。例如,如果字符集的最大字节长度大于 3,CHAR(255)列可能超过 768 字节,就像utf8mb4一样。

如果列的值为 768 字节或更少,则不使用溢出页,可能会节省一些 I/O,因为该值完全存储在 B 树节点中。这对于相对较短的BLOB列值效果很好,但可能导致 B 树节点填满数据而不是键值,降低其效率。具有许多BLOB列的表可能导致 B 树节点变得过满,并包含太少行,使整个索引比行较短或列值存储在页外时效率低。

REDUNDANT 行格式存储特性

REDUNDANT行格式具有以下存储特性:

  • 每个索引记录包含一个 6 字节的头部。头部用于链接连续的记录,并用于行级锁定。

  • 聚簇索引中的记录包含所有用户定义的列字段。此外,还有一个 6 字节的事务 ID 字段和一个 7 字节的回滚指针字段。

  • 如果表没有定义主键,则每个聚簇索引记录还包含一个 6 字节的行 ID 字段。

  • 每个二级索引记录包含所有未在二级索引中的聚簇索引键定义的主键列。

  • 记录包含对记录的每个字段的指针。如果记录中字段的总长度小于 128 字节,则指针为一个字节;否则为两个字节。指针数组称为记录目录。指针指向的区域是记录的数据部分。

  • 在内部,像CHAR(10)这样的固定长度字符列以固定长度格式存储。在VARCHAR列中不会截断尾随空格。

  • 大于或等于 768 字节的固定长度列被编码为变长列,可以存储在页外。例如,如果字符集的最大字节长度大于 3,CHAR(255)列可能超过 768 字节,就像utf8mb4一样。

  • SQL 中的NULL值在记录目录中保留一到两个字节。如果存储在可变长度列中,则 SQL 中的NULL值在记录的数据部分中不保留任何字节。对于固定长度列,列的固定长度在记录的数据部分中保留。为NULL值保留固定空间允许列在位更新为非NULL值而不会导致索引页碎片化。

紧凑行格式

REDUNDANT行格式相比,COMPACT行格式将行存储空间减少约 20%,但会增加某些操作的 CPU 使用率。如果您的工作负载是受缓存命中率和磁盘速度限制的典型工作负载,COMPACT格式可能更快。如果工作负载受 CPU 速度限制,则紧凑格式可能较慢。

使用COMPACT行格式的表将变长列值(VARCHARVARBINARYBLOBTEXT类型)的前 768 字节存储在 B 树节点内的索引记录中,其余部分存储在溢出页上。大于或等于 768 字节的固定长度列被编码为可变长度列,可以存储在页外。例如,如果字符集的最大字节长度大于 3,那么CHAR(255)列可能超过 768 字节,就像utf8mb4一样。

如果列的值为 768 字节或更少,则不使用溢出页,可能会节省一些 I/O,因为该值完全存储在 B 树节点中。这对于相对较短的BLOB列值效果很好,但可能导致 B 树节点填满数据而不是键值,降低其效率。具有许多BLOB列的表可能导致 B 树节点变得过满,并包含太少行,使整个索引比行较短或列值存储在页外时效率低。

紧凑行格式存储特性

COMPACT行格式具有以下存储特性:

  • 每个索引记录包含一个 5 字节的头部,可能在可变长度头部之前。头部用于链接连续记录,并用于行级锁定。

  • 记录头部的可变长度部分包含一个位向量,用于指示NULL列。如果索引中可以为NULL的列数为N,则位向量占用CEILING(*N*/8)字节。(例如,如果可以为NULL的列数为 9 到 16 列,位向量将使用两个字节。)NULL列不占用除此向量中的位之外的空间。头部的可变长度部分还包含可变长度列的长度。每个长度占用一到两个字节,取决于列的最大长度。如果索引中的所有列都是NOT NULL且具有固定长度,则记录头部没有可变长度部分。

  • 对于每个非NULL可变长度字段,记录头部包含列的长度,使用一到两个字节。只有当列的一部分存储在溢出页中或最大长度超过 255 字节且实际长度超过 127 字节时,才需要两个字节。对于外部存储的列,2 字节长度表示内部存储部分的长度加上指向外部存储部分的 20 字节指针。内部部分为 768 字节,因此长度为 768+20。20 字节指针存储列的真实长度。

  • 记录头部后跟非NULL列的数据内容。

  • 聚集索引中的记录包含所有用户定义的列。此外,还有一个 6 字节的事务 ID 字段和一个 7 字节的回滚指针字段。

  • 如果表没有定义主键,则每个聚集索引记录还包含一个 6 字节的行 ID 字段。

  • 每个二级索引记录包含所有聚集索引键中定义的主键列,这些列不在二级索引中。如果任何主键列是可变长度的,则每个二级索引的记录头部都有一个可变长度部分来记录它们的长度,即使二级索引是在固定长度列上定义的。

  • 在内部,对于非可变长度字符集,如CHAR(10)这样的固定长度字符列以固定长度格式存储。

    VARCHAR列不会截断尾随空格。

  • 在内部,对于可变长度字符集,如utf8mb3utf8mb4InnoDB尝试将CHAR(*N*)存储为N字节,通过修剪尾随空格。如果CHAR(*N*)列值的字节长度超过N字节,则尾随空格将被修剪至列值字节长度的最大值。CHAR(*N*)列的最大长度为最大字符字节长度×N

    CHAR(*N*)保留了最小的N字节。在许多情况下,保留最小空间N可以使列更新就地完成,而不会导致索引页碎片化。相比之下,使用REDUNDANT行格式时,CHAR(*N*)列占用最大字符字节长度×N

    长度大于或等于 768 字节的固定长度列被编码为变长字段,可以存储在页外。例如,如果字符集的最大字节长度大于 3,那么CHAR(255)列可以超过 768 字节,就像utf8mb4一样。

动态行格式

DYNAMIC行格式提供了与COMPACT行格式相同的存储特性,但增加了对长变长列的增强存储能力,并支持大型索引键前缀。

当使用ROW_FORMAT=DYNAMIC创建表时,InnoDB可以将长变长列值(如VARCHARVARBINARYBLOB以及TEXT类型)完全存储在页外,聚簇索引记录仅包含指向溢出页的 20 字节指针。长度大于或等于 768 字节的固定长度字段被编码为变长字段。例如,如果字符集的最大字节长度大于 3,那么CHAR(255)列可以超过 768 字节,就像utf8mb4一样。

列是否存储在页外取决于页大小和行的总大小。当行过长时,会选择最长的列进行页外存储,直到聚簇索引记录适合于 B 树页。长度小于或等于 40 字节的TEXTBLOB列存储在行内。

DYNAMIC行格式保持了在索引节点中存储整个行的效率(与COMPACTREDUNDANT格式一样),但DYNAMIC行格式避免了将 B 树节点填充大量长列数据字节的问题。DYNAMIC行格式基于这样一个思想,即如果长数据值的一部分存储在页外,通常最有效的方式是将整个值存储在页外。使用DYNAMIC格式,较短的列可能会保留在 B 树节点中,最大程度地减少给定行所需的溢出页数量。

DYNAMIC行格式支持索引键前缀长达 3072 字节。

使用 DYNAMIC 行格式的表可以存储在系统表空间、每表一个表空间和通用表空间中。要将 DYNAMIC 表存储在系统表空间中,要么禁用innodb_file_per_table并使用常规的 CREATE TABLEALTER TABLE 语句,要么在 CREATE TABLEALTER TABLE 中使用 TABLESPACE [=] innodb_system 表选项。innodb_file_per_table 变量不适用于通用表空间,也不适用于使用 TABLESPACE [=] innodb_system 表选项将 DYNAMIC 表存储在系统表空间中。

DYNAMIC 行格式存储特性

DYNAMIC 行格式是 COMPACT 行格式的一种变体。有关存储特性,请参阅 COMPACT 行格式存储特性。

压缩行格式

COMPRESSED 行格式提供了与 DYNAMIC 行格式相同的存储特性和功能,但增加了对表和索引数据压缩的支持。

COMPRESSED 行格式使用与 DYNAMIC 行格式相似的内部细节进行页外存储,同时由于表和索引数据被压缩并使用较小的页大小,还有额外的存储和性能考虑。对于 COMPRESSED 行格式,KEY_BLOCK_SIZE 选项控制存储在聚簇索引中的列数据量,以及放置在溢出页上的数据量。有关 COMPRESSED 行格式的更多信息,请参阅第 17.9 节,“InnoDB 表和页压缩”。

COMPRESSED 行格式支持索引键前缀长达 3072 字节。

使用 COMPRESSED 行格式的表可以在每表一个表空间或通用表空间中创建。系统表空间不支持 COMPRESSED 行格式。要将 COMPRESSED 表存储在每表一个表空间中,必须启用innodb_file_per_table变量。innodb_file_per_table变量不适用于通用表空间。通用表空间支持所有行格式,但由于不同的物理页大小,压缩和非压缩表不能共存于同一通用表空间中。有关更多信息,请参阅第 17.6.3.3 节,“通用表空间”。

压缩行格式存储特性

COMPRESSED 行格式是 COMPACT 行格式的一种变体。有关存储特性,请参阅 COMPACT 行格式存储特性。

定义表的行格式

InnoDB表的默认行格式由innodb_default_row_format变量定义,默认值为DYNAMIC。当未明确定义ROW_FORMAT表选项或指定ROW_FORMAT=DEFAULT时,将使用默认行格式。

表格的行格式可以在CREATE TABLEALTER TABLE语句中通过ROW_FORMAT表选项明确定义。例如:

CREATE TABLE t1 (c1 INT) ROW_FORMAT=DYNAMIC;

明确定义的ROW_FORMAT设置会覆盖默认行格式。指定ROW_FORMAT=DEFAULT等同于使用隐式默认值。

innodb_default_row_format变量可以动态设置:

mysql> SET GLOBAL innodb_default_row_format=DYNAMIC;

有效的innodb_default_row_format选项包括DYNAMICCOMPACTREDUNDANT。不支持在系统表空间中使用的COMPRESSED行格式不能定义为默认值。只能在CREATE TABLEALTER TABLE语句中明确定义。尝试将innodb_default_row_format变量设置为COMPRESSED会返回错误:

mysql> SET GLOBAL innodb_default_row_format=COMPRESSED;
ERROR 1231 (42000): Variable 'innodb_default_row_format'
can't be set to the value of 'COMPRESSED'

新创建的表在未明确指定ROW_FORMAT选项或使用ROW_FORMAT=DEFAULT时,将使用由innodb_default_row_format变量定义的行格式。例如,以下CREATE TABLE语句使用由innodb_default_row_format变量定义的行格式。

CREATE TABLE t1 (c1 INT);
CREATE TABLE t2 (c1 INT) ROW_FORMAT=DEFAULT;

当未明确指定ROW_FORMAT选项,或使用ROW_FORMAT=DEFAULT时,重建表的操作会静默更改表的行格式为由innodb_default_row_format变量定义的格式。

表重建操作包括使用ALGORITHM=COPYALGORITHM=INPLACEALTER TABLE操作,其中需要重建表。有关更多信息,请参见第 17.12.1 节,“在线 DDL 操作”。OPTIMIZE TABLE也是一种表重建操作。

以下示例演示了一种表重建操作,静默更改了未明确定义行格式的表的行格式。

mysql> SELECT @@innodb_default_row_format;
+-----------------------------+
| @@innodb_default_row_format |
+-----------------------------+
| dynamic                     |
+-----------------------------+

mysql> CREATE TABLE t1 (c1 INT);

mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME LIKE 'test/t1' \G
*************************** 1\. row ***************************
     TABLE_ID: 54
         NAME: test/t1
         FLAG: 33
       N_COLS: 4
        SPACE: 35
   ROW_FORMAT: Dynamic
ZIP_PAGE_SIZE: 0
   SPACE_TYPE: Single 
mysql> SET GLOBAL innodb_default_row_format=COMPACT;

mysql> ALTER TABLE t1 ADD COLUMN (c2 INT);

mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME LIKE 'test/t1' \G
*************************** 1\. row ***************************
     TABLE_ID: 55
         NAME: test/t1
         FLAG: 1
       N_COLS: 5
        SPACE: 36
   ROW_FORMAT: Compact
ZIP_PAGE_SIZE: 0
   SPACE_TYPE: Single

在将现有表格的行格式从REDUNDANTCOMPACT更改为DYNAMIC之前,请考虑以下潜在问题。

  • REDUNDANTCOMPACT行格式支持最大索引键前缀长度为 767 字节,而DYNAMICCOMPRESSED行格式支持索引键前缀长度为 3072 字节。在复制环境中,如果源上的innodb_default_row_format变量设置为DYNAMIC,并在副本上设置为COMPACT,则以下 DDL 语句,在源上成功但在副本上失败,因为未明确定义行格式:

    CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 VARCHAR(5000), KEY i1(c2(3070)));
    

    有关相关信息,请参见第 17.22 节,“InnoDB 限制”。

  • 导入未明确定义行格式的表,如果源服务器上的innodb_default_row_format设置与目标服务器上的设置不同,则会导致模式不匹配错误。有关更多信息,请参见第 17.6.1.3 节,“导入 InnoDB 表”。

确定表的行格式

要确定表的行格式,请使用SHOW TABLE STATUS

mysql> SHOW TABLE STATUS IN test1\G
*************************** 1\. row ***************************
           Name: t1
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 0
 Avg_row_length: 0
    Data_length: 16384
Max_data_length: 0
   Index_length: 16384
      Data_free: 0
 Auto_increment: 1
    Create_time: 2016-09-14 16:29:38
    Update_time: NULL
     Check_time: NULL
      Collation: utf8mb4_0900_ai_ci
       Checksum: NULL
 Create_options:
        Comment:

或者,查询信息模式INNODB_TABLES表:

mysql> SELECT NAME, ROW_FORMAT FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME='test1/t1';
+----------+------------+
| NAME     | ROW_FORMAT |
+----------+------------+
| test1/t1 | Dynamic    |
+----------+------------+

17.11 InnoDB 磁盘 I/O 和文件空间管理

原文:dev.mysql.com/doc/refman/8.0/en/innodb-disk-management.html

17.11.1 InnoDB 磁盘 I/O

17.11.2 文件空间管理

17.11.3 InnoDB 检查点

17.11.4 表碎片整理

17.11.5 使用 TRUNCATE TABLE 回收磁盘空间

作为数据库管理员,您必须管理磁盘 I/O,以防止 I/O 子系统过载,并管理磁盘空间,以避免存储设备填满。 ACID 设计模型需要一定量的 I/O,这可能看起来是多余的,但有助于确保数据的可靠性。 在这些约束条件下,InnoDB 尝试优化数据库工作和磁盘文件的组织,以最小化磁盘 I/O 的数量。 有时,I/O 被推迟,直到数据库不忙,或者直到需要将所有内容带到一致状态,例如在 快速关闭 后进行数据库重启时。

本节讨论了使用默认类型的 MySQL 表(也称为 InnoDB 表)时的主要考虑因素:

  • 控制用于改善查询性能的后台 I/O 的数量。

  • 启用或禁用提供额外耐久性的功能,但会增加额外的 I/O。

  • 将表组织成许多小文件、少量大文件或两者的组合。

  • 在重做日志文件大小与日志文件变满时发生的 I/O 活动之间取得平衡。

  • 如何重新组织表以获得最佳的查询性能。

17.11.1 InnoDB 磁盘 I/O

原文:dev.mysql.com/doc/refman/8.0/en/innodb-disk-io.html

InnoDB在可能的情况下使用异步磁盘 I/O,通过创建多个线程来处理 I/O 操作,同时允许其他数据库操作在 I/O 仍在进行时继续进行。在 Linux 和 Windows 平台上,InnoDB使用可用的操作系统和库函数执行“本机”异步 I/O。在其他平台上,InnoDB仍然使用 I/O 线程,但线程可能实际上会等待 I/O 请求完成;这种技术称为“模拟”异步 I/O。

预读

如果InnoDB可以确定数据很可能很快就会被需要,它会执行预读操作将数据带入缓冲池,以便在内存中可用。对连续数据进行少量大的读取请求可能比进行几个小的分散请求更有效。InnoDB有两种预读启发式:

  • 在顺序读取预读中,如果InnoDB注意到表空间中某个段的访问模式是顺序的,它会提前向 I/O 系统发布一批数据库页面的读取。

  • 在随机读取预读中,如果InnoDB注意到表空间中某个区域似乎正在被完全读入缓冲池,它会将剩余的读取发布到 I/O 系统。

有关配置预读启发式的信息,请参见第 17.8.3.4 节,“配置 InnoDB 缓冲池预取(预读)”")。

双写缓冲区

InnoDB使用一种涉及名为双写缓冲区的新颖文件刷新技术,默认情况下在大多数情况下启用(innodb_doublewrite=ON)。它增加了在意外退出或停电后恢复的安全性,并通过减少fsync()操作的需求来提高大多数 Unix 系统的性能。

在将页面写入数据文件之前,InnoDB首先将它们写入称为双写缓冲区的存储区域。只有在写入和刷新到双写缓冲区完成后,InnoDB才将页面写入数据文件的适当位置。如果在页面写入过程中有操作系统、存储子系统或意外的mysqld进程退出(导致破碎页条件),InnoDB可以在恢复过程中从双写缓冲区找到页面的良好副本。

有关双写缓冲区的更多信息,请参见第 17.6.4 节,“双写缓冲区”。

17.11.2 文件空间管理

原文:dev.mysql.com/doc/refman/8.0/en/innodb-file-space.html

您在配置文件中使用innodb_data_file_path配置选项定义的数据文件形成InnoDB系统表空间。这些文件在逻辑上连接在一起形成系统表空间。没有使用条带化。您无法定义表在系统表空间内的分配位置。在新创建的系统表空间中,InnoDB从第一个数据文件开始分配空间。

为了避免将所有表和索引存储在系统表空间内带来的问题,您可以启用innodb_file_per_table配置选项(默认情况下),该选项将每个新创建的表存储在单独的表空间文件中(扩展名为.ibd)。以这种方式存储的表在磁盘文件内部的碎片较少,当表被截断时,空间将被返回给操作系统,而不是仍然被 InnoDB 在系统表空间内保留。更多信息,请参阅第 17.6.3.2 节,“每表一个表空间”。

您还可以将表存储在通用表空间中。通用表空间是使用CREATE TABLESPACE语法创建的共享表空间。它们可以在 MySQL 数据目录之外创建,能够容纳多个表,并支持所有行格式的表。更多信息,请参阅第 17.6.3.3 节,“通用表空间”。

页、区、段和表空间

每个表空间由数据库页组成。MySQL 实例中的每个表空间具有相同的页大小。默认情况下,所有表空间的页大小为 16KB;您可以通过在创建 MySQL 实例时指定innodb_page_size选项来将页大小减小到 8KB 或 4KB。您还可以将页大小增加到 32KB 或 64KB。更多信息,请参考innodb_page_size文档。

页面被分组为大小为 1MB 的 extent,用于大小不超过 16KB 的页面(64 个连续的 16KB 页面,或 128 个 8KB 页面,或 256 个 4KB 页面)。对于 32KB 的页面大小,extent 大小为 2MB。对于 64KB 的页面大小,extent 大小为 4MB。表空间中的“文件”被称为InnoDB中的 segments。(这些段与实际包含许多表空间段的 rollback segment 不同。)

当段在表空间内增长时,InnoDB逐个为其分配前 32 页。之后,InnoDB开始为段分配整个 extent。InnoDB可以一次向大段添加多达 4 个 extent,以确保数据的良好顺序性。

InnoDB中,每个索引分配两个段。一个用于 B-tree 的非叶节点,另一个用于叶节点。在磁盘上保持叶节点连续使得更好的顺序 I/O 操作成为可能,因为这些叶节点包含实际的表数据。

表空间中的一些页面包含其他页面的位图,因此InnoDB表空间中的一些 extent 无法作为整体分配给段,而只能作为单个页面分配。

通过发出SHOW TABLE STATUS语句来查询表空间中的可用空闲空间时,InnoDB报告表空间中明确空闲的 extent。InnoDB始终保留一些 extent 用于清理和其他内部目的;这些保留的 extent 不包括在空闲空间中。

当您从表中删除数据时,InnoDB会收缩相应的 B-tree 索引。释放的空间是否可供其他用户使用取决于删除模式是否将单个页面或 extent 释放到表空间。删除表或从表中删除所有行将确保将空间释放给其他用户,但请记住,删除的行只有在不再需要进行事务回滚或一致性读取后一段时间自动执行的 purge 操作才会被物理删除。(参见 Section 17.3,“InnoDB 多版本”.)

配置保留文件段页面的百分比

innodb_segment_reserve_factor变量,引入于 MySQL 8.0.26,是一个高级功能,允许定义作为空页面保留的表空间文件段页面的百分比。保留一定比例的页面用于未来增长,以便 B-tree 中的页面可以连续分配。修改保留页面百分比的能力允许对InnoDB进行微调,以解决数据碎片化或存储空间的低效使用问题。

该设置适用于每表文件和通用表空间。innodb_segment_reserve_factor 的默认设置为 12.5%,与之前的 MySQL 版本中保留的页面百分比相同。

innodb_segment_reserve_factor 变量是动态的,可以使用 SET 语句进行配置。例如:

mysql> SET GLOBAL innodb_segment_reserve_factor=10;

页面与表行的关系

对于 4KB、8KB、16KB 和 32KB 的 innodb_page_size 设置,最大行长度略小于半个数据库页面大小。例如,默认的 16KB InnoDB 页面大小的最大行长度略小于 8KB。对于 64KB 的 innodb_page_size 设置,最大行长度略小于 16KB。

如果行未超过最大行长度,则所有内容都存储在页面内。如果行超过最大行长度,变长列 被选择进行外部页存储,直到行符合最大行长度限制。变长列的外部页存储根据行格式而异:

  • 紧凑和冗余行格式

    当选择变长列进行外部页存储时,InnoDB 将前 768 字节存储在行内,其余部分存储在溢出页中。每个这样的列都有自己的溢出页列表。768 字节前缀伴随着一个 20 字节的值,存储列的真实长度,并指向溢出列表,其中存储了值的其余部分。参见 第 17.10 节,“InnoDB 行格式”。

  • 动态和压缩行格式

    当选择变长列进行外部页存储时,InnoDB 在行内存储一个 20 字节的指针,其余部分存储在溢出页中。参见 第 17.10 节,“InnoDB 行格式”。

LONGBLOBLONGTEXT 列必须小于 4GB,包括 BLOBTEXT 列在内的总行长度必须小于 4GB。

17.11.3 InnoDB 检查点

原文:dev.mysql.com/doc/refman/8.0/en/innodb-checkpoints.html

使你的日志文件变得非常大可能会在检查点期间减少磁盘 I/O。通常情况下,将日志文件的总大小设置为缓冲池的大小甚至更大是有意义的。

检查点处理的工作原理

InnoDB实现了一种被称为模糊检查点的检查点机制。InnoDB会将修改过的数据库页面以小批量方式从缓冲池刷新出去。在检查点过程中,没有必要一次性刷新缓冲池,这样会干扰用户 SQL 语句的处理过程。

在崩溃恢复期间,InnoDB会寻找写入日志文件的检查点标签。它知道标签之前对数据库的所有修改都存在于数据库的磁盘镜像中。然后,InnoDB从检查点开始向前扫描日志文件,将记录的修改应用到数据库中。

17.11.4 表碎片整理

原文:dev.mysql.com/doc/refman/8.0/en/innodb-file-defragmenting.html

随机插入或从二级索引中删除可能导致索引碎片化。碎片化意味着磁盘上索引页面的物理排序与页面上记录的索引排序不接近,或者在为索引分配的 64 页块中有许多未使用的页面。

碎片化的一个症状是表占用的空间比“应该”占用的空间多。确切的数量很难确定。所有 InnoDB 数据和索引都存储在 B 树中,它们的填充因子可能从 50% 变化到 100%。碎片化的另一个症状是像这样的表扫描所需的时间比“应该”花费的时间更长:

SELECT COUNT(*) FROM t WHERE *non_indexed_column* <> 12345;

前面的查询需要 MySQL 执行完整表扫描,这是针对大表最慢的查询类型。

为加快索引扫描速度,您可以定期执行“null”ALTER TABLE操作,这会导致 MySQL 重建表:

ALTER TABLE *tbl_name* ENGINE=INNODB

您还可以使用ALTER TABLE *tbl_name* FORCE执行“null” alter 操作,重建表。

ALTER TABLE *tbl_name* ENGINE=INNODBALTER TABLE *tbl_name* FORCE 都使用在线 DDL。有关更多信息,请参见 Section 17.12, “InnoDB and Online DDL”。

另一种执行碎片整理操作的方法是使用mysqldump将表转储到文本文件中,删除表,并从转储文件重新加载。

如果对索引的插入始终是升序的,并且仅从末尾删除记录,则 InnoDB 文件空间管理算法保证索引不会发生碎片化。

17.11.5 使用 TRUNCATE TABLE 回收磁盘空间

原文:dev.mysql.com/doc/refman/8.0/en/innodb-truncate-table-reclaim-space.html

要在截断InnoDB表时回收操作系统磁盘空间,表必须存储在自己的.ibd 文件中。为了让表存储在自己的.ibd 文件中,在创建表时必须启用innodb_file_per_table。此外,被截断的表与其他表之间不能有外键约束,否则TRUNCATE TABLE操作会失败。然而,同一表中两列之间的外键约束是允许的。

当表被截断时,它会被删除并在一个新的.ibd文件中重新创建,释放的空间会返回给操作系统。这与截断存储在InnoDB系统表空间(当innodb_file_per_table=OFF时创建的表)和存储在共享通用表空间中的InnoDB表形成对比,在这种情况下,只有InnoDB在表被截断后才能使用释放的空间。

截断表并将磁盘空间返回给操作系统的能力也意味着物理备份可以更小。截断存储在系统表空间(当innodb_file_per_table=OFF时创建的表)或通用表空间中的表会在表空间中留下未使用的空间块。

17.12 InnoDB 和在线 DDL

原文:dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl.html

17.12.1 在线 DDL 操作

17.12.2 在线 DDL 性能和并发性

17.12.3 在线 DDL 空间要求

17.12.4 在线 DDL 内存管理

17.12.5 配置在线 DDL 操作的并行线程

17.12.6 简化 DDL 语句的在线 DDL

17.12.7 在线 DDL 失败条件

17.12.8 在线 DDL 限制

在线 DDL 功能支持即时和原地表更改以及并发 DML。此功能的好处包括:

  • 在繁忙的生产环境中提高响应性和可用性,使表在几分钟或几小时内不可用是不切实际的。

  • 对于原地操作,在 DDL 操作期间使用 LOCK 子句调整性能和并发性之间的平衡。参见 LOCK 子句。

  • 比表复制方法使用更少的磁盘空间和 I/O 开销。

注意

ALGORITHM=INSTANT 支持在 MySQL 8.0.12 中的 ADD COLUMN 和其他操作中使用。

通常,您不需要采取任何特殊措施来启用在线 DDL。默认情况下,MySQL 尽可能以立即或原地的方式执行操作,并尽量减少锁定。

您可以使用ALTER TABLE语句的 ALGORITHMLOCK 子句来控制 DDL 操作的各个方面。这些子句位于语句末尾,与表和列规范用逗号分隔。例如:

ALTER TABLE *tbl_name* ADD PRIMARY KEY (*column*), ALGORITHM=INPLACE;

LOCK 子句可用于原地执行的操作,并且在操作期间对表的并发访问程度进行微调非常有用。仅支持 LOCK=DEFAULT 用于立即执行的操作。ALGORITHM 子句主要用于性能比较,并作为旧表复制行为的后备,以防遇到任何问题。例如:

  • 为了避免在原地ALTER TABLE操作期间意外使表对读取、写入或两者都不可用,请在ALTER TABLE语句上指定一个子句,如 LOCK=NONE(允许读取和写入)或 LOCK=SHARED(允许读取)。如果所请求的并发级别不可用,则操作立即停止。

  • 为了比较算法之间的性能,请运行带有ALGORITHM=INSTANTALGORITHM=INPLACEALGORITHM=COPY的语句。您还可以运行带有启用old_alter_table配置选项的语句,以强制使用ALGORITHM=COPY

  • 为了避免使用ALTER TABLE操作拷贝表格而占用服务器资源,请包含ALGORITHM=INSTANTALGORITHM=INPLACE。如果无法使用指定的算法,该语句将立即停止。

17.12.1 在线 DDL 操作

原文:dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html

本节中提供了 DDL 操作的在线支持详细信息、语法示例和用法说明。

  • 索引操作

  • 主键操作

  • 列操作

  • 生成列操作

  • 外键操作

  • 表操作

  • 表空间操作

  • 分区操作

索引操作

以下表格概述了索引操作的在线 DDL 支持。星号表示额外信息、异常或依赖关系。详情请参阅语法和用法说明。

表 17.16 索引操作的在线 DDL 支持

操作 立即 就地 重建表 允许并发 DML 仅修改元数据
创建或添加二级索引
删除索引
重命名索引
添加FULLTEXT索引 是* 否*
添加SPATIAL索引
更改索引类型
语法和用法说明
  • 创建或添加二级索引

    CREATE INDEX *name* ON *table* (*col_list*);
    
    ALTER TABLE *tbl_name* ADD INDEX *name* (*col_list*);
    

    在创建索引时,表仍可用于读写操作。CREATE INDEX语句仅在访问表的所有事务完成后才完成,以便索引的初始状态反映表的最新内容。

    添加二级索引的在线 DDL 支持意味着您通常可以通过在加载数据后创建不带二级索引的表,然后在数据加载后添加二级索引,从而加快创建和加载表及相关索引的整个过程。

    新创建的二级索引仅包含在CREATE INDEXALTER TABLE语句执行完成时表中的已提交数据。它不包含任何未提交的值、旧版本的值或标记为删除但尚未从旧索引中删除的值。

    一些因素会影响此操作的性能、空间使用和语义。详情请参阅 Section 17.12.8, “在线 DDL 限制”。

  • 删除索引

    DROP INDEX *name* ON *table*;
    
    ALTER TABLE *tbl_name* DROP INDEX *name*;
    

    当索引被删除时,表仍然可用于读写操作。DROP INDEX语句只有在访问表的所有事务完成后才会完成,以便索引的初始状态反映表的最新内容。

  • 重命名索引

    ALTER TABLE *tbl_name* RENAME INDEX *old_index_name* TO *new_index_name*, ALGORITHM=INPLACE, LOCK=NONE;
    
  • 添加FULLTEXT索引

    CREATE FULLTEXT INDEX *name* ON table(*column*);
    

    添加第一个FULLTEXT索引会在没有用户定义的FTS_DOC_ID列的情况下重建表。可以在不重建表的情况下添加额外的FULLTEXT索引。

  • 添加SPATIAL索引

    CREATE TABLE geom (g GEOMETRY NOT NULL);
    ALTER TABLE geom ADD SPATIAL INDEX(g), ALGORITHM=INPLACE, LOCK=SHARED;
    
  • 更改索引类型(USING {BTREE | HASH}

    ALTER TABLE *tbl_name* DROP INDEX i1, ADD INDEX i1(*key_part,...*) USING BTREE, ALGORITHM=INSTANT;
    

主键操作

下表概述了主键操作的在线 DDL 支持。星号表示额外信息、异常或依赖项。请参阅语法和用法说明。

表 17.17 主键操作的在线 DDL 支持

操作 立即 原地 重建表 允许并发 DML 仅修改元数据
添加主键 是* 是*
删除主键
删除主键并添加另一个
语法和用法说明
  • 添加主键

    ALTER TABLE *tbl_name* ADD PRIMARY KEY (*column*), ALGORITHM=INPLACE, LOCK=NONE;
    

    在原地重建表。数据被大幅重新组织,使其成为一项昂贵的操作。如果必须将列转换为NOT NULL,则在某些条件下不允许使用ALGORITHM=INPLACE

    重构聚簇索引总是需要复制表数据。因此,最好在创建表时定义主键,而不是稍后发出ALTER TABLE ... ADD PRIMARY KEY

    当创建UNIQUEPRIMARY KEY索引时,MySQL 必须做一些额外的工作。对于UNIQUE索引,MySQL 检查表中是否没有重复键值。对于PRIMARY KEY索引,MySQL 还检查是否没有PRIMARY KEY列包含NULL

    当使用ALGORITHM=COPY子句添加主键时,MySQL 会将相关列中的NULL值转换为默认值:数字为 0,基于字符的列和 BLOB 为空字符串,DATETIME为 0000-00-00 00:00:00。这是一种非标准行为,Oracle 建议您不要依赖于此。只有在SQL_MODE设置包括strict_trans_tablesstrict_all_tables标志时,才允许使用ALGORITHM=INPLACE添加主键;当SQL_MODE设置为 strict 时,允许使用ALGORITHM=INPLACE,但如果请求的主键列包含NULL值,则语句仍可能失败。ALGORITHM=INPLACE行为更符合标准。

    如果创建一个没有主键的表,InnoDB会为您选择一个主键,可以是第一个在NOT NULL列上定义的UNIQUE键,或者是系统生成的键。为了避免不确定性和额外隐藏列的潜在空间需求,请在CREATE TABLE语句中指定PRIMARY KEY子句。

    MySQL 通过将现有数据从原始表复制到具有所需索引结构的临时表来创建新的聚集索引。一旦数据完全复制到临时表,原始表将以不同的临时表名称重命名。包含新聚集索引的临时表将以原始表的名称重命名,并且原始表将从数据库中删除。

    适用于次要索引操作的在线性能增强不适用于主键索引。InnoDB 表的行存储在基于主键组织的聚集索引中,形成一些数据库系统称为“索引组织表”的结构。由于表结构与主键紧密相关,重新定义主键仍然需要复制数据。

    当对主键使用ALGORITHM=INPLACE时,即使数据仍在复制,也比使用ALGORITHM=COPY更有效,因为:

    • 对于ALGORITHM=INPLACE不需要撤消日志或相关的重做日志。这些操作会给使用ALGORITHM=COPY的 DDL 语句增加开销。

    • 次要索引条目已经预先排序,因此可以按顺序加载。

    • 由于没有随机访问插入到次要索引中,因此不使用更改缓冲区。

  • 删除主键

    ALTER TABLE *tbl_name* DROP PRIMARY KEY, ALGORITHM=COPY;
    

    只有ALGORITHM=COPY支持在同一ALTER TABLE语句中删除主键而不添加新主键。

  • 删除一个主键并添加另一个

    ALTER TABLE *tbl_name* DROP PRIMARY KEY, ADD PRIMARY KEY (*column*), ALGORITHM=INPLACE, LOCK=NONE;
    

    数据进行了大幅重组,这是一项昂贵的操作。

列操作

下表提供了关于列操作的在线 DDL 支持的概述。星号表示额外信息、异常或依赖关系。详情请参见语法和用法说明。

表 17.18 列操作的在线 DDL 支持

操作 Instant In Place 重建表 允许并发 DML 仅修改元数据
添加列 是* 否* 是*
删除列 是*
重命名列 是* 是*
重新排序列
设置列默认值
更改列数据类型
扩展VARCHAR列大小
删除列默认值
更改自增值 否*
使列为NULL 是*
使列为NOT NULL 是* 是*
修改ENUMSET列的定义
操作 Instant In Place 重建表 允许并发 DML 仅修改元数据
语法和用法说明
  • 添加列

    ALTER TABLE *tbl_name* ADD COLUMN *column_name* *column_definition*, ALGORITHM=INSTANT;
    

    INSTANT是 MySQL 8.0.12 之后的默认算法,之前是INPLACE

    INSTANT算法添加列时,以下限制适用:

    • 一条语句不能将添加列与不支持INSTANT算法的其他ALTER TABLE操作结合在一起。

    • INSTANT算法可以在表中的任何位置添加列。在 MySQL 8.0.29 之前,INSTANT算法只能将列添加为表的最后一列。

    • 不能向使用ROW_FORMAT=COMPRESSED、具有FULLTEXT索引、位于数据字典表空间中的表或临时表添加列。临时表仅支持ALGORITHM=COPY

    • INSTANT算法添加列时,MySQL 会检查行大小,如果添加超过限制,则会抛出以下错误。

      错误 4092(HY000):无法使用 ALGORITHM=INSTANT 添加列,因为此后最大可能行大小超过了最大允许行大小。请尝试 ALGORITHM=INPLACE/COPY。

      在 MySQL 8.0.29 之前,MySQL 在INSTANT算法添加列时不会检查行大小。但是,在插入和更新表中的行的 DML 操作期间,MySQL 会检查行大小。

    • 在使用INSTANT算法添加列后,表的内部表示中列的最大数量不能超过 1022。错误消息为:

      错误 4158(HY000):无法使用 ALGORITHM=INSTANT 将列添加到tbl_name。请尝试 ALGORITHM=INPLACE/COPY

    • INSTANT算法无法向系统模式表(如内部mysql表)添加或删除列。此限制是在 MySQL 8.0.29 中添加的。

    可以在同一 ALTER TABLE 语句中添加多个列。例如:

    ALTER TABLE t1 ADD COLUMN c2 INT, ADD COLUMN c3 INT, ALGORITHM=INSTANT;
    

    每次执行ALTER TABLE ... ALGORITHM=INSTANT 操作添加一个或多个列、删除一个或多个列,或者在同一操作中添加和删除一个或多个列后,都会创建一个新的行版本。INFORMATION_SCHEMA.INNODB_TABLES.TOTAL_ROW_VERSIONS 列跟踪表的行版本数量。每次立即添加或删除列时,该值都会递增。初始值为 0。

    mysql>  SELECT NAME, TOTAL_ROW_VERSIONS FROM INFORMATION_SCHEMA.INNODB_TABLES 
            WHERE NAME LIKE 'test/t1';
    +---------+--------------------+
    | NAME    | TOTAL_ROW_VERSIONS |
    +---------+--------------------+
    | test/t1 |                  0 |
    +---------+--------------------+
    

    当具有立即添加或删除列的表通过表重建的 ALTER TABLEOPTIMIZE TABLE 操作重建时,TOTAL_ROW_VERSIONS 值将重置为 0。允许的最大行版本数为 64,因为每个行版本都需要额外的表元数据空间。当达到行版本限制时,使用ALGORITHM=INSTANTADD COLUMNDROP COLUMN操作将被拒绝,并显示错误消息建议使用COPYINPLACE算法重建表。

    错误 4080 (HY000): 表 test/t1 的最大行版本已达到。无法立即添加或删除更多列。请使用 COPY/INPLACE。

    以下 INFORMATION_SCHEMA 列提供了立即添加列的附加元数据。有关这些列的描述,请参考这些列的描述以获取更多信息。参见 Section 28.4.9, “The INFORMATION_SCHEMA INNODB_COLUMNS Table” 和 Section 28.4.23, “The INFORMATION_SCHEMA INNODB_TABLES Table”。

    • INNODB_COLUMNS.DEFAULT_VALUE

    • INNODB_COLUMNS.HAS_DEFAULT

    • INNODB_TABLES.INSTANT_COLS

    在添加自增列时不允许并发 DML。数据会被大幅重组,使其成为一项昂贵的操作。至少需要ALGORITHM=INPLACE, LOCK=SHARED

    如果使用ALGORITHM=INPLACE添加列,则表将被重建。

  • 删除列

    ALTER TABLE *tbl_name* DROP COLUMN *column_name*, ALGORITHM=INSTANT;
    

    截至 MySQL 8.0.29,INSTANT是默认算法,之前是INPLACE

    使用INSTANT算法删除列时会出现以下限制:

    • 不能将删除列与不支持ALGORITHM=INSTANT的其他 ALTER TABLE 操作结合在同一语句中。

    • 不能从使用ROW_FORMAT=COMPRESSED、具有FULLTEXT索引、位于数据字典表空间中的表或临时表中删除列。临时表仅支持ALGORITHM=COPY

    可以在同一ALTER TABLE语句中删除多个列;例如:

    ALTER TABLE t1 DROP COLUMN c4, DROP COLUMN c5, ALGORITHM=INSTANT;
    

    每次使用ALGORITHM=INSTANT添加或删除列时,都会创建一个新的行版本。INFORMATION_SCHEMA.INNODB_TABLES.TOTAL_ROW_VERSIONS列跟踪表的行版本数。每次立即添加或删除列时,该值都会递增。初始值为 0。

    mysql>  SELECT NAME, TOTAL_ROW_VERSIONS FROM INFORMATION_SCHEMA.INNODB_TABLES 
            WHERE NAME LIKE 'test/t1';
    +---------+--------------------+
    | NAME    | TOTAL_ROW_VERSIONS |
    +---------+--------------------+
    | test/t1 |                  0 |
    +---------+--------------------+
    

    当具有立即添加或删除列的表通过表重建ALTER TABLEOPTIMIZE TABLE操作重建时,TOTAL_ROW_VERSIONS值将重置为 0。允许的最大行版本数为 64,因为每个行版本都需要额外的空间用于表元数据。当达到行版本限制时,使用ALGORITHM=INSTANTADD COLUMNDROP COLUMN操作将被拒绝,并显示错误消息建议使用COPYINPLACE算法重建表。

    错误 4080 (HY000):表 test/t1 的最大行版本已达到。无法立即添加或删除更多列。请使用 COPY/INPLACE。

    如果使用ALGORITHM=INSTANT之外的算法,数据会被大幅重组,使其成为一项昂贵的操作。

  • 重命名列

    ALTER TABLE *tbl* CHANGE *old_col_name* *new_col_name* *data_type*, ALGORITHM=INSTANT;
    

    MySQL 8.0.28 中添加了对重命名列的ALGORITHM=INSTANT支持。在较早的 MySQL Server 版本中,重命名列仅支持ALGORITHM=INPLACEALGORITHM=COPY

    为了允许并发 DML,请保持相同的数据类型,只更改列名。

    当保持相同的数据类型和[NOT] NULL属性,仅更改列名时,该操作始终可以在线执行。

    仅允许使用ALGORITHM=INPLACE重命名另一个表引用的列。如果使用ALGORITHM=INSTANTALGORITHM=COPY或导致操作使用这些算法的其他条件,则ALTER TABLE语句将失败。

    ALGORITHM=INSTANT支持重命名虚拟列;ALGORITHM=INPLACE不支持。

    在相同语句中添加或删除虚拟列时,ALGORITHM=INSTANTALGORITHM=INPLACE不支持重命名列。在这种情况下,只支持ALGORITHM=COPY

  • 重新排序列

    要重新排序列,请在CHANGEMODIFY操作中使用FIRSTAFTER

    ALTER TABLE *tbl_name* MODIFY COLUMN *col_name* *column_definition* FIRST, ALGORITHM=INPLACE, LOCK=NONE;
    

    数据会被大幅重组,使其成为一项昂贵的操作。

  • 更改列数据类型

    ALTER TABLE *tbl_name* CHANGE c1 c1 BIGINT, ALGORITHM=COPY;
    

    只有使用ALGORITHM=COPY才支持更改列数据类型。

  • 扩展VARCHAR列大小

    ALTER TABLE *tbl_name* CHANGE COLUMN c1 c1 VARCHAR(255), ALGORITHM=INPLACE, LOCK=NONE;
    

    VARCHAR列所需的长度字节数必须保持不变。对于大小为 0 到 255 字节的VARCHAR列,需要一个长度字节来编码值。对于大小为 256 字节或更大的VARCHAR列,需要两个长度字节。因此,原地ALTER TABLE仅支持将VARCHAR列大小从 0 到 255 字节增加,或从 256 字节增加到更大的大小。原地ALTER TABLE不支持将VARCHAR列大小从小于 256 字节增加到等于或大于 256 字节。在这种情况下,所需的长度字节数从 1 变为 2,只能通过表复制(ALGORITHM=COPY)支持。例如,尝试使用原地ALTER TABLE将单字节字符集的VARCHAR列大小从 VARCHAR(255)更改为 VARCHAR(256)会返回此错误:

    ALTER TABLE *tbl_name* ALGORITHM=INPLACE, CHANGE COLUMN c1 c1 VARCHAR(256);
    ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Cannot change
    column type INPLACE. Try ALGORITHM=COPY.
    

    注意

    VARCHAR列的字节长度取决于字符集的字节长度。

    使用原地ALTER TABLE不支持减小VARCHAR大小。减小VARCHAR大小需要表复制(ALGORITHM=COPY)。

  • 设置列默认值

    ALTER TABLE *tbl_name* ALTER COLUMN *col* SET DEFAULT *literal*, ALGORITHM=INSTANT;
    

    仅修改表元数据。默认列值存储在数据字典中。

  • 删除列默认值

    ALTER TABLE *tbl* ALTER COLUMN *col* DROP DEFAULT, ALGORITHM=INSTANT;
    
  • 更改自增值

    ALTER TABLE *table* AUTO_INCREMENT=*next_value*, ALGORITHM=INPLACE, LOCK=NONE;
    

    修改存储在内存中的值,而不是数据文件中的值。

    在使用复制或分片的分布式系统中,有时会将表的自增计数器重置为特定值。表中插入的下一行将使用其自增列的指定值。您也可以在数据仓库环境中使用此技术,定期清空所有表并重新加载它们,并从 1 重新启动自增序列。

  • 使列NULL

    ALTER TABLE tbl_name MODIFY COLUMN *column_name* *data_type* NULL, ALGORITHM=INPLACE, LOCK=NONE;
    

    在原地重建表。数据被大幅重新组织,使其成为昂贵的操作。

  • 使列NOT NULL

    ALTER TABLE *tbl_name* MODIFY COLUMN *column_name* *data_type* NOT NULL, ALGORITHM=INPLACE, LOCK=NONE;
    

    在原地重建表格。操作需要STRICT_ALL_TABLESSTRICT_TRANS_TABLES SQL_MODE 的支持才能成功。如果列包含 NULL 值,则操作将失败。服务器禁止对可能导致引用完整性丢失的外键列进行更改。请参阅第 15.1.9 节,“ALTER TABLE Statement”。数据将被大幅重新组织,这是一个昂贵的操作。

  • 修改ENUMSET列的定义

    CREATE TABLE t1 (c1 ENUM('a', 'b', 'c'));
    ALTER TABLE t1 MODIFY COLUMN c1 ENUM('a', 'b', 'c', 'd'), ALGORITHM=INSTANT;
    

    通过在有效成员值列表的末尾添加新的枚举或集合成员来修改ENUMSET列的定义可以立即执行或原地执行,只要数据类型的存储大小不变。例如,向具有 8 个成员的SET列添加一个成员会将每个值所需的存储从 1 字节更改为 2 字节;这需要复制表格。在列表中间添加成员会导致现有成员的重新编号,这需要复制表格。

生成列操作

下表提供了生成列操作的在线 DDL 支持概述。详情请参阅语法和用法注意事项。

表 17.19 生成列操作的在线 DDL 支持

操作 立即执行 原地执行 重建表格 允许并发 DML 仅修改元数据
添加STORED
修改STORED列顺序
删除STORED
添加VIRTUAL
修改VIRTUAL列顺序
删除VIRTUAL
语法和用法注意事项
  • 添加STORED

    ALTER TABLE t1 ADD COLUMN (c2 INT GENERATED ALWAYS AS (c1 + 1) STORED), ALGORITHM=COPY;
    

    ADD COLUMN对于存储列(不使用临时表)不是一个原地操作,因为表达式必须由服务器评估。

  • 修改STORED列顺序

    ALTER TABLE t1 MODIFY COLUMN c2 INT GENERATED ALWAYS AS (c1 + 1) STORED FIRST, ALGORITHM=COPY;
    

    在原地重建表格。

  • 删除STORED

    ALTER TABLE t1 DROP COLUMN c2, ALGORITHM=INPLACE, LOCK=NONE;
    

    在原地重建表格。

  • 添加VIRTUAL

    ALTER TABLE t1 ADD COLUMN (c2 INT GENERATED ALWAYS AS (c1 + 1) VIRTUAL), ALGORITHM=INSTANT;
    

    对于非分区表,添加虚拟列可以立即执行或原地执行。

    对分区表来说,添加VIRTUAL列不是一个原地操作。

  • 修改VIRTUAL列顺序

    ALTER TABLE t1 MODIFY COLUMN c2 INT GENERATED ALWAYS AS (c1 + 1) VIRTUAL FIRST, ALGORITHM=COPY;
    
  • 删除VIRTUAL

    ALTER TABLE t1 DROP COLUMN c2, ALGORITHM=INSTANT;
    

    对于非分区表,删除VIRTUAL列可以立即执行或原地执行。

外键操作

下表提供了外键操作的在线 DDL 支持概述。星号表示额外信息、异常或依赖关系。详情请参阅语法和用法注意事项。

表 17.20 外键操作的在线 DDL 支持

操作 立即 就地 重建表 允许并发 DML 仅修改元数据
添加外键约束 是*
删除外键约束
语法和用法说明
  • 添加外键约束

    foreign_key_checks被禁用时,支持INPLACE算法。否则,只支持COPY算法。

    ALTER TABLE *tbl1* ADD CONSTRAINT *fk_name* FOREIGN KEY *index* (*col1*)
      REFERENCES *tbl2*(*col2*) *referential_actions*;
    
  • 删除外键约束

    ALTER TABLE *tbl* DROP FOREIGN KEY *fk_name*;
    

    可以在启用或禁用foreign_key_checks选项的情况下在线执行删除外键操作。

    如果不知道特定表上外键约束的名称,请发出以下语句,并在每个外键的CONSTRAINT子句中找到约束名称:

    SHOW CREATE TABLE *table*\G
    

    或者,查询信息模式TABLE_CONSTRAINTS表,并使用CONSTRAINT_NAMECONSTRAINT_TYPE列来识别外键名称。

    您还可以在单个语句中删除外键及其关联的索引:

    ALTER TABLE *table* DROP FOREIGN KEY *constraint*, DROP INDEX *index*;
    

注意

如果表中已经存在外键(即,它是包含FOREIGN KEY ... REFERENCE子句的子表),则对在线 DDL 操作会有额外限制,即使这些操作并不直接涉及外键列:

  • 如果对父表的更改通过ON UPDATEON DELETE子句使用CASCADESET NULL参数导致子表中的关联更改,那么对子表进行的ALTER TABLE可能需要等待另一个事务提交。

  • 同样,如果一张表是外键关系中的父表,即使它不包含任何FOREIGN KEY子句,如果INSERTUPDATEDELETE语句导致子表中的ON UPDATEON DELETE操作,它也可能需要等待ALTER TABLE完成。

表操作

以下表格提供了表操作的在线 DDL 支持概述。星号表示额外信息、异常或依赖关系。有关详细信息,请参阅语法和用法说明。

表 17.21 表操作的在线 DDL 支持

操作 立即 就地 重建表 允许并发 DML 仅修改元数据
更改 ROW_FORMAT
更改 KEY_BLOCK_SIZE
设置持久表统计信息
指定字符集 是*
转换字符集 是*
优化表 是*
使用 FORCE 选项重建 是*
执行空重建 是*
重命名表
语法和用法说明
  • 更改 ROW_FORMAT

    ALTER TABLE *tbl_name* ROW_FORMAT = *row_format*, ALGORITHM=INPLACE, LOCK=NONE;
    

    数据进行了大幅重组,这是一个昂贵的操作。

    有关 ROW_FORMAT 选项的更多信息,请参见表选项。

  • 更改 KEY_BLOCK_SIZE

    ALTER TABLE *tbl_name* KEY_BLOCK_SIZE = *value*, ALGORITHM=INPLACE, LOCK=NONE;
    

    数据进行了大幅重组,这是一个昂贵的操作。

    有关 KEY_BLOCK_SIZE 选项的更多信息,请参见表选项。

  • 设置持久表统计信息选项

    ALTER TABLE *tbl_name* STATS_PERSISTENT=0, STATS_SAMPLE_PAGES=20, STATS_AUTO_RECALC=1, ALGORITHM=INPLACE, LOCK=NONE;
    

    仅修改表元数据。

    持久统计信息包括 STATS_PERSISTENTSTATS_AUTO_RECALCSTATS_SAMPLE_PAGES。有关更多信息,请参见 Section 17.8.10.1, “Configuring Persistent Optimizer Statistics Parameters”。

  • 指定字符集

    ALTER TABLE *tbl_name* CHARACTER SET = *charset_name*, ALGORITHM=INPLACE, LOCK=NONE;
    

    如果新的字符编码不同,则重新构建表。

  • 转换字符集

    ALTER TABLE *tbl_name* CONVERT TO CHARACTER SET *charset_name*, ALGORITHM=COPY;
    

    如果新的字符编码不同,则重新构建表。

  • 优化表

    OPTIMIZE TABLE *tbl_name*;
    

    不支持具有 FULLTEXT 索引的表的就地操作。该操作使用 INPLACE 算法,但不允��使用 ALGORITHMLOCK 语法。

  • 使用 FORCE 选项重建表

    ALTER TABLE *tbl_name* FORCE, ALGORITHM=INPLACE, LOCK=NONE;
    

    在 MySQL 5.6.17 中使用 ALGORITHM=INPLACE。对于具有 FULLTEXT 索引的表,不支持 ALGORITHM=INPLACE

  • 执行“null”重建

    `ALTER TABLE *tbl_name* ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE;`
    

    在 MySQL 5.6.17 中使用 ALGORITHM=INPLACE。对于具有 FULLTEXT 索引的表,不支持 ALGORITHM=INPLACE

  • 重命名表

    `ALTER TABLE *old_tbl_name* RENAME TO *new_tbl_name*, ALGORITHM=INSTANT;`
    

    重命名表可以立即执行或就地执行。MySQL 重命名与表 *tbl_name* 对应的文件,而不进行复制。(您也可以使用 RENAME TABLE 语句来重命名表。请参见 Section 15.1.36, “RENAME TABLE Statement”。)专门授予重命名表的权限不会迁移到新名称。必须手动更改。

`#### 表空间操作

以下表格概述了表空间操作的在线 DDL 支持。有关详细信息,请参见语法和用法说明。

表 17.22 表空间操作的在线 DDL 支持

操作 立即 就地 重建表 允许并发 DML 仅修改元数据
重命名通用表空间
启用或禁用通用表空间加密
启用或禁用按表加密的表空间
语法和使用说明
  • 重命名通用表空间

    ALTER TABLESPACE *tablespace_name* RENAME TO *new_tablespace_name*;
    

    ALTER TABLESPACE ... RENAME TO使用INPLACE算法,但不支持ALGORITHM子句。

  • 启用或禁用通用表空间加密

    ALTER TABLESPACE *tablespace_name* ENCRYPTION='Y';
    

    ALTER TABLESPACE ... ENCRYPTION使用INPLACE算法,但不支持ALGORITHM子句。

    有关相关信息,请参阅第 17.13 节,“InnoDB 数据静态加密”。

  • 启用或禁用按表加密的表空间

    ALTER TABLE *tbl_name* ENCRYPTION='Y', ALGORITHM=COPY;
    

    有关相关信息,请参阅第 17.13 节,“InnoDB 数据静态加密”。

分区操作

除了一些ALTER TABLE分区子句外,针对分区InnoDB表的在线 DDL 操作遵循适用于常规InnoDB表的相同规则。

一些ALTER TABLE分区子句不会像常规非分区InnoDB表一样通过相同的内部在线 DDL API。因此,对于ALTER TABLE分区子句的在线支持有所不同。

以下表格显示了每个ALTER TABLE分区语句的在线状态。无论使用哪种在线 DDL API,MySQL 都会尽可能减少数据复制和锁定。

使用ALGORITHM=COPYALTER TABLE分区选项或仅允许“ALGORITHM=DEFAULT, LOCK=DEFAULT”的选项,使用COPY算法重新分区表。换句话说,使用新的分区方案创建了一个新的分区表。新创建的表包括ALTER TABLE语句应用的任何更改,并且表数据被复制到新表结构中。

表 17.23 分区操作的在线 DDL 支持

分区子句 立即 就地 允许 DML 备注
PARTITION BY 允许ALGORITHM=COPYLOCK={DEFAULT&#124;SHARED&#124;EXCLUSIVE}
ADD PARTITION 是* 是* 对于 RANGELIST 分区,支持 ALGORITHM=INPLACE, LOCK={DEFAULT&#124;NONE&#124;SHARED&#124;EXCLUSISVE},对于 HASHKEY 分区,支持 ALGORITHM=INPLACE, LOCK={DEFAULT&#124;SHARED&#124;EXCLUSISVE},对于所有分区类型,支持 ALGORITHM=COPY, LOCK={SHARED&#124;EXCLUSIVE}。对于使用 RANGELIST 进行分区的表,不会复制现有数据。对于使用 HASHLIST 进行分区的表,允许使用 ALGORITHM=COPY 进行并发查询,因为 MySQL 在持有共享锁的同时复制数据。
DROP PARTITION 是* 是* 支持 ALGORITHM=INPLACE, LOCK={DEFAULT&#124;NONE&#124;SHARED&#124;EXCLUSIVE}。对于使用 RANGELIST 进行分区的表,不会复制数据。使用 ALGORITHM=INPLACEDROP PARTITION 删除存储在分区中的数据并删除该分区。然而,使用 ALGORITHM=COPYold_alter_table=ONDROP PARTITION 会重建分区表,并尝试将数据从已删除的分区移动到具有兼容的 PARTITION ... VALUES 定义的另一个分区。无法移动到另一个分区的数据将被删除。
DISCARD PARTITION 仅允许 ALGORITHM=DEFAULT, LOCK=DEFAULT
IMPORT PARTITION 仅允许 ALGORITHM=DEFAULT, LOCK=DEFAULT
TRUNCATE PARTITION 不复制现有数据。它仅删除行;不会改变表本身或任何分区的定义。
COALESCE PARTITION 是* 支持 ALGORITHM=INPLACE, LOCK={DEFAULT&#124;SHARED&#124;EXCLUSIVE}
REORGANIZE PARTITION 是* 支持 ALGORITHM=INPLACE, LOCK={DEFAULT&#124;SHARED&#124;EXCLUSIVE}
EXCHANGE PARTITION
ANALYZE PARTITION
CHECK PARTITION
OPTIMIZE PARTITION ALGORITHMLOCK 子句被忽略。重建整个表。参见 Section 26.3.4, “Maintenance of Partitions”。
REBUILD PARTITION 是* 支持 ALGORITHM=INPLACE, LOCK={DEFAULT&#124;SHARED&#124;EXCLUSIVE}
REPAIR PARTITION
REMOVE PARTITIONING 允许 ALGORITHM=COPYLOCK={DEFAULT&#124;SHARED&#124;EXCLUSIVE}
分区子句 瞬时 就地 允许 DML 备注

针对分区表的非分区在线ALTER TABLE操作遵循适用于常规表的相同规则。然而,ALTER TABLE对每个表分区执行在线操作,这会导致系统资源需求增加,因为操作在多个分区上执行。

有关ALTER TABLE分区子句的更多信息,请参阅分区选项,以及第 15.1.9.1 节,“ALTER TABLE 分区操作”。有关分区的一般信息,请参阅第二十六章,“分区”。

posted @ 2024-06-23 12:24  绝不原创的飞龙  阅读(7)  评论(0编辑  收藏  举报