MySQL8 中文参考(二十一)
7.4.4.1 二进制日志格式
服务器使用几种日志记录格式来记录二进制日志中的信息:
-
MySQL 中的复制功能最初基于从源到副本的 SQL 语句传播。这被称为基于语句的日志记录。您可以通过使用
--binlog-format=STATEMENT启动服务器来使用此格式。 -
在基于行的日志记录(默认情况下)中,源将事件写入二进制日志,指示单个表行受到的影响。您可以通过使用
--binlog-format=ROW启动服务器来使用基于行的日志记录。 -
还有第三个选项:混合日志记录。使用混合日志记录,默认情况下使用基于语句的日志记录,但在某些情况下自动切换到基于行的日志记录,如下所述。您可以通过使用选项
--binlog-format=MIXED显式启动 MySQL 来使用混合日志记录。
日志记录格式也可以由正在使用的存储引擎设置或限制。这有助于消除在源和副本之间复制某些语句时使用不同存储引擎时出现的问题。
使用基于语句的复制时,可能会出现复制非确定性语句的问题。在决定给定语句是否适合基于语句的复制时,MySQL 确定是否可以保证该语句可以使用基于语句的日志记录进行复制。如果 MySQL 无法做出此保证,则将该语句标记为潜在不可靠,并发出警告,语句可能不安全以语句格式记录。
您可以通过使用 MySQL 的基于行的复制来避免这些问题。
7.4.4.2 设置二进制日志格式
您可以通过使用--binlog-format=*type*启动 MySQL 服务器来明确选择二进制日志记录格式。*type*支持的值为:
-
STATEMENT导致记录以语句为基础。 -
ROW导致记录以行为基础。这是默认设置。 -
MIXED导致记录使用混合格式。
设置二进制日志记录格式不会激活服务器的二进制日志记录。该设置仅在服务器启用二进制日志记录时生效,当log_bin系统变量设置为ON时为此情况。从 MySQL 8.0 开始,默认情况下启用二进制日志记录,只有在启动时指定--skip-log-bin或--disable-log-bin选项时才会禁用。
记录格式也可以在运行时切换,尽管请注意,在本节后面讨论的一些情况下,您无法这样做。设置全局值binlog_format系统变量以指定更改后连接的客户端的格式:
mysql> SET GLOBAL binlog_format = 'STATEMENT';
mysql> SET GLOBAL binlog_format = 'ROW';
mysql> SET GLOBAL binlog_format = 'MIXED';
通过设置binlog_format的会话值,个别客户端可以控制其自己语句的记录格式:
mysql> SET SESSION binlog_format = 'STATEMENT';
mysql> SET SESSION binlog_format = 'ROW';
mysql> SET SESSION binlog_format = 'MIXED';
更改全局binlog_format值需要具有足够权限来设置全局系统变量。更改会话binlog_format值需要具有足够权限来设置受限会话系统变量。参见 Section 7.1.9.1, “System Variable Privileges”。
有几个原因可能导致客户端希望按会话设置二进制日志记录:
-
对数据库进行许多小更改的会话可能希望使用基于行的记录。
-
执行更新匹配
WHERE子句中许多行的会话可能希望使用基于语句的记录,因为记录少量语句比记录许多行更有效率。 -
一些语句在源端执行时需要大量时间,但只会修改少量行。因此,使用基于行的记录可能是有益的。
在运行时无法切换复制格式的情况有例外:
-
无法从存储函数或触发器内部更改复制格式。
-
如果启用了
NDB存储引擎。 -
如果会话中有打开的临时表,则无法为该会话更改复制格式(
SET @@SESSION.binlog_format)。 -
如果任何复制通道有打开的临时表,则无法全局更改复制格式(
SET @@GLOBAL.binlog_format或SET @@PERSIST.binlog_format)。 -
如果任何复制通道应用程序线程当前正在运行,则无法全局更改复制格式(
SET @@GLOBAL.binlog_format或SET @@PERSIST.binlog_format)。
在这些情况下尝试切换复制格式(或尝试设置当前复制格式)会导致错误。但是,您可以随时使用 PERSIST_ONLY(SET @@PERSIST_ONLY.binlog_format)更改复制格式,因为此操作不会修改运行时全局系统变量值,并且仅在服务器重新启动后生效。
不建议在存在任何临时表时在运行时切换复制格式,因为仅在使用基于语句的复制时才记录临时表,而在基于行的复制和混合复制中,它们不会被记录。
当复制正在进行时切换复制格式也可能会导致问题。每个 MySQL 服务器都可以设置自己的二进制日志格式(无论 binlog_format 是以全局还是会话范围设置)。这意味着在复制源服务器上更改日志格式不会导致副本更改其日志格式以匹配。在使用 STATEMENT 模式时,binlog_format 系统变量不会被复制。在使用 MIXED 或 ROW 日志模式时,它会被复制,但副本会忽略它。
副本无法将以 ROW 日志格式接收的二进制日志条目转换为 STATEMENT 格式以在其自己的二进制日志中使用。因此,如果源使用 ROW 或 MIXED 格式,则副本必须使用 ROW 或 MIXED 格式。在复制仍在进行的情况下,将源上的二进制日志格式从 STATEMENT 更改为 ROW 或 MIXED 到具有 STATEMENT 格式的副本可能会导致复制失败,出现错误,例如执行行事件时出错:'无法执行语句:由于语句处于行格式且 BINLOG_FORMAT = STATEMENT,因此无法写入二进制日志。' 当源仍在使用 MIXED 或 ROW 格式时,将副本上的二进制日志格式更改为 STATEMENT 格式也会导致相同类型的复制失败。要安全更改格式,必须停止复制,并确保在源和副本上都进行相同的更改。
如果您正在使用 InnoDB 表,并且事务隔离级别为 READ COMMITTED 或 READ UNCOMMITTED,则只能使用基于行的日志记录。可以可能将日志格式更改为 STATEMENT,但在运行时这样做会非常快速地导致错误,因为 InnoDB 无法再执行插入操作。
将二进制日志格式设置为 ROW 后,许多更改将以基于行的格式写入二进制日志。然而,仍然有一些更改使用基于语句的格式。例如,所有 DDL(数据定义语言)语句,如 CREATE TABLE, ALTER TABLE, 或 DROP TABLE。
当使用基于行的二进制日志记录时,binlog_row_event_max_size 系统变量及其对应的启动选项 --binlog-row-event-max-size 设置了行事件的最大大小的软限制。默认值为 8192 字节,该值只能在服务器启动时更改。在可能的情况下,存储在二进制日志中的行被分组为大小不超过此设置值的事件。如果事件无法分割,则最大大小可能会超过。
--binlog-row-event-max-size 选项适用于能够进行基于行的复制的服务器。行以不超过此选项值的字节大小的块存储在二进制日志中。该值必须是 256 的倍数。默认值为 8192。
警告
当为复制使用基于语句的日志记录时,如果语句设计为数据修改是非确定性的,即由查询优化器决定,则源和副本上的数据可能会变得不同。一般来说,即使在复制之外,这也不是一个好的做法。有关此问题的详细解释,请参见 Section B.3.7, “MySQL 中的已知问题”。
7.4.4.3 混合二进制日志格式
当在MIXED日志格式下运行时,服务器在以下情况下会自动从基于语句的日志切换到基于行的日志记录:
-
当函数包含
UUID()时。 -
当更新一个或多个具有
AUTO_INCREMENT列的表并调用触发器或存储函数时。与所有其他不安全语句一样,如果binlog_format = STATEMENT,则会生成警告。更多信息,请参见第 19.5.1.1 节,“复制和 AUTO_INCREMENT”。
-
当视图的主体需要基于行的复制时,创建视图的语句也会使用它。例如,当创建视图的语句使用
UUID()函数时。 -
当涉及对可加载函数的调用时。
-
当使用
FOUND_ROWS()或ROW_COUNT()时。(Bug #12092, Bug #30244) -
当使用
USER()、CURRENT_USER()或CURRENT_USER时。(Bug #28086) -
当涉及的表之一是
mysql数据库中的日志表时。 -
当使用
LOAD_FILE()函数时。(Bug #39701) -
当语句涉及一个或多个系统变量时。(Bug #31168)
异常。 下列系统变量在会话范围(仅限)中使用时不会导致日志格式切换:
-
auto_increment_increment -
auto_increment_offset -
character_set_client -
character_set_connection -
character_set_database -
character_set_server -
collation_connection -
collation_database -
collation_server -
foreign_key_checks -
identity -
last_insert_id -
lc_time_names -
pseudo_thread_id -
sql_auto_is_null -
time_zone -
timestamp -
unique_checks
有关确定系统变量范围的信息,请参见 Section 7.1.9, “Using System Variables”。
有关复制如何处理
sql_mode的信息,请参见 Section 19.5.1.39, “Replication and Variables”。 -
在早期版本中,当使用混合二进制日志格式时,如果一条语句被记录为行,并且执行该语句的会话有任何临时表,那么所有后续语句都被视为不安全,并以行为基础的格式记录,直到该会话中使用的所有临时表都被删除。从 MySQL 8.0 开始,对临时表的操作不会以混合二进制日��格式记录,并且会话中临时表的存在不会影响每条语句使用的日志模式。
注意
如果尝试使用基于语句的日志记录执行应该使用基于行的日志记录的语句,则会生成警告。警告会显示在客户端(在SHOW WARNINGS的输出中)和通过mysqld错误日志。每次执行这样的语句时,都会向SHOW WARNINGS表中添加一个警告。但是,为了防止日志淹没,每个客户端会话中生成警告的第一条语句才会写入错误日志。
除了上述决定外,各个引擎还可以确定在更新表中的信息时使用的日志格式。各个引擎的日志记录能力可以定义如下:
-
如果一个引擎支持基于行的日志记录,那么该引擎被称为支持行日志记录。
-
如果一个引擎支持基于语句的日志记录,那么该引擎被称为支持语句日志记录。
给定的存储引擎可以支持任一或两种日志记录格式。以下表格列出了每个引擎支持的格式。
| 存储引擎 | 支持行日志记录 | 支持语句日志记录 |
|---|---|---|
ARCHIVE | 是 | 是 |
BLACKHOLE | 是 | 是 |
CSV | 是 | 是 |
EXAMPLE | 是 | 否 |
FEDERATED | 是 | 是 |
HEAP | 是 | 是 |
InnoDB | 是 | 当事务隔离级别为REPEATABLE READ或SERIALIZABLE时为是;否则为否。 |
MyISAM | 是 | 是 |
MERGE | 是 | 是 |
NDB | 是 | 否 |
| 存储引擎 | 支持行记录 | 支持语句记录 |
语句是否记录以及使用的记录模式是根据语句类型(安全、不安全或二进制注入)、二进制日志格式(STATEMENT、ROW或MIXED)以及存储引擎的记录能力(支持语句、支持行、两者都支持或两者都不支持)来确定的。(二进制注入指的是必须使用ROW格式记录的更改的记录。)
语句可能会被记录,有或没有警告;失败的语句不会被记录,但会在日志中生成错误。这在以下决策表中显示。类型,binlog_format,SLC和RLC列概述了条件,错误/警告和记录为列代表相应的操作。SLC代表“支持语句记录”,RLC代表“支持行记录”。
| 类型 | binlog_format | SLC | RLC | 错误/警告 | 记录为 |
|---|---|---|---|---|---|
| * | * | 否 | 否 | 错误:无法执行语句:由于至少有一个既不支持行也不支持语句的引擎参与其中,因此无法进行二进制日志记录。 | - |
| 安全 | STATEMENT | 是 | 否 | - | STATEMENT |
| 安全 | MIXED | 是 | 否 | - | STATEMENT |
| 安全 | ROW | 是 | 否 | 错误:无法执行语句:由于BINLOG_FORMAT = ROW且至少有一张表使用不支持基于行的日志记录的存储引擎,因此无法进行二进制日志记录。 | - |
| 不安全 | STATEMENT | 是 | 否 | 警告:由于BINLOG_FORMAT = STATEMENT,不安全语句以语句格式记录到二进制日志中 | STATEMENT |
| 不安全 | MIXED | 是 | 否 | 错误:无法执行语句:当存储引擎限制为基于语句的日志记录时,即使BINLOG_FORMAT = MIXED,也无法对不安全语句进行二进制日志记录。 | - |
| 不安全 | ROW | 是 | 否 | 错误:无法执行语句:由于BINLOG_FORMAT = ROW且至少有一张表使用不支持基于行的日志记录的存储引擎,因此无法进行二进制日志记录。 | - |
| 行注入 | STATEMENT | 是 | 否 | 错误:无法执行行注入:由于至少有一张表使用不支持基于行的日志记录的存储引擎,因此无法进行二进制日志记录。 | - |
| 行注入 | MIXED | 是 | 否 | 错误:无法执行行注入:由于至少有一张表使用不支持基于行的日志记录的存储引擎,因此无法进行二进制日志记录。 | - |
| 行注入 | ROW | 是 | 否 | 错误:无法执行行注入:由于至少有一张表使用不支持基于行的日志记录的存储引擎,因此无法进行二进制日志记录。 | - |
| 安全 | STATEMENT | 否 | 是 | 错误:无法执行语句:由于BINLOG_FORMAT = STATEMENT且至少有一张表使用不支持基于语句的日志记录的存储引擎,因此无法进行二进制日志记录。 | - |
| 安全 | MIXED | 否 | 是 | - | ROW |
| 安全 | ROW | 否 | 是 | - | ROW |
| 不安全 | STATEMENT | 否 | 是 | 错误:无法执行语句:由于 BINLOG_FORMAT = STATEMENT,并且至少有一个表使用不支持基于语句的日志记录的存储引擎,因此无法进行二进制日志记录。 | - |
| 不安全 | MIXED | 否 | 是 | - | ROW |
| 不安全 | ROW | 否 | 是 | - | ROW |
| 行注入 | STATEMENT | 否 | 是 | 错误:无法执行行注入:由于 BINLOG_FORMAT = STATEMENT,因此无法进行二进制日志记录。 | - |
| 行注入 | MIXED | 否 | 是 | - | ROW |
| 行注入 | ROW | 否 | 是 | - | ROW |
| 安全 | STATEMENT | 是 | 是 | - | STATEMENT |
| 安全 | MIXED | 是 | 是 | - | STATEMENT |
| 安全 | ROW | 是 | 是 | - | ROW |
| 不安全 | STATEMENT | 是 | 是 | 警告:由于 BINLOG_FORMAT = STATEMENT,不安全的语句以语句格式记录到二进制日志中。 | STATEMENT |
| 不安全 | MIXED | 是 | 是 | - | ROW |
| 不安全 | ROW | 是 | 是 | - | ROW |
| 行注入 | STATEMENT | 是 | 是 | 错误:无法执行行注入:由于 BINLOG_FORMAT = STATEMENT,因此无法进行二进制日志记录。 | - |
| 行注入 | MIXED | 是 | 是 | - | ROW |
| 行注入 | ROW | 是 | 是 | - | ROW |
| 类型 | binlog_format | SLC | RLC | 错误 / 警告 | 记录为 |
当决定产生警告时,会产生一个标准的 MySQL 警告(可以使用 SHOW WARNINGS 查看)。这些信息也会被写入到 mysqld 错误日志中。为了防止日志被淹没,每个客户端连接的每个错误实例只记录一个错误。日志消息包括尝试执行的 SQL 语句。
如果一个复制实例设置了 log_error_verbosity 以显示警告,那么复制实例会将消息打印到错误日志中,以提供有关其状态的信息,例如开始作业的二进制日志和中继日志坐标,切换到另一个中继日志时,重新连接后的情况,不适合基于语句的日志记录的语句等等。
原文:
dev.mysql.com/doc/refman/8.0/en/binary-log-mysql-database.html
7.4.4.4 更改mysql数据库表的日志格式
mysql数据库中授权表的内容可以直接(例如,使用INSERT或DELETE)或间接(例如,使用GRANT或CREATE USER)进行修改。影响mysql数据库表的语句将根据以下规则写入二进制日志:
-
直接更改
mysql数据库表中数据的数据操作语句将根据binlog_format系统变量的设置进行记录。这包括诸如INSERT、UPDATE、DELETE、REPLACE、DO、LOAD DATA、SELECT和TRUNCATE TABLE等语句。 -
间接更改
mysql数据库的语句将作为语句记录,不受binlog_format值的影响。这包括诸如GRANT、REVOKE、SET PASSWORD、RENAME USER、CREATE(除CREATE TABLE ... SELECT之外的所有形式)、ALTER(所有形式)和DROP(所有形式)等语句。
CREATE TABLE ... SELECT是数据定义和数据操作的组合。CREATE TABLE部分使用语句格式记录,而SELECT部分根据binlog_format的值进行记录。
原文:
dev.mysql.com/doc/refman/8.0/en/binary-log-transaction-compression.html
7.4.4.5 二进制日志事务压缩
从 MySQL 8.0.20 开始,您可以在 MySQL 服务器实例上启用二进制日志事务压缩。启用二进制日志事务压缩后,事务负载将使用 zstd 算法进行压缩,然后作为单个事件(Transaction_payload_event)写入服务器的二进制日志文件。
在将压缩的事务负载发送到副本、其他组复制组成员或客户端(如mysqlbinlog)时,压缩的事务负载保持压缩状态。接收线程不会对其进行解压缩,并且仍以压缩状态写入中继日志。因此,二进制日志事务压缩在事务发起者和接收者(以及它们的备份)上节省了存储空间,并在服务器实例之间发送事务时节省了网络带宽。
当需要检查其中包含的各个事件时,压缩的事务负载将被解压缩。例如,Transaction_payload_event由应用程序线程解压缩,以便在接收端应用其中包含的事件。在恢复期间,通过mysqlbinlog重放事务时,以及通过SHOW BINLOG EVENTS和SHOW RELAYLOG EVENTS语句进行解压缩。
你可以使用binlog_transaction_compression系统变量在 MySQL 服务器实例上启用二进制日志事务压缩,该变量默认为OFF。您还可以使用binlog_transaction_compression_level_zstd系统变量设置用于压缩的 zstd 算法的级别。该值确定了压缩的努力程度,从 1(最低努力)到 22(最高努力)。随着压缩级别的增加,压缩比例增加,从而减少了事务负载所需的存储空间和网络带宽。然而,数据压缩所需的努力也增加,消耗了源服务器上的时间、CPU 和内存资源。压缩努力的增加与压缩比例的增加之间没有线性关系。
注意
NDB 8.0.31 之前:可以在 NDB Cluster 中启用二进制日志事务压缩,但仅在使用--binlog-transaction-compression 选项(可能还包括--binlog-transaction-compression-level-zstd)启动服务器时;在运行时更改binlog_transaction_compression和binlog_transaction_compression_level_zstd系统变量的值对NDB表的日志记录没有影响。
NDB 8.0.31 及更高版本:您可以在运行时使用该版本引入的ndb_log_transaction_compression系统变量启用对使用NDB存储引擎的表的压缩事务的二进制日志记录,并使用ndb_log_transaction_compression_level_zstd控制压缩级别。在命令行或my.cnf文件中使用--binlog-transaction-compression启动mysqld会自动启用ndb_log_transaction_compression,并忽略--ndb-log-transaction-compression选项的任何设置;要仅为NDB存储引擎禁用二进制日志事务压缩,需在启动mysqld后在客户端会话中设置ndb_log_transaction_compression=OFF。
以下类型的事件不包括在二进制日志事务压缩中,因此始终以未压缩形式写入二进制日志:
-
与事务的 GTID 相关的事件(包括匿名 GTID 事件)。
-
其他类型的控制事件,例如视图更改事件和心跳事件。
-
事故事件和包含它们的任何事务的全部内容。
-
非事务事件和包含它们的任何事务的全部内容。涉及非事务性和事务性存储引擎混合的事务不会对其有效负载进行压缩。
-
使用基于语句的二进制日志记录的事件。二进制日志事务压缩仅适用于基于行的二进制日志格式。
可以在包含压缩事务的二进制日志文件上使用二进制日志加密。
7.4.4.5.1 启用二进制日志事务压缩时的行为
具有压缩有效负载的事务可以像任何其他事务一样回滚,并且也可以通过常规过滤选项在副本中过滤掉。二进制日志事务压缩可以应用于 XA 事务。
当启用二进制日志事务压缩时,服务器的max_allowed_packet和replica_max_allowed_packet或slave_max_allowed_packet限制仍然适用,并且是基于Transaction_payload_event的压缩大小加上事件头使用的字节进行计量。
重要
压缩事务负载作为一个单独的数据包发送,而不是在未使用二进制日志事务压缩时,将事务的每个事件作为单独的数据包发送。如果您的复制拓扑处理大型事务,请注意,当使用二进制日志事务压缩时,一个在未使用二进制日志事务压缩时可以成功复制的大型事务,可能由于其大小而停止复制。
对于多线程工作者,每个事务(包括其 GTID 事件和Transaction_payload_event)被分配给一个工作线程。工作线程解压缩事务负载,并逐个应用其中的各个事件。如果在Transaction_payload_event中应用任何事件时发现错误,则将报告整个事务失败给协调者。当replica_parallel_type或slave_parallel_type设置为DATABASE时,事务调度之前将受事务影响的所有数据库映射。与未压缩事务相比,使用二进制日志事务压缩与DATABASE策略可以减少并行性,后者将每个事件映射并调度。
对于半同步复制(参见 Section 19.4.10, “Semisynchronous Replication”),当完整的Transaction_payload_event被接收时,副本确认事务。
当启用二进制日志校验和(这是默认设置)时,复制源服务器不会为压缩事务负载中的单个事件写入校验和。相反,为完整的Transaction_payload_event写入校验和,并为未压缩的任何事件写入单独的校验和,例如与 GTID 相关的事件。
对于SHOW BINLOG EVENTS和SHOW RELAYLOG EVENTS语句,Transaction_payload_event首先作为一个单元打印,然后解压缩并打印其中的每个事件。
对于引用事件结束位置的操作,比如START REPLICA(或在 MySQL 8.0.22 之前的版本中,START SLAVE)带有UNTIL子句,SOURCE_POS_WAIT()或MASTER_POS_WAIT(),以及sql_replica_skip_counter或sql_slave_skip_counter,您必须指定压缩事务有效载荷(Transaction_payload_event)的结束位置。当使用sql_replica_skip_counter或sql_slave_skip_counter跳过事件时,压缩的事务有效载荷被计为单个计数器值,因此其中的所有事件都作为一个单元被跳过。
7.4.4.5.2 组合压缩和未压缩的事务有效载荷
支持二进制日志事务压缩的 MySQL 服务器版本可以处理压缩和未压缩的事务有效载荷的混合。
-
与二进制日志事务压缩相关的系统变量不需要在所有组复制组成员上设置相同,并且不会从源复制到副本在复制拓扑中。您可以决定是否对具有二进制日志的每个 MySQL 服务器实例启用二进制日志事务压缩。
-
如果启用了事务压缩,然后在服务器上禁用了压缩,则未来在该服务器上发起的事务不会应用压缩,但已经压缩的事务有效载荷仍然可以被处理和显示。
-
如果通过设置
binlog_transaction_compression的会话值为个别会话指定了事务压缩,则二进制日志可以包含压缩和未压缩的事务有效载荷的混合。
当复制拓扑中的源和其副本都启用了二进制日志事务压缩时,副本接收压缩的事务有效载荷并将其压缩写入其中继日志。它解压事务有效载荷以应用事务,然后在应用后再次压缩以写入其二进制日志。任何下游副本都会接收压缩的事务有效载荷。
当复制拓扑中的源具有启用二进制日志事务压缩,但其副本没有启用时,副本接收压缩的事务有效载荷并将其压缩写入其中继日志。它解压事务有效载荷以应用事务,然后将其未压缩写入其自己的二进制日志(如果有)。任何下游副本接收未压缩的事务有效载荷。
当复制拓扑中的源没有启用二进制日志事务压缩,但其副本启用时,如果副本具有二进制日志,则在应用事务后压缩事务有效载荷,并将压缩的事务有效载荷写入其二进制日志。任何下游副本接收压缩的事务有效载荷。
当 MySQL 服务器实例没有二进制日志时,如果它是来自 MySQL 8.0.20 的版本,则无论其对binlog_transaction_compression的值如何,它都可以接收、处理和显示压缩的事务有效载荷。这些服务器实例接收的压缩事务有效载荷以其压缩状态写入中继日志,因此它们间接受益于复制拓扑中其他服务器执行的压缩。
MySQL 8.0.20 之前的版本的副本无法从启用二进制日志事务压缩的源进行复制。MySQL 8.0.20 或更高版本的副本可以从不支持二进制日志事务压缩的早期版本的源进行复制,并在将其写入自己的二进制日志时对从该源接收的事务进行自己的压缩。
7.4.4.5.3 监视二进制日志事务压缩
您可以使用性能模式表binary_log_transaction_compression_stats监视二进制日志事务压缩的影响。统计数据包括监控期间的数据压缩比率,您还可以查看压缩对服务器上最后一个事务的影响。您可以通过截断表来重置统计信息。二进制日志和中继日志的统计数据是分开的,因此您可以看到每种日志类型的压缩影响。MySQL 服务器实例必须具有二进制日志才能生成这些统计信息。
Performance Schema 表 events_stages_current 显示事务何时处于解压缩或压缩阶段,以及显示该阶段的进度。压缩是由处理事务的工作线程在事务提交之前执行的,前提是在最终捕获缓存中没有排除事务的事件(例如,事故事件)。当需要解压缩时,会逐个事件从有效载荷中进行解压缩。
mysqlbinlog 使用 --verbose 选项时,会包含注释,说明压缩事务有效载荷的压缩大小和未压缩大小,以及所使用的压缩算法。
您可以在复制连接的协议级别启用连接压缩,使用 CHANGE REPLICATION SOURCE TO 语句(从 MySQL 8.0.23 开始)或 CHANGE MASTER TO 语句(在 MySQL 8.0.23 之前),或 replica_compressed_protocol 或 slave_compressed_protocol 系统变量的 SOURCE_COMPRESSION_ALGORITHMS | MASTER_COMPRESSION_ALGORITHMS 和 SOURCE_ZSTD_COMPRESSION_LEVEL | MASTER_ZSTD_COMPRESSION_LEVEL 选项。如果在启用连接压缩的系统中启用了二进制日志事务压缩,连接压缩的影响会减小,因为可能很少有机会进一步压缩已经压缩的事务有效载荷。但是,连接压缩仍然可以操作未压缩的事件和消息头。如果您需要节省存储空间以及网络带宽,可以在启用连接压缩的情况下启用二进制日志事务压缩。有关复制连接的连接压缩的更多信息,请参见 Section 6.2.8, “Connection Compression Control”。
对于组复制,当消息超过group_replication_compression_threshold系统变量设置的阈值时,默认启用压缩。您还可以通过使用group_replication_recovery_compression_algorithms和group_replication_recovery_zstd_compression_level系统变量,为从捐赠者的二进制日志进行状态传输的消息配置压缩。如果在配置了这些内容的系统中启用了二进制日志事务压缩,组复制的消息压缩仍然可以在未压缩事件和消息头上运行,但其影响会减小。有关组复制消息压缩的更多信息,请参见第 20.7.4 节,“消息压缩”。
7.4.5 慢查询日志
慢查询日志包含执行时间超过long_query_time秒且需要至少检查min_examined_row_limit行的 SQL 语句。慢查询日志可用于查找执行时间长且需要优化的查询。然而,检查长时间的慢查询日志可能是一项耗时的任务。为了简化这个过程,可以使用mysqldumpslow命令处理慢查询日志文件并总结其内容。参见第 6.6.10 节,“mysqldumpslow — Summarize Slow Query Log Files”。
获取初始锁的时间不计入执行时间。mysqld在执行完语句并释放所有锁之后将其写入慢查询日志,因此日志顺序可能与执行顺序不同。
-
慢查询日志参数
-
慢查询日志内容
慢查询日志参数
long_query_time的最小和默认值分别为 0 和 10。该值可以以微秒为分辨率进行指定。
默认情况下,不会记录管理语句,也不会记录未使用索引进行查找的查询。可以通过log_slow_admin_statements和log_queries_not_using_indexes进行更改,稍后会详细描述。
默认情况下,慢查询日志是禁用的。要明确指定初始慢查询日志状态,请使用--slow_query_log[={0|1}]。没有参数或参数为 1 时,--slow_query_log启用日志。参数为 0 时,此选项禁用日志。要指定日志文件名,请使用--slow_query_log_file=*file_name*。要指定日志目标,请使用log_output系统变量(如第 7.4.1 节,“选择通用查询日志和慢查询日志输出目标”中所述)。
注意
如果指定TABLE日志目的地,请参阅 Log Tables and “Too many open files” Errors。
如果未为慢查询日志文件指定名称,则默认名称为*host_name*-slow.log。服务器将在数据目录中创建该文件,除非给定绝对路径名以指定不同的目录。
要在运行时禁用或启用慢查询日志,或更改日志文件名,请使用全局slow_query_log和slow_query_log_file系统变量。将slow_query_log设置为 0 以禁用日志,设置为 1 以启用日志。将slow_query_log_file设置为指定日志文件的名称。如果已经打开了日志文件,则关闭该文件并打开新文件。
如果使用--log-short-format选项,服务器将向慢查询日志写入较少的信息。
要在慢查询日志中包含慢管理语句,请启用log_slow_admin_statements系统变量。管理语句包括ALTER TABLE、ANALYZE TABLE、CHECK TABLE、CREATE INDEX、DROP INDEX、OPTIMIZE TABLE和REPAIR TABLE。
要在慢查询日志中写入不使用索引进行行查找的查询语句,请启用log_queries_not_using_indexes系统变量。(即使启用了该变量,由于表少于两行,服务器也不会记录不受索引影响的查询。)
当记录不使用索引的查询时,慢查询日志可能会迅速增长。可以通过设置log_throttle_queries_not_using_indexes系统变量对这些查询设置速率限制。默认情况下,此变量为 0,表示没有限制。正值对不使用索引的查询的日志记录施加每分钟限制。第一个这样的查询打开一个 60 秒的窗口,在此窗口内,服务器记录达到给定限制的查询,然后抑制额外的查询。如果在窗口结束时有被抑制的查询,服务器将记录一个摘要,指示有多少个查询以及在其中花费的总时间。当服务器记录下一个不使用索引的查询时,下一个 60 秒的窗口开始。
服务器按照以下顺序使用控制参数来确定是否将查询写入慢查询日志:
-
查询必须不是管理语句,或者必须启用
log_slow_admin_statements。 -
查询必须至少花费
long_query_time秒,或者必须启用log_queries_not_using_indexes,并且查询没有使用索引进行行查找。 -
查询必须至少检查了
min_examined_row_limit行。 -
查询必须根据
log_throttle_queries_not_using_indexes设置而不被抑制。
log_timestamps系统变量控制写入慢查询日志文件(以及一般查询日志文件和错误日志)中消息的时间戳的时区。它不影响写入日志表的一般查询日志和慢查询日志消息的时区,但是可以通过CONVERT_TZ()或设置会话time_zone系统变量,将从这些表中检索的行从本地系统时区转换为任何所需的时区。
默认情况下,副本不会将复制的查询写入慢查询日志。要更改此设置,请启用系统变量log_slow_replica_statements(从 MySQL 8.0.26 开始)或log_slow_slave_statements(在 MySQL 8.0.26 之前)。请注意,如果使用基于行的复制(binlog_format=ROW),这些系统变量不起作用。仅当以语句格式在二进制日志中记录时,即当设置了binlog_format=STATEMENT时,或者当设置了binlog_format=MIXED并且语句以语句格式记录时,才会将查询添加到副本的慢查询日志中。即使启用了log_slow_replica_statements或log_slow_slave_statements,当以行格式记录慢查询时,即使设置了binlog_format=MIXED,或者当设置了binlog_format=ROW,也不会将其添加到副本的慢查询日志中。
慢查询日志内容
当慢查询日志被启用时,服务器会将输出写入由log_output系统变量指定的任何目的地。如果启用了日志,服务器会打开日志文件并将启动消息写入其中。然而,除非选择了FILE日志目的地,否则不会进一步将查询记录到文件中。如果目的地是NONE,即使启用了慢查询日志,服务器也不会写入任何查询。如果未选择FILE作为输出目的地,则设置日志文件名不会影响日志记录。
如果启用了慢查询日志并选择了FILE作为输出目的地,则写入日志的每个语句前面都会有一行以#字符开头并具有以下字段(所有字段都在一行上):
-
Query_time: *持续时间*语句执行时间(秒)。
-
Lock_time: *持续时间*获取锁所需的时间(秒)。
-
Rows_sent: *N*发送到客户端的行数。
-
Rows_examined:服务器层检查的行数(不包括存储引擎内部处理的任何处理)。
启用log_slow_extra系统变量(MySQL 8.0.14 起可用)会导致服务器将以下额外字段写入FILE输出,除了刚刚列出的字段外(TABLE`输出不受影响)。一些字段描述涉及状态变量名称。有关更多信息,请参考状态变量描述。但是,在慢查询日志中,计数器是每个语句的值,而不是每个会话的累积值。
-
Thread_id: *ID*语句线程标识符。
-
Errno: *错误编号*语句错误编号,如果没有错误则为 0。
-
Killed: *N*如果语句被终止,表示为什么的错误编号,如果语句正常终止则为 0。
-
Bytes_received: *N*Bytes_received语句的值。 -
Bytes_sent: *N*Bytes_sent语句的值。 -
Read_first: *N*Handler_read_first语句的值。 -
Read_last: *N*Handler_read_last语句的值。 -
Read_key: *N*Handler_read_key语句的值。 -
Read_next: *N*Handler_read_next语句的值。 -
Read_prev: *N*Handler_read_prev语句的值。 -
Read_rnd: *N*Handler_read_rnd语句的值。 -
Read_rnd_next: *N*Handler_read_rnd_next语句的值。 -
Sort_merge_passes: *N*Sort_merge_passes语句的值。 -
Sort_range_count: *N*Sort_range语句的值。 -
Sort_rows: *N*Sort_rows语句的值。 -
Sort_scan_count: *N*Sort_scan语句的值。 -
Created_tmp_disk_tables: *N*Created_tmp_disk_tables语句的值。 -
Created_tmp_tables: *N*Created_tmp_tables语句的值。 -
Start: *时间戳*语句执行开始时间。
-
End: *时间戳*语句执行结束时间。
给定的慢查询日志文件可能包含启用log_slow_extra后添加了额外字段的行和没有额外字段的行。日志文件分析器可以通过字段计数确定一行是否包含额外字段。
每条写入慢查询日志文件的语句前面都有一个包含时间戳的SET语句。从 MySQL 8.0.14 开始,时间戳表示慢语句开始执行的时间。在 8.0.14 之前,时间戳表示慢语句被记录的时间(这发生在语句执行完成后)。
写入慢查询日志的语句中的密码会被服务器重写,以避免明文出现。参见第 8.1.2.3 节,“密码和日志记录”。
从 MySQL 8.0.29 开始,由于语法错误等原因无法解析的语句不会被写入慢查询日志。
7.4.6 服务器日志维护
原文:
dev.mysql.com/doc/refman/8.0/en/log-file-maintenance.html
如 第 7.4 节,“MySQL 服务器日志” 中所述,MySQL 服务器可以创建几个不同的日志文件,帮助您查看正在发生的活动。然而,您必须定期清理这些文件,以确保日志不会占用太多磁盘空间。
当使用启用日志记录的 MySQL 时,您可能希望定期备份并删除旧的日志文件,并告诉 MySQL 开始记录到新文件中。参见 第 9.2 节,“数据库备份方法”。
在 Linux(Red Hat)安装中,您可以使用 mysql-log-rotate 脚本进行日志维护。如果您从 RPM 发行版安装了 MySQL,则此脚本应该已自动安装。如果您正在使用二进制日志进行复制,请小心使用此脚本。在确定所有副本都已处理其内容之前,不应删除二进制日志。
在其他系统上,您必须自己安装一个短脚本,然后从 cron(或其等效物)启动以处理日志文件。
二进制日志文件在服务器的二进制日志过期期间后会自动删除。文件的删除可以在启动时和二进制日志刷新时进行。默认的二进制日志过期期限为 30 天。要指定替代的过期期限,请使用 binlog_expire_logs_seconds 系统变量。如果您正在使用复制,您应该指定一个不低于副本可能滞后源的最长时间的过期期限。要按需删除二进制日志,请使用 PURGE BINARY LOGS 语句(参见 第 15.4.1.1 节,“PURGE BINARY LOGS 语句”)。
要强制 MySQL 开始使用新的日志文件,需要刷新日志。执行FLUSH LOGS语句或mysqladmin flush-logs、mysqladmin refresh、mysqldump --flush-logs或mysqldump --master-data命令时会发生日志刷新。参见第 15.7.8.3 节,“FLUSH 语句”、第 6.5.2 节,“mysqladmin — A MySQL Server Administration Program”和第 6.5.4 节,“mysqldump — A Database Backup Program”。此外,当当前二进制日志文件大小达到max_binlog_size系统变量的值时,服务器会自动刷新二进制日志。
FLUSH LOGS支持可选修饰符,以启用对单个日志的选择性刷新(例如,FLUSH BINARY LOGS)。参见第 15.7.8.3 节,“FLUSH 语句”。
日志刷新操作具有以下效果:
-
如果启用了二进制日志记录,服务器会关闭当前的二进制日志文件,并打开下一个序列号的新日志文件。
-
如果启用了一般查询日志或慢查询日志到日志文件的记录,服务器会关闭并重新打开日志文件。
-
如果服务器是使用
--log-error选项启动的,以将错误日志写入文件,服务器会关闭并重新打开日志文件。
执行刷新日志语句或命令需要使用具有RELOAD权限的帐户连接到服务器。在 Unix 和类 Unix 系统上,刷新日志的另一种方法是向服务器发送信号,可以由root或拥有服务器进程的帐户执行。(参见第 6.10 节,“MySQL 中的 Unix 信号处理”。)信号使得可以在不连接到服务器的情况下执行日志刷新:
-
SIGHUP信号会刷新所有日志。然而,SIGHUP除了日志刷新之外还有其他可能不希望的附加效果。 -
截至 MySQL 8.0.19 版本,
SIGUSR1会导致服务器刷新错误日志、一般查询日志和慢查询日志。如果只想刷新这些日志,SIGUSR1可以作为一个更“轻量级”的信号,不会产生与日志无关的SIGHUP效果。
如前所述,刷新二进制日志会创建一个新的二进制日志文件,而刷新一般查询日志、慢查询日志或错误日志只是关闭并重新打开日志文件。对于后者的日志,在 Unix 上,要在刷新之前重命名当前日志文件以创建一个新的日志文件。在刷新时,服务器会使用原始名称打开新的日志文件。例如,如果一般查询日志、慢查询日志和错误日志文件分别命名为mysql.log、mysql-slow.log和err.log,您可以在命令行中使用一系列命令如下:
cd *mysql-data-directory*
mv mysql.log mysql.log.old
mv mysql-slow.log mysql-slow.log.old
mv err.log err.log.old
mysqladmin flush-logs
在 Windows 上,使用rename而不是mv。
此时,您可以备份mysql.log.old、mysql-slow.log.old和err.log.old,然后将它们从磁盘中删除。
要在运行时重命名一般查询日志或慢查询日志,首先连接到服务器并禁用日志:
SET GLOBAL general_log = 'OFF';
SET GLOBAL slow_query_log = 'OFF';
禁用日志后,可以在外部重命名日志文件(例如,从命令行)。然后再次启用日志:
SET GLOBAL general_log = 'ON';
SET GLOBAL slow_query_log = 'ON';
这种方法适用于任何平台,不需要重新启动服务器。
注意
在您在外部重命名文件后,服务器重新创建给定日志文件时,文件位置必须可被服务器写入。这并非总是如此。例如,在 Linux 上,服务器可能将错误日志写入为/var/log/mysqld.log,其中/var/log由root拥有且不可写入mysqld。在这种情况下,日志刷新操作无法创建新的日志文件。
处理这种情况,您必须在重命名原始日志文件后手动创建具有适当所有权的新日志文件。例如,以root身份执行以下命令:
mv /var/log/mysqld.log /var/log/mysqld.log.old
install -omysql -gmysql -m0644 /dev/null /var/log/mysqld.log
7.5 MySQL 组件
7.5.1 安装和卸载组件
7.5.2 获取组件信息
7.5.3 错误日志组件
7.5.4 查询属性组件
7.5.5 调度器组件
MySQL Server 包括一个基于组件的基础架构,用于扩展服务器功能。组件提供对服务器和其他组件可用的服务。(在服务使用方面,服务器是一个组件,与其他组件相等。)组件之间仅通过它们提供的服务进行交互。
MySQL 发行版包含几个实现服务器扩展的组件:
-
用于配置错误日志的组件。参见第 7.4.2 节,“错误日志”和第 7.5.3 节,“错误日志组件”。
-
一个用于检查密码的组件。参见第 8.4.3 节,“密码验证组件”。
-
Keyring 组件为敏感信息提供安全存储。参见第 8.4.4 节,“MySQL Keyring”。
-
一个使应用程序能够向审计日志添加自己的消息事件的组件。参见第 8.4.6 节,“审计消息组件”。
-
实现用于访问查询属性的可加载函数的组件。参见第 11.6 节,“查询属性”。
-
用于调度主动执行任务的组件。参见第 7.5.5 节,“调度器组件”。
当组件安装时,由组件实现的系统和状态变量将被公开,并具有以组件特定前缀开头的名称。例如,log_filter_dragnet错误日志过滤组件实现了一个名为log_error_filter_rules的系统变量,其完整名称为dragnet.log_error_filter_rules。要引用此变量,请使用完整名称。
以下部分描述了如何安装和卸载组件,以及在运行时如何确定已安装的组件并获取有关它们的信息。
有关组件的内部实现信息,请参阅 MySQL Server Doxygen 文档,网址为dev.mysql.com/doc/index-other.html。例如,如果您打算编写自己的组件,了解这些信息对于理解组件的工作原理非常重要。
7.5.1 安装和卸载组件
组件必须在服务器中加载后才能使用。MySQL 支持在运行时手动加载组件和在服务器启动期间自动加载组件。
在加载组件时,可以按照 Section 7.5.2, “Obtaining Component Information”中描述的方式获取有关组件的信息。
INSTALL COMPONENT和UNINSTALL COMPONENT SQL 语句可实现组件的加载和卸载。例如:
INSTALL COMPONENT 'file://component_validate_password';
UNINSTALL COMPONENT 'file://component_validate_password';
加载程序服务处理组件的加载和卸载,并在mysql.component系统表中注册已加载的组件。
组件操作的 SQL 语句会影响服务器操作和mysql.component系统表,具体如下:
-
INSTALL COMPONENT会将组件加载到服务器中。组件会立即生效。加载程序服务还会在mysql.component系统表中注册已加载的组件。对于后续的服务器重新启动,加载程序服务会在启动序列中加载mysql.component中列出的任何组件。即使服务器使用--skip-grant-tables选项启动,也会发生这种情况。可选的SET子句允许在安装组件时设置组件系统变量的值。 -
UNINSTALL COMPONENT会停用组件并从服务器中卸载它们。加载程序服务还会从mysql.component系统表中注销这些组件,以便服务器在后续重新启动时不再在启动序列中加载它们。
与服务器插件的对应INSTALL PLUGIN语句相比,组件的INSTALL COMPONENT语句具有显著优势,不需要知道任何特定于平台的文件名后缀来命名组件。这意味着给定的INSTALL COMPONENT语句可以在各个平台上统一执行。
安装组件时,可能还会自动安装相关的可加载函数。如果是这样,卸载组件时也会自动卸载这些函数。
7.5.2 获取组件信息
原文:
dev.mysql.com/doc/refman/8.0/en/obtaining-component-information.html
mysql.component 系统表包含有关当前加载的组件的信息,并显示哪些组件已使用INSTALL COMPONENT进行注册。从表中选择显示已安装的组件。例如:
mysql> SELECT * FROM mysql.component;
+--------------+--------------------+------------------------------------+
| component_id | component_group_id | component_urn |
+--------------+--------------------+------------------------------------+
| 1 | 1 | file://component_validate_password |
| 2 | 2 | file://component_log_sink_json |
+--------------+--------------------+------------------------------------+
component_id 和 component_group_id 值仅供内部使用。component_urn 是在INSTALL COMPONENT和UNINSTALL COMPONENT语句中用于加载和卸载组件的 URN。
7.5.3 错误日志组件
原文:
dev.mysql.com/doc/refman/8.0/en/error-log-components.html
本节描述了各个错误日志组件的特性。有关配置错误日志的一般信息,请参见 Section 7.4.2, “The Error Log”。
日志组件可以是过滤器或接收器:
-
过滤器处理日志事件,以添加、删除或修改事件字段,或完全删除事件。处理后的事件传递到已启用组件列表中的下一个日志组件。
-
接收器是日志事件的目的地(写入器)。通常,接收器将日志事件处理成具有特定格式的日志消息,并将这些消息写入其关联的输出,如文件或系统日志。接收器还可以写入到性能模式
error_log表;参见 Section 29.12.21.2, “The error_log Table”。事件未经修改地传递到已启用组件列表中的下一个日志组件(即,尽管接收器格式化事件以生成输出消息,但它不会在内部传递给下一个组件时修改事件)。
log_error_services系统变量值列出了已启用的日志组件。未在列表中命名的组件已禁用。从 MySQL 8.0.30 开始,如果未加载错误日志组件,则log_error_services也会隐式加载它们。有关更多信息,请参见 Section 7.4.2.1, “Error Log Configuration”。
以下各节描述了按组件类型分组的各个日志组件:
-
过滤器错误日志组件
-
接收器错误日志组件
组件描述包括以下类型的信息:
-
组件名称和预期目的。
-
组件是内置的还是必须加载。对于可加载组件,描述指定了在使用
INSTALL COMPONENT和UNINSTALL COMPONENT语句显式加载或卸载组件时要使用的 URN。隐式加载错误日志组件只需要组件名称。有关更多信息,请参见 Section 7.4.2.1, “Error Log Configuration”。 -
组件是否可以在
log_error_services值中列出多次。 -
对于一个接收组件,组件写入输出的目的地。
-
对于一个接收组件,它是否支持与性能模式
error_log表的接口。
过滤错误日志组件
错误日志过滤组件实现对错误日志事件的过滤。如果没有启用过滤组件,则不会进行过滤。
任何启用的过滤组件仅影响稍后在log_error_services值中列出的组件的日志事件。特别是,对于在log_error_services中较早列出的任何日志接收组件,不会发生任何日志事件过滤。
log_filter_internal 组件
-
目的:实现基于日志事件优先级和错误代码的过滤,结合
log_error_verbosity和log_error_suppression_list系统变量。参见第 7.4.2.5 节“基于优先级的错误日志过滤(log_filter_internal)”。 -
URN:此组件是内置的,无需加载。
-
允许多次使用:否。
如果log_filter_internal被禁用,log_error_verbosity和log_error_suppression_list将不起作用。
log_filter_dragnet 组件
-
目的:实现根据
dragnet.log_error_filter_rules系统变量设置定义的规则进行过滤。参见第 7.4.2.6 节“基于规则的错误日志过滤(log_filter_dragnet)”。 -
URN:
file://component_log_filter_dragnet -
允许多次使用:否。
接收错误日志组件
错误日志接收组件是实现错误日志输出的写入器。如果没有启用接收组件,则不会发生日志输出。
一些接收组件描述涉及默认的错误日志目的地。这是控制台或文件,并由log_error系统变量的值表示,如第 7.4.2.2 节“默认错误日志目的地配置”中所述确定。
log_sink_internal 组件
-
目的:实现传统的错误日志消息输出格式。
-
URN:此组件是内置的,无需加载。
-
允许多次使用:否。
-
输出目的地:写入默认的错误日志目的地。
-
性能模式支持:写入
error_log表。提供一个解析器用于读取以前服务器实例创建的错误日志文件。
JSON 格式日志汇聚组件
-
目的:实现 JSON 格式的错误日志记录。参见第 7.4.2.7 节,“JSON 格式错误日志记录”。
-
URN:
file://component_log_sink_json -
多次使用允许:允许。
-
输出目的地:该汇聚根据默认错误日志目的地确定其输出目的地,该目的地由
log_error系统变量给出:-
如果
log_error指定了一个文件,汇聚将基于该文件名进行输出文件命名,加上一个以.*NN*.json为后缀的编号,其中*NN从 00 开始。例如,如果log_error是file_name*,那么在log_error_services值中连续命名的log_sink_json将写入*file_name*.00.json,*file_name*.01.json等。 -
如果
log_error是stderr,则汇聚将写入控制台。如果在log_error_services值中多次命名log_sink_json,它们都将写入控制台,这可能没有用处。
-
-
性能模式支持:写入
error_log表。提供一个解析器用于读取以前服务器实例创建的错误日志文件。
日志汇聚系统事件日志组件
-
目的:实现将错误日志记录到系统日志。这是 Windows 上的事件日志,以及 Unix 和类 Unix 系统上的
syslog。参见第 7.4.2.8 节,“将错误日志记录到系统日志”。 -
URN:
file://component_log_sink_syseventlog -
多次使用允许:不允许。
-
输出目的地:写入系统日志。不使用默认错误日志目的地。
-
性能模式支持:不写入
error_log表。不提供解析器用于读取以前服务器实例创建的错误日志文件。
日志汇聚测试组件
-
目的:用于编写测试用例的内部使用,不用于生产环境。
-
URN:
file://component_log_sink_test
由于log_sink_test是用于内部使用,因此未指定诸如是否允许多次使用和输出目的地等汇聚属性。因此,其行为可能随时发生变化。
7.5.4 查询属性组件
原文:
dev.mysql.com/doc/refman/8.0/en/query-attribute-components.html
从 MySQL 8.0.23 开始,一个组件服务提供了对查询属性的访问(参见第 11.6 节,“查询属性”)。query_attributes组件使用此服务在 SQL 语句中提供对查询属性的访问。
-
目的:实现
mysql_query_attribute_string()函数,该函数接受属性名称参数并将属性值作为字符串返回,如果属性不存在则返回NULL。 -
URN:
file://component_query_attributes
希望使用query_attributes所使用的相同查询属性组件服务的开发人员应查阅 MySQL 源代码分发中的mysql_query_attributes.h文件。
7.5.5 调度程序组件
注意
scheduler组件包含在 MySQL 企业版中,这是一款商业产品。要了解更多关于商业产品的信息,请参见www.mysql.com/products/。
截至 MySQL 8.0.34,scheduler组件提供了mysql_scheduler服务的实现,使应用程序、组件或插件可以每隔N秒配置、运行和取消配置任务。例如,audit_log服务器插件在初始化时调用scheduler组件,并配置定期刷新其内存缓存(参见启用审计日志刷新任务)。
-
目的:实现
component_scheduler.enabled系统变量,控制调度程序是否正在执行任务。在启动时,scheduler组件注册performance_schema.component_scheduler_tasks表,列出当前计划任务以及关于每个任务的一些运行时数据。 -
URN:
file://component_sheduler
安装说明,请参见第 7.5.1 节,“安装和卸载组件”。
scheduler组件使用以下元素实现服务:
-
一个按照下次运行时间(升序)排序的注册的非活动计划任务的优先级队列。
-
一个注册的活动任务列表。
-
一个后台线程:
-
如果没有任务或者顶部任务需要更多时间来运行,则休眠。它会定期唤醒以检查是否到达结束时间。
-
编译需要运行的任务列表,将其从非活动队列移动到活动队列,并逐个执行每个任务。
-
执行任务列表后,将任务从活动列表中移除,添加到非活动列表,并计算它们下次需要运行的时间。
-
当调用者调用mysql_scheduler.create()服务时,它会创建一个新的计划任务实例添加到队列中,这会向后台线程的信号量发送信号。将新任务的句柄返回给调用者。调用代码应保留此句柄和调度服务的引用,直到调用mysql_scheduler.destroy()服务后。当调用者调用destroy()并传入从create()接收的句柄时,服务会等待任务变为非活动状态(如果正在运行),然后将其从非活动队列中移除。
组件服务调用每个应用程序提供的回调(函数指针)到同一调度线程中,依次按照每个需要运行的时间的顺序。
希望将调度队列功能整合到应用程序、组件或插件中的开发人员应查阅 MySQL 源代码分发中的 mysql_scheduler.h 文件。
7.6 MySQL 服务器插件
7.6.1 安装和卸载插件
7.6.2 获取服务器插件信息
7.6.3 MySQL 企业线程池
7.6.4 Rewriter Query Rewrite 插件
7.6.5 ddl_rewriter 插件
7.6.6 版本标记
7.6.7 Clone 插件
7.6.8 Keyring Proxy Bridge 插件
7.6.9 MySQL 插件服务
MySQL 支持一个插件 API,可以创建服务器插件。插件可以在服务器启动时加载,也可以在运行时加载和卸载而无需重新启动服务器。此接口支持的插件包括但不限于存储引擎、INFORMATION_SCHEMA表、全文解析器插件和服务器扩展。
MySQL 发行版包括几个实现服务器扩展的插件:
-
用于验证客户端连接到 MySQL 服务器的尝试的插件。有几种认证协议的插件可用。参见第 8.2.17 节,“可插拔认证”。
-
一个连接控制插件,使管理员能够在一定数量的连续失败的客户端连接尝试之后引入逐渐增加的延迟。参见第 8.4.2 节,“连接控制插件”。
-
一个密码验证插件实现密码强度策略并评估潜在密码的强度。参见第 8.4.3 节,“密码验证组件”。
-
半同步复制插件实现了一个接口,允许源继续进行,只要至少有一个副本对每个事务做出响应。参见第 19.4.10 节,“半同步复制”。
-
Group Replication 使您能够在一组 MySQL 服务器实例之间创建高可用的分布式 MySQL 服务,具有数据一致性、冲突检测和解决以及组成员服务等功能。参见第二十章,Group Replication。
-
MySQL 企业版包括一个线程池插件,通过有效管理大量客户端连接的语句执行线程来增加服务器性能。参见第 7.6.3 节,“MySQL 企业线程池”。
-
MySQL Enterprise Edition 包括用于监视和记录连接和查询活动的审计插件。参见第 8.4.5 节,“MySQL 企业审计”。
-
MySQL Enterprise Edition 包括一个防火墙插件,实现应用级防火墙,使数据库管理员可以根据匹配接受的语句模式允许或拒绝 SQL 语句执行。参见第 8.4.7 节,“MySQL 企业防火墙”。
-
查询重写插件检查 MySQL 服务器接收到的语句,并在服务器执行之前可能对其进行重写。参见第 7.6.4 节,“重写器查询重写插件”,以及第 7.6.5 节,“ddl_rewriter 插件”。
-
版本令牌允许创建和同步服务器令牌,应用程序可以使用这些令牌来防止访问不正确或过时的数据。版本令牌基于一个实现
version_tokens插件和一组可加载函数的插件库。参见第 7.6.6 节,“版本令牌”。 -
Keyring 插件为敏感信息提供安全存储。参见第 8.4.4 节,“MySQL Keyring”。
在 MySQL 8.0.24 中,MySQL Keyring 开始从插件过渡到使用组件基础架构,通过使用名为
daemon_keyring_proxy_plugin的插件作为插件和组件服务 API 之间的桥梁来实现。参见第 7.6.8 节,“Keyring 代理桥插件”。 -
X Plugin 扩展了 MySQL Server 的功能,使其能够作为文档存储库。运行 X Plugin 使 MySQL Server 能够使用 X 协议与客户端通信,该协议旨在将 MySQL 的 ACID 兼容存储能力作为文档存储库暴露出来。参见第 22.5 节,“X 插件”。
-
克隆允许从本地或远程 MySQL 服务器实例克隆
InnoDB数据。参见第 7.6.7 节,“克隆插件”。 -
测试框架插件测试服务器服务。有关这些插件的信息,请参阅 MySQL Server Doxygen 文档中的 Plugins for Testing Plugin Services 部分,网址为
dev.mysql.com/doc/index-other.html。
以下各节描述了如何安装和卸载插件,以及如何在运行时确定已安装的插件并获取有关它们的信息。有关编写插件的信息,请参见 MySQL 插件 API。
7.6.1 安装和卸载插件
必须在服务器中加载服务器插件才能使用它们。MySQL 支持在服务器启动和运行时加载插件。还可以在启动时控制已加载插件的激活状态,并在运行时卸载它们。
插件加载后,可以根据第 7.6.2 节“获取服务器插件信息”中的描述获取有关插件的信息。
-
安装插件
-
控制插件激活状态
-
卸载插件
-
插件和可加载函数
安装插件
在使用服务器插件之前,必须使用以下方法之一安装它。在描述中,*plugin_name*代表插件名称,如innodb、csv或validate_password。
-
内置插件
-
注册在 mysql.plugin 系统表中的插件
-
使用命令行选项命名的插件
-
使用 INSTALL PLUGIN 语句安装的插件
内置插件
服务器会自动识别内置插件。默认情况下,服务器在启动时启用插件。一些内置插件允许使用--*plugin_name*[=*activation_state*]选项进行更改。
注册在 mysql.plugin 系统表中的插件
mysql.plugin系统表用作插件的注册表(内置插件无需注册)。在正常启动序列期间,服务器会加载在表中注册的插件。默认情况下,对于从mysql.plugin表加载的插件,服务器还会启用插件。这可以通过--*plugin_name*[=*activation_state*]选项进行更改。
如果服务器使用--skip-grant-tables选项启动,mysql.plugin表中注册的插件将不会被加载,也将无法使用。
使用命令行选项命名的插件
位于插件库文件中的插件可以通过--plugin-load、--plugin-load-add或--early-plugin-load选项在服务器启动时加载。通常,对于在启动时加载的插件,服务器还会启用该插件。这可以通过--*plugin_name*[=*activation_state*]选项进行更改。
--plugin-load和--plugin-load-add选项在服务器启动序列期间内置插件和存储引擎初始化后加载插件。--early-plugin-load选项用于加载必须在内置插件和存储引擎初始化之前可用的插件。
每个插件加载选项的值是一个以分号分隔的*plugin_library和name=plugin_library值的列表。每个plugin_library是包含插件代码的库文件的名称,每个name*是要加载的插件的名称。如果插件库的名称没有任何前置插件名称,服务器将加载库中的所有插件。有了前置插件名称,服务器将仅从库中加载指定的插件。服务器在由plugin_dir系统变量命名的目录中查找插件库文件。
插件加载选项不会在mysql.plugin表中注册任何插件。对于后续的重新启动,只有在再次提供--plugin-load、--plugin-load-add或--early-plugin-load时,服务器才会再次加载插件。也就是说,该选项产生一次性的插件安装操作,仅持续一个服务器调用。
--plugin-load、--plugin-load-add和--early-plugin-load使得即使在给定--skip-grant-tables的情况下(导致服务器忽略mysql.plugin表),也能加载插件。--plugin-load、--plugin-load-add和--early-plugin-load还使得在启动时加载无法在运行时加载的插件成为可能。
--plugin-load-add选项是对--plugin-load选项的补充:
-
每个
--plugin-load的实例都会在启动时重置要加载的插件集合,而--plugin-load-add会向要加载的插件集合添加一个或多个插件,而不会重置当前集合。因此,如果指定了多个--plugin-load的实例,只有最后一个会生效。对于多个--plugin-load-add的实例,所有实例都会生效。 -
参数格式与
--plugin-load相同,但可以使用多个--plugin-load-add的实例来避免将大量插件作为单个长而难以控制的--plugin-load参数进行指定。 -
可以在没有
--plugin-load的情况下给出--plugin-load-add,但是在--plugin-load之前出现的任何--plugin-load-add实例都不会生效,因为--plugin-load会重置要加载的插件集合。
例如,这些选项:
--plugin-load=x --plugin-load-add=y
等同于这些选项:
--plugin-load-add=x --plugin-load-add=y
并且等同于这个选项:
--plugin-load="x;y"
但这些选项:
--plugin-load-add=y --plugin-load=x
等同于这个选项:
--plugin-load=x
使用 INSTALL PLUGIN 语句安装的插件
位于插件库文件中的插件可以通过INSTALL PLUGIN语句在运行时加载。该语句还会在mysql.plugin表中注册插件,以便导致服务器在后续重新启动时加载它。因此,INSTALL PLUGIN需要对mysql.plugin表的INSERT权限。
插件库文件的基本名称取决于您的平台。Unix 和类 Unix 系统通常使用.so作为后缀,Windows 则使用.dll。
示例:--plugin-load-add选项在服务器启动时安装插件。要从名为somepluglib.so的插件库文件中安装名为myplugin的插件,请在my.cnf文件中使用以下行:
[mysqld]
plugin-load-add=myplugin=somepluglib.so
在这种情况下,插件不会在mysql.plugin中注册。在没有--plugin-load-add选项的情况下重新启动服务器会导致插件在启动时不被加载。
替代地,INSTALL PLUGIN语句会导致服务器在运行时从库文件加载插件代码:
INSTALL PLUGIN myplugin SONAME 'somepluglib.so';
INSTALL PLUGIN还会导致“永久”插件注册:插件将列在mysql.plugin表中,以确保服务器在后续重新启动时加载它。
许多插件可以在服务器启动时或运行时加载。但是,如果插件设计为必须在服务器启动期间加载和初始化,则尝试使用INSTALL PLUGIN在运行时加载插件会产生错误:
mysql> INSTALL PLUGIN myplugin SONAME 'somepluglib.so';
ERROR 1721 (HY000): Plugin 'myplugin' is marked as not dynamically
installable. You have to stop the server to install it.
在这种情况下,必须使用--plugin-load、--plugin-load-add或--early-plugin-load。
如果插件既使用--plugin-load、--plugin-load-add或--early-plugin-load选项命名,又(由于先前的INSTALL PLUGIN语句的结果)在mysql.plugin表中,服务器会启动,但会将这些消息写入错误日志:
[ERROR] Function '*plugin_name*' already exists
[Warning] Couldn't load plugin named '*plugin_name*'
with soname '*plugin_object_file*'.
控制插件激活状态
如果服务器在启动时知道插件(例如,因为插件是使用--plugin-load-add选项命名或在mysql.plugin表中注册的),服务器会默认加载和启用插件。可以使用--*plugin_name*[=*activation_state*]启动选项来控制此类插件的激活状态,其中*plugin_name*是要影响的插件名称,如innodb、csv或validate_password。与其他选项一样,选项名称中的破折号和下划线是可以互换的。此外,激活状态值不区分大小写。例如,--my_plugin=ON和--my-plugin=on是等效的。
-
--*plugin_name*=OFF告诉服务器禁用插件。对于某些内置插件,如
mysql_native_password,可能无法实现。 -
--*plugin_name*[=ON]告诉服务器启用插件。(不带值地指定选项为
--*plugin_name*具有相同效果。)如果插件初始化失败,服务器将以插件禁用状态运行。 -
--*plugin_name*=FORCE告诉服务器启用插件,但如果插件初始化失败,服务器将不会启动。换句话说,此选项强制服务器以插件启用或完全禁用的方式运行。
-
--*plugin_name*=FORCE_PLUS_PERMANENT类似于
FORCE,但另外防止插件在运行时卸载。如果用户尝试使用UNINSTALL PLUGIN这样做,将会出现错误。
插件激活状态可在信息模式PLUGINS表的LOAD_OPTION列中看到。
假设CSV、BLACKHOLE和ARCHIVE是内置的可插拔存储引擎,并且您希望服务器在启动时加载它们,但要满足以下条件:如果CSV初始化失败,服务器允许运行,必须要求BLACKHOLE初始化成功,并且应禁用ARCHIVE。为了实现这一点,在选项文件中使用以下行:
[mysqld]
csv=ON
blackhole=FORCE
archive=OFF
--enable-*plugin_name*选项格式是--*plugin_name*=ON的同义词。--disable-*plugin_name*和--skip-*plugin_name*选项格式是--*plugin_name*=OFF的同义词。
如果插件被禁用,要么明确使用OFF禁用,要么因为启用了ON但初始化失败而隐式禁用,那么需要插件的服务器操作方面发生变化。例如,如果插件实现了存储引擎,那么现有的存储引擎表将变得无法访问,并且尝试为存储引擎创建新表将导致使用默认存储引擎的表,除非启用了NO_ENGINE_SUBSTITUTION SQL 模式以导致发生错误。
禁用插件可能需要调整其他选项。例如,如果您使用--skip-innodb启动服务器以禁用InnoDB,则启动时可能还需要省略其他innodb_*xxx*选项。此外,因为InnoDB是默认存储引擎,除非您使用--default_storage_engine指定另一个可用的存储引擎,否则它无法启动。您还必须设置--default_tmp_storage_engine。
卸载插件
在运行时,UNINSTALL PLUGIN语句会禁用并卸载服务器已知的插件。该语句会卸载插件并从mysql.plugin系统表中删除它(如果在那里注册)。因此,UNINSTALL PLUGIN语句需要对mysql.plugin表具有DELETE权限。由于插件不再在表中注册,服务器在后续重新启动期间不会加载插件。
UNINSTALL PLUGIN可以卸载插件,无论它是在运行时使用INSTALL PLUGIN加载还是在启动时使用插件加载选项加载,但要满足以下条件:
-
无法卸载内置于服务器中的插件。这些可以通过信息模式
PLUGINS表或SHOW PLUGINS的输出中具有NULL库名称的插件来识别。 -
无法卸载在服务器启动时使用
--*plugin_name*=FORCE_PLUS_PERMANENT启动的插件,这会阻止运行时插件的卸载。这些可以从PLUGINS表的LOAD_OPTION列中识别出来。
要卸载当前在服务器启动时使用插件加载选项加载的插件,请使用以下步骤。
-
从
my.cnf文件中删除与插件相关的任何选项和系统变量。如果任何插件系统变量已经持久化到mysqld-auto.cnf文件中,请使用RESET PERSIST *var_name*来逐个删除它们。 -
重新启动服务器。
-
通常情况下,插件可以通过启动时的插件加载选项或在运行时使用
INSTALL PLUGIN来安装,但不能同时使用两者。然而,如果在某个时刻还使用了INSTALL PLUGIN,那么仅仅从my.cnf文件中删除插件的选项可能不足以卸载它。如果插件仍然出现在PLUGINS或SHOW PLUGINS的输出中,请使用UNINSTALL PLUGIN将其从mysql.plugin表中移除。然后再次重启服务器。
插件和可加载函数
安装插件时,可能还会自动安装相关的可加载函数。如果是这样,那么卸载插件时也会自动卸载这些函数。
7.6.2 获取服务器插件信息
原文:
dev.mysql.com/doc/refman/8.0/en/obtaining-plugin-information.html
有几种方法可以确定服务器中安装了哪些插件:
-
信息模式
PLUGINS表为每个已加载的插件包含一行。任何具有PLUGIN_LIBRARY值为NULL的插件都是内置的,无法卸载。mysql> SELECT * FROM INFORMATION_SCHEMA.PLUGINS\G *************************** 1\. row *************************** PLUGIN_NAME: binlog PLUGIN_VERSION: 1.0 PLUGIN_STATUS: ACTIVE PLUGIN_TYPE: STORAGE ENGINE PLUGIN_TYPE_VERSION: 50158.0 PLUGIN_LIBRARY: NULL PLUGIN_LIBRARY_VERSION: NULL PLUGIN_AUTHOR: Oracle Corporation PLUGIN_DESCRIPTION: This is a pseudo storage engine to represent the binlog in a transaction PLUGIN_LICENSE: GPL LOAD_OPTION: FORCE ... *************************** 10\. row *************************** PLUGIN_NAME: InnoDB PLUGIN_VERSION: 1.0 PLUGIN_STATUS: ACTIVE PLUGIN_TYPE: STORAGE ENGINE PLUGIN_TYPE_VERSION: 50158.0 PLUGIN_LIBRARY: ha_innodb_plugin.so PLUGIN_LIBRARY_VERSION: 1.0 PLUGIN_AUTHOR: Oracle Corporation PLUGIN_DESCRIPTION: Supports transactions, row-level locking, and foreign keys PLUGIN_LICENSE: GPL LOAD_OPTION: ON ... -
SHOW PLUGINS语句为每个已加载的插件显示一行。任何具有Library值为NULL的插件都是内置的,无法卸载。mysql> SHOW PLUGINS\G *************************** 1\. row *************************** Name: binlog Status: ACTIVE Type: STORAGE ENGINE Library: NULL License: GPL ... *************************** 10\. row *************************** Name: InnoDB Status: ACTIVE Type: STORAGE ENGINE Library: ha_innodb_plugin.so License: GPL ... -
mysql.plugin表显示已经通过INSTALL PLUGIN注册的插件。该表仅包含插件名称和库文件名称,因此提供的信息不如PLUGINS表或SHOW PLUGINS语句详细。
7.6.3 MySQL 企业线程池
7.6.3.1 线程池元素
7.6.3.2 线程池安装
7.6.3.3 线程池操作
7.6.3.4 线程池调优
注意
MySQL 企业线程池是 MySQL 企业版中包含的一个扩展,是一款商业产品。要了解更多关于商业产品的信息,请访问www.mysql.com/products/。
MySQL 企业版包括 MySQL 企业线程池,使用服务器插件实现。MySQL 服务器中的默认线程处理模型使用每个客户端连接一个线程执行语句。随着更多客户端连接到服务器并执行语句,整体性能会下降。线程池插件提供了一个旨在减少开销并提高性能的替代线程处理模型。该插件实现了一个线程池,通过有效管理大量客户端连接的语句执行线程来提高服务器性能。
线程池解决了使用每个连接一个线程的模型存在的几个问题:
-
太多线程堆栈使 CPU 缓存在高度并行执行工作负载中几乎无用。线程池促进线程堆栈重用,以最小化 CPU 缓存占用。
-
太多线程并行执行时,上下文切换开销很高。这也对操作系统调度程序构成挑战。线程池控制活动线程的数量,以保持 MySQL 服务器内部的并行性在一个服务器主机上可以处理的水平,并且适合 MySQL 正在执行的服务器主机。
-
太多并行执行的事务会增加资源争用。在
InnoDB中,这会增加持有中央互斥锁的时间。线程池控制事务何时开始以确保不会有太多并行执行。
其他资源
第 A.15 节,“MySQL 8.0 FAQ:MySQL 企业线程池”
原文:
dev.mysql.com/doc/refman/8.0/en/thread-pool-elements.html
7.6.3.1 线程池元素
MySQL 企业线程池包括以下元素:
-
一个插件库文件实现了线程池代码的插件以及几个相关的监控表,提供有关线程池操作的信息:
-
截至 MySQL 8.0.14,监控表是性能模式表;请参见第 29.12.16 节,“性能模式线程池表”。
-
在 MySQL 8.0.14 之前,监控表是
INFORMATION_SCHEMA表;请参见第 28.5 节,“INFORMATION_SCHEMA 线程池表”。INFORMATION_SCHEMA表现在已被弃用;预计它们将在将来的 MySQL 版本中被移除。应用程序应该从INFORMATION_SCHEMA表过渡到性能模式表。例如,如果一个应用程序使用此查询:SELECT * FROM INFORMATION_SCHEMA.TP_THREAD_STATE;应用程序应该改用此查询:
SELECT * FROM performance_schema.tp_thread_state;
注意
如果您没有加载所有监控表,一些或所有 MySQL 企业监控器线程池图可能为空。
要详细了解线程池的工作原理,请参见第 7.6.3.3 节,“线程池操作”。
-
-
有几个系统变量与线程池相关。当服务器成功加载线程池插件时,
thread_handling系统变量的值为loaded-dynamically。其他相关的系统变量由线程池插件实现,并且除非启用,否则不可用。有关使用这些变量的信息,请参见第 7.6.3.3 节,“线程池操作”和第 7.6.3.4 节,“线程池调整”。
-
性能模式具有暴露有关线程池信息的工具,可用于调查操作性能。要识别它们,请使用此查询:
SELECT * FROM performance_schema.setup_instruments WHERE NAME LIKE '%thread_pool%';有关更多信息,请参见第二十九章,“MySQL 性能模式”。
原文:
dev.mysql.com/doc/refman/8.0/en/thread-pool-installation.html
7.6.3.2 线程池安装
本节描述了如何安装 MySQL Enterprise Thread Pool。有关安装插件的一般信息,请参见第 7.6.1 节,“安装和卸载插件”。
要被服务器使用,插件库文件必须位于 MySQL 插件目录中(由plugin_dir系统变量命名的目录)。如有必要,在服务器启动时通过设置plugin_dir的值来��置插件目录位置。
插件库文件基本名称为thread_pool。文件名后缀因平台而异(例如,对于 Unix 和类 Unix 系统,为.so,对于 Windows 为.dll)。
-
MySQL 8.0.14 线程池安装
-
MySQL 8.0.14 之前的线程池安装
MySQL 8.0.14 线程池安装
在 MySQL 8.0.14 及更高版本中,线程池监控表是性能模式表,随着线程池插件一起加载和卸载。INFORMATION_SCHEMA 版本的表已被弃用,但仍可用;它们按照 MySQL 8.0.14 之前的线程池安装说明中的说明安装。
要启用线程池功能,请通过使用--plugin-load-add选项启动服务器加载插件。为此,请将以下行放入服务器的my.cnf文件中,并根据需要调整.so后缀以适应您的平台:
[mysqld]
plugin-load-add=thread_pool.so
要验证插件安装,请检查信息模式PLUGINS表或使用SHOW PLUGINS语句(参见第 7.6.2 节,“获取服务器插件信息”)。例如:
mysql> SELECT PLUGIN_NAME, PLUGIN_STATUS
FROM INFORMATION_SCHEMA.PLUGINS
WHERE PLUGIN_NAME LIKE 'thread%';
+-----------------------+---------------+
| PLUGIN_NAME | PLUGIN_STATUS |
+-----------------------+---------------+
| thread_pool | ACTIVE |
+-----------------------+---------------+
要验证性能模式监控表是否可用,请检查信息模式TABLES表或使用SHOW TABLES语句。例如:
mysql> SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'performance_schema'
AND TABLE_NAME LIKE 'tp%';
+-----------------------+
| TABLE_NAME |
+-----------------------+
| tp_thread_group_state |
| tp_thread_group_stats |
| tp_thread_state |
+-----------------------+
如果服务器成功加载线程池插件,则将thread_handling系统变量设置为loaded-dynamically。
如果插件初始化失败,请检查服务器错误日志以获取诊断消息。
MySQL 8.0.14 之前的线程池安装
在 MySQL 8.0.14 之前,线程池监视表是与线程池插件分开的插件,可以单独安装。
要启用线程池功能,请通过使用--plugin-load-add选项启动服务器加载要使用的插件。例如,如果只命名插件库文件,则服务器将加载其中包含的所有插件(即线程池插件和所有INFORMATION_SCHEMA表)。为此,请将以下行放入服务器的my.cnf文件中,并根据需要调整平台的.so后缀:
[mysqld]
plugin-load-add=thread_pool.so
这相当于通过逐个命名加载所有线程池插件:
[mysqld]
plugin-load-add=thread_pool=thread_pool.so
plugin-load-add=tp_thread_state=thread_pool.so
plugin-load-add=tp_thread_group_state=thread_pool.so
plugin-load-add=tp_thread_group_stats=thread_pool.so
如果需要,可以从库文件中加载单独的插件。要加载线程池插件但不加载INFORMATION_SCHEMA表,请使用以下选项:
[mysqld]
plugin-load-add=thread_pool=thread_pool.so
要加载线程池插件和仅TP_THREAD_STATE INFORMATION_SCHEMA表,请使用以下选项:
[mysqld]
plugin-load-add=thread_pool=thread_pool.so
plugin-load-add=tp_thread_state=thread_pool.so
要验证插件安装,请检查信息模式PLUGINS表,或使用SHOW PLUGINS语句(参见第 7.6.2 节,“获取服务器插件信息”)。例如:
mysql> SELECT PLUGIN_NAME, PLUGIN_STATUS
FROM INFORMATION_SCHEMA.PLUGINS
WHERE PLUGIN_NAME LIKE 'thread%' OR PLUGIN_NAME LIKE 'tp%';
+-----------------------+---------------+
| PLUGIN_NAME | PLUGIN_STATUS |
+-----------------------+---------------+
| thread_pool | ACTIVE |
| TP_THREAD_STATE | ACTIVE |
| TP_THREAD_GROUP_STATE | ACTIVE |
| TP_THREAD_GROUP_STATS | ACTIVE |
+-----------------------+---------------+
如果服务器成功加载线程池插件,则将thread_handling系统变量设置为loaded-dynamically。
如果插件初始化失败,请检查服务器错误日志以获取诊断消息。
原文:
dev.mysql.com/doc/refman/8.0/en/thread-pool-operation.html
7.6.3.3 线程池操作
线程池由多个线程组组成,每个组管理一组客户端连接。随着连接的建立,线程池以轮询方式将它们分配给线程组。
线程池公开了可用于配置其操作的系统变量:
-
thread_pool_algorithm: 用于调度的并发算法。 -
thread_pool_dedicated_listeners: 在每个线程组中专门指定一个监听线程,以监听分配给该组的连接的传入语句。 -
thread_pool_high_priority_connection: 如何为会话安排语句执行。 -
thread_pool_max_active_query_threads: 每组允许的活动线程数。 -
thread_pool_max_transactions_limit: 线程池插件允许的最大事务数。 -
thread_pool_max_unused_threads: 允许的休眠线程数。 -
thread_pool_prio_kickup_timer: 在低优先级队列中等待执行的语句移动到高优先级队列之前的时间。 -
thread_pool_query_threads_per_group: 在线程组中允许的查询线程数(默认为单个查询线程)。如果由于长时间运行的事务导致响应时间变慢,考虑增加该值。 -
thread_pool_size: 线程池中线程组的数量。这是控制线程池性能最重要的参数。 -
thread_pool_stall_limit: 执行语句被视为停滞之前的时间。 -
thread_pool_transaction_delay: 开始新事务之前的延迟时间。
要配置线程组的数量,请使用thread_pool_size系统变量。默认组数为 16。有关设置此变量的指导,请参见第 7.6.3.4 节“线程池调优”。
每个组的最大线程数为 4096(在某些系统上为 4095,其中一个线程在内部使用)。
线程池将连接和线程分开,因此连接和执行来自这些连接的语句的线程之间没有固定关系。这与默认的线程处理模型不同,后者将一个线程与一个连接关联起来,以便给定线程执行来自其连接的所有语句。
默认情况下,线程池尝试确保每个组中最多有一个线程在任何时候执行,但有时允许更多线程暂时执行以获得最佳性能:
-
每个线程组都有一个监听线程,用于监听分配给该组的连接的传入语句。当语句到达时,线程组要么立即开始执行它,要么将其排队以供稍后执行:
-
如果接收到的语句是唯一的,并且没有排队或当前正在执行的语句,则会立即执行。
从 MySQL 8.0.31 开始,可以通过配置
thread_pool_transaction_delay来延迟立即执行,这对事务有限制作用。有关更多信息,请参考后续讨论中对该变量的描述。 -
如果语句由于同时排队或执行的语句而无法立即开始执行,则会发生排队。
-
-
thread_pool_transaction_delay变量指定了以毫秒为单位的事务延迟。工作线程在执行新事务之前会休眠指定的时间段。在并行事务影响其他操作的性能的情况下,可以使用事务延迟。例如,如果并行事务影响索引创建或在线缓冲池调整操作,可以配置事务延迟以减少资源争用。延迟对事务有限制作用。
thread_pool_transaction_delay设置不影响从特权连接(分配给Admin线程组的连接)发出的查询。这些查询不受配置的事务延迟影响。 -
如果立即执行发生,则监听线程执行它。(这意味着暂时没有线程在组中监听。)如果语句很快完成,执行线程将返回到监听语句。否则,线程池会认为语句被阻塞,并启动另一个线程作为监听线程(必要时创建)。为了确保没有线程组被阻塞在被阻塞的语句上,线程池有一个定期监视线程组状态的后台线程。
通过使用监听线程执行可以立即开始的语句,如果语句很快完成,则无需创建额外的线程。这确保在并发线程数量较低的情况下实现最有效的执行。
当线程池插件启动时,它会为每个组创建一个线程(监听线程),以及后台线程。根据需要创建额外的线程来执行语句。
-
thread_pool_stall_limit系统变量的值确定了前一项中“快速完成”的含义。在被视为停顿之前的默认时间为 60ms,但可以设置为最大 6 秒。此参数可配置,以便您能够在服务器工作负载中找到适当的平衡。较短的等待值允许线程更快地启动。较短的值也更有利于避免死锁情况。较长的等待值对包含长期运行语句的工作负载很有用,以避免在当前语句执行时启动过多的新语句。 -
如果
thread_pool_max_active_query_threads为 0,则默认算法适用于确定每个组的活动线程的最大数量,就像前面描述的那样。默认算法考虑了停顿的线程,并可能暂时允许更多的活动线程。如果thread_pool_max_active_query_threads大于 0,则它会限制每个组的活动线程数量。 -
线程池专注于限制并发短期运行的语句数量。在执行语句达到停顿时间之前,它会阻止其他语句开始执行。如果语句执行超过停顿时间,它将被允许继续执行,但不再阻止其他语句开始。通过这种方式,线程池试图确保在每个线程组中永远不会有多个短期运行的语句,尽管可能存在多个长期运行的语句。让长期运行的语句阻止其他语句执行是不可取的,因为可能需要等待的时间没有限制。例如,在复制源服务器上,负责向副本发送二进制日志事件的线程实际上会永远运行。
-
如果语句遇到磁盘 I/O 操作或用户级锁(行锁或表锁),则该语句将被阻塞。阻塞将导致线程组变为未使用状态,因此会有回调到线程池,以确保线程池可以立即在该组中启动一个新线程来执行另一个语句。当阻塞的线程返回时,线程池允许其立即重新启动。
-
有两个队列,一个高优先级队列和一个低优先级队列。事务中的第一条语句进入低优先级队列。如果事务正在进行中(其语句已开始执行),则事务的任何后续语句进入高优先级队列,否则进入低优先级队列。通过启用
thread_pool_high_priority_connection系统变量,可以影响队列分配,导致会话的所有排队语句进入高优先级队列。针对非事务性存储引擎的语句,或者如果启用了
autocommit的事务性引擎,被视为低优先级语句,因为在这种情况下每条语句都是一个事务。因此,对于InnoDB和MyISAM表的语句混合,线程池优先处理InnoDB表的语句而不是MyISAM表的语句,除非启用了autocommit。启用autocommit后,所有语句都具有低优先级。 -
当线程组选择一个排队的语句进行执行时,首先查看高优先级队列,然后查看低优先级队列。如果找到一条语句,则将其从队列中移除并开始执行。
-
如果一条语句在低优先级队列中停留时间过长,线程池将移动到高优先级队列。
thread_pool_prio_kickup_timer系统变量的值控制移动前的时间。对于每个线程组,每 10ms(每秒 100 次)最多将一条语句从低优先级队列移动到高优先级队列。 -
线程池重复使用最活跃的线程以更好地利用 CPU 缓存。这是一个对性能有很大影响的小调整。
-
当一个线程执行来自用户连接的语句时,性能模式仪表化将线程活动归因于用户连接。否则,性能模式将活动归因于线程池。
以下是线程组可能启动多个线程执行语句的条件示例:
-
一个线程开始执行一条语句,但运行时间足够长以被视为停滞。线程组允许另一个线程开始执行另一条语句,即使第一个线程仍在执行。
-
一个线程开始执行一条语句,然后变为阻塞状态并将此情况报告给线程池。线程组允许另一个线程开始执行另一条语句。
-
一个线程开始执行一个语句,变得阻塞,但没有报告它被阻塞,因为阻塞不发生在已经使用线程池回调进行仪器化的代码中。在这种情况下,该线程对线程组来说仍在运行。如果阻塞持续时间足够长,使语句被视为停滞不前,组允许另一个线程开始执行另一个语句。
线程池设计为可扩展到越来越多的连接。它还设计为避免由于限制活动执行语句的数量而可能产生的死锁。重要的是,不向线程池报告的线程不应该阻止其他语句的执行,从而导致线程池陷入死锁。以下是此类语句的示例:
-
长时间运行的语句。这些将导致仅有少数语句使用所有资源,它们可能阻止其他所有人访问服务器。
-
读取二进制日志并将其发送到副本的二进制日志转储线程。这是一种长时间运行的“语句”,运行时间很长,不应该阻止其他语句的执行。
-
在行锁、表锁、休眠或任何其他未被 MySQL 服务器或存储引擎报告给线程池的阻塞活动上被阻塞的语句。
在每种情况下,为了防止死锁,当语句不能快速完成时,将该语句移至停滞类别,以便线程组可以允许另一个语句开始执行。通过这种设计,当线程执行或长时间阻塞时,线程池将该线程移至停滞类别,并在语句的其余执行过程中,不会阻止其他语句的执行。
可发生的线程最大数量是max_connections和thread_pool_size的总和。这可能发生在所有连接都处于执行模式且每个组创建了一个额外线程来监听更多语句的情况下。这不一定经常发生,但在理论上是可能的。
特权连接
当达到thread_pool_max_transactions_limit定义的限制时,新连接似乎会挂起,直到一个或多个现有事务完成。当尝试在现有连接上启动新事务时也会发生相同情况。如果现有连接被阻塞或长时间运行,访问服务器的唯一方法是使用特权连接。
要建立特权连接,发起连接的用户必须具有TP_CONNECTION_ADMIN权限。特权连接会忽略由thread_pool_max_transactions_limit定义的限制,并允许连接到服务器以增加限制、移除限制或终止正在运行的事务。TP_CONNECTION_ADMIN权限必须显式授予,不会默认授予任何用户。
特权连接可以执行语句和启动事务,并分配给指定为Admin线程组的线程组。
当查询performance_schema.tp_thread_group_stats表时,该表报告每个线程组的统计信息,Admin线程组的统计信息报告在结果集的最后一行。例如,如果SELECT * FROM performance_schema.tp_thread_group_stats\G返回 17 行(每个线程组一行),Admin线程组的统计信息将报告在第 17 行。
7.6.3.4 线程池调优
本节提供了关于确定线程池性能最佳配置的指导,以事务每秒等指标来衡量。
最重要的是线程池中线程组的数量,可以在服务器启动时使用--thread-pool-size选项进行设置;这个值在运行时无法更改。对于这个选项的推荐值取决于主要使用的存储引擎是InnoDB还是MyISAM:
-
如果主要存储引擎是
InnoDB,线程池大小的推荐值是主机机器上可用的物理核心数,最大不超过 512。 -
如果主要存储引擎是
MyISAM,线程池大小应该设置得相对较低。通常情况下,最佳性能在 4 到 8 之间。较高的值可能会对性能产生轻微负面影响,但不会太明显。
线程池插件可以处理的并发事务数量上限由thread_pool_max_transactions_limit的值确定。这个系统变量的推荐初始设置是物理核心数乘以 32。您可能需要根据特定工作负载调整这个值;这个值的合理上限是预期的最大并发连接数;Max_used_connections状态变量的值可以作为确定这个值的指导。一个好的方法是将thread_pool_max_transactions_limit设置为这个值,然后在观察吞吐量的影响时逐渐调整它的值。
线程组中允许的最大查询线程数由thread_pool_query_threads_per_group的值确定,可以在运行时进行调整。此值与线程池大小的乘积大致等于可用于处理查询的总线程数。通常获得最佳性能意味着在thread_pool_query_threads_per_group和线程池大小之间为您的应用程序找到适当的平衡。较大的thread_pool_query_threads_per_group值使得在线程组中同时执行长时间查询并阻塞较短查询的可能性较小,当工作负载包括长时间和短时间运行的查询时。您应该记住,当使用较小的线程池大小值和较大的thread_pool_query_threads_per_group值时,每个线程组的连接轮询操作的开销会增加。因此,我们建议将thread_pool_query_threads_per_group的起始值设置为2;将此变量设置为较低的值通常不会提供任何性能优势。
在正常情况下获得最佳性能,我们还建议将thread_pool_algorithm设置为 1 以实现高并发。
此外,thread_pool_stall_limit系统变量的值决定了阻塞和长时间运行语句的处理方式。如果所有阻塞 MySQL 服务器的调用都报告给线程池,那么它将始终知道执行线程何时被阻塞,但这并不总是正确的。例如,可能会在未使用线程池回调进行仪器化的代码中发生阻塞。对于这种情况,线程池必须能够识别似乎被阻塞的线程。这是通过由thread_pool_stall_limit的值确定的超时来完成的,该超时确保服务器不会完全被阻塞。thread_pool_stall_limit的值表示 10 毫秒间隔的数量,因此600(最大值)表示 6 秒。
thread_pool_stall_limit还可以使线程池处理长时间运行的语句。如果允许长时间运行的语句阻塞线程组,那么分配给该组的所有其他连接都将被阻塞,无法开始执行,直到长时间运行的语句完成。在最坏的情况下,这可能需要几个小时甚至几天。
应选择thread_pool_stall_limit的值,使得执行时间超过该值的语句被视为停滞。停滞的语句会产生大量额外开销,因为它们涉及额外的上下文切换,有时甚至涉及额外的线程创建。另一方面,将thread_pool_stall_limit参数设置得太高意味着长时间运行的语句会阻塞一些短时间运行的语句,时间超过必要的时间。较短的等待值允许线程更快地启动。较短的值也更有利于避免死锁情况。较长的等待值对包含长时间运行语句的工作负载有用,以避免在当前语句执行时启动太多新语句。
假设一个服务器执行的工作负载中,即使在服务器负载较重时,99.9%的语句在 100ms 内完成,而剩余的语句在 100ms 至 2 小时之间均匀分布。在这种情况下,将thread_pool_stall_limit设置为 10(10 × 10ms = 100ms)是有意义的。默认值为 6(60ms),适用于主要执行非常简单语句的服务器。
thread_pool_stall_limit 参数可以在运行时更改,以使您能够找到适合服务器工作负载的平衡。假设启用了tp_thread_group_stats表,您可以使用以下查询来确定停滞执行语句的比例:
SELECT SUM(STALLED_QUERIES_EXECUTED) / SUM(QUERIES_EXECUTED)
FROM performance_schema.tp_thread_group_stats;
这个数字应尽可能低。为了减少语句停滞的可能性,增加thread_pool_stall_limit的值。
当一个语句到达时,它实际开始执行之前可以延迟的最长时间是多少?假设满足以下条件:
-
低优先级队列中有 200 个语句在排队。
-
高优先级队列中有 10 个语句在排队。
-
thread_pool_prio_kickup_timer设置为 10000(10 秒)。 -
thread_pool_stall_limit设置为 100(1 秒)。
在最坏的情况下,这 10 个高优先级语句代表了 10 个长时间执行的事务。因此,在最坏的情况下,没有语句可以移动到高优先级队列,因为它总是包含等待执行的语句。在 10 秒后,新语句有资格被移动到高优先级队列。然而,在它被移动之前,它之前的所有语句也必须被移动。这可能需要另外 2 秒,因为每秒最多可以将 100 个语句移动到高优先级队列。现在当语句到达高优先级队列时,可能会有许多长时间运行的语句在它前面。在最坏的情况下,每一个都会被阻塞,每个语句之间需要 1 秒才能从高优先级队列中检索到下一个语句。因此,在这种情况下,新语句开始执行前需要 222 秒。
这个例子展示了一个应用程序的最坏情况。如何处理取决于应用程序。如果应用程序对响应时间有很高的要求,它很可能应该在更高的级别自行限制用户。否则,它可以使用线程池配置参数来设置某种最大等待时间。
7.6.4 Rewriter 查询重写插件
译文:
dev.mysql.com/doc/refman/8.0/en/rewriter-query-rewrite-plugin.html
7.6.4.1 安装或卸载 Rewriter 查询重写插件
7.6.4.2 使用 Rewriter 查询重写插件
7.6.4.3 Rewriter 查询重写插件参考
MySQL 支持查询重写插件,可以在服务器执行之前检查并可能修改服务器接收到的 SQL 语句。请参阅 查询重写插件。
MySQL 发行版包括一个名为 Rewriter 的后解析查询重写插件以及用于安装插件及其相关元素的脚本。这些元素共同工作,提供语句重写功能:
-
一个名为
Rewriter的服务器端插件检查语句并可能根据其内存中的重写规则缓存对其进行重写。 -
以下语句可能会被重写:
-
截至 MySQL 8.0.12:支持
SELECT、INSERT、REPLACE、UPDATE和DELETE。 -
在 MySQL 8.0.12 之前:仅支持
SELECT。
独立语句和预编译语句可能会被重写。出现在视图定义或存储程序中的语句不会被重写。
-
-
Rewriter插件使用名为query_rewrite的数据库,其中包含名为rewrite_rules的表。该表为插件决定是否重写语句提供持久存储的规则。用户通过修改存储在此表中的规则集与插件进行通信。插件通过设置表行的message列与用户进行通信。 -
query_rewrite数据库包含一个名为flush_rewrite_rules()的存储过程,将规则表的内容加载到插件中。 -
一个名为
load_rewrite_rules()的可加载函数由flush_rewrite_rules()存储过程使用。 -
Rewriter插件公开了系统变量,使插件配置和状态变量提供运行时操作信息。在 MySQL 8.0.31 及更高版本中,该插件还支持一个权限(SKIP_QUERY_REWRITE),用于保护特定用户的查询免受重写。
以下部分描述了如何安装和使用 Rewriter 插件,并提供了其相关元素的参考信息。
原文:
dev.mysql.com/doc/refman/8.0/en/rewriter-query-rewrite-plugin-installation.html
7.6.4.1 安装或卸载 Rewriter 查询重写插件
注意
如果安装了Rewriter插件,即使禁用了也会带来一些开销。为避免这种开销,除非打算使用该插件,否则不要安装它。
要安装或卸载Rewriter查询重写插件,请选择位于 MySQL 安装的share目录中的适当脚本:
-
install_rewriter.sql:选择此脚本以安装Rewriter插件及其相关元素。 -
uninstall_rewriter.sql:选择此脚本以卸载Rewriter插件及其相关元素。
按照以下方式运行所选脚本:
$> mysql -u root -p < install_rewriter.sql
Enter password: *(enter root password here)*
此处示例使用install_rewriter.sql安装脚本。如果要卸载插件,请替换为uninstall_rewriter.sql。
运行安装脚本应该会安装并启用插件。要验证,请连接到服务器并执行以下语句:
mysql> SHOW GLOBAL VARIABLES LIKE 'rewriter_enabled';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| rewriter_enabled | ON |
+------------------+-------+
有关使用说明,请参见第 7.6.4.2 节,“使用 Rewriter 查询重写插件”。有关参考信息,请参见第 7.6.4.3 节,“Rewriter 查询重写插件参考”。
原文:
dev.mysql.com/doc/refman/8.0/en/rewriter-query-rewrite-plugin-usage.html
7.6.4.2 使用 Rewriter 查询重写插件
要启用或禁用插件,请启用或禁用rewriter_enabled系统变量。默认情况下,安装插件时Rewriter插件是启用的(参见第 7.6.4.1 节,“安装或卸载 Rewriter 查询重写插件”)。要显式设置初始插件状态,可以在服务器启动时设置该变量。例如,要在选项文件中启用插件,请使用以下行:
[mysqld]
rewriter_enabled=ON
也可以在运行时启用或禁用插件:
SET GLOBAL rewriter_enabled = ON;
SET GLOBAL rewriter_enabled = OFF;
假设Rewriter插件已启用,它会检查并可能修改服务器接收到的每个可重写语句。插件根据其内存中的重写规则缓存来决定是否重写语句,这些规则从query_rewrite数据库中的rewrite_rules表中加载。
这些语句会被重写:
-
截至 MySQL 8.0.12:
SELECT、INSERT、REPLACE、UPDATE和DELETE。 -
在 MySQL 8.0.12 之前:仅
SELECT。
独立语句和准备语句会被重写。在视图定义或存储程序中出现的语句不会被重写。
从 MySQL 8.0.31 开始,具有SKIP_QUERY_REWRITE权限的用户运行的语句不会被重写,前提是rewriter_enabled_for_threads_without_privilege_checks系统变量设置为OFF(默认为ON)。这可用于控制语句和应该保持不变的语句,例如来自由CHANGE REPLICATION SOURCE TO指定的SOURCE_USER的语句。对于由 MySQL 客户端程序执行的语句,包括mysqlbinlog、mysqladmin、mysqldump和mysqlpump;因此,您应该授予SKIP_QUERY_REWRITE给这些实用程序用于连接到 MySQL 的用户帐户或帐户。
-
添加重写规则
-
语句匹配工作原理
-
重写准备语句
-
重写插件操作信息
-
字符集的重写插件使用
添加重写规则
要为Rewriter插件添加规则,请向rewrite_rules表添加行,然后调用flush_rewrite_rules()存储过程将规则从表中加载到插件中。以下示例创建了一个简单的规则,用于匹配选择单个文字值的语句:
INSERT INTO query_rewrite.rewrite_rules (pattern, replacement)
VALUES('SELECT ?', 'SELECT ? + 1');
结果表内容如下所示:
mysql> SELECT * FROM query_rewrite.rewrite_rules\G
*************************** 1\. row ***************************
id: 1
pattern: SELECT ?
pattern_database: NULL
replacement: SELECT ? + 1
enabled: YES
message: NULL
pattern_digest: NULL
normalized_pattern: NULL
规则指定了一个模式模板,指示要匹配哪些SELECT语句,并指定了一个替换模板,指示如何重写匹配的语句。但是,将规则添加到rewrite_rules表中并不足以使Rewriter插件使用该规则。您必须调用flush_rewrite_rules()将表内容加载到插件的内存缓存中:
mysql> CALL query_rewrite.flush_rewrite_rules();
提示
如果您的重写规则似乎无法正常工作,请确保通过调用flush_rewrite_rules()重新加载规则表。
当插件从规则表中读取每个规则时,它会从模式计算出一个规范化(语句摘要)形式和一个摘要哈希值,并使用它们来更新normalized_pattern和pattern_digest列:
mysql> SELECT * FROM query_rewrite.rewrite_rules\G
*************************** 1\. row ***************************
id: 1
pattern: SELECT ?
pattern_database: NULL
replacement: SELECT ? + 1
enabled: YES
message: NULL
pattern_digest: d1b44b0c19af710b5a679907e284acd2ddc285201794bc69a2389d77baedddae
normalized_pattern: select ?
有关语句摘要、规范化语句和摘要哈希值的信息,请参见第 29.10 节,“性能模式语句摘要和采样”。
如果由于某些错误而无法加载��则,则调用flush_rewrite_rules()会产生一个错误:
mysql> CALL query_rewrite.flush_rewrite_rules();
ERROR 1644 (45000): Loading of some rule(s) failed.
当发生这种情况时,插件会将错误消息写入规则行的message列,以传达问题。检查rewrite_rules表,查看具有非NULL message列值的行,以查看存在哪些问题。
模式使用与准备语句相同的语法(参见第 15.5.1 节,“PREPARE 语句”)。在模式模板中,?字符充当匹配数据值的参数标记。?字符不应包含在引号内。参数标记仅可用于数据值应出现的位置,不能用于 SQL 关键字、标识符、函数等。插件解析语句以识别文本值(如第 11.1 节,“文本值”中定义的那样),因此您可以在任何文本值的位置放置参数标记。
像模式一样,替换内容可以包含?字符。对于与模式模板匹配的语句,插件会重写它,使用数据值替换替换中的?参数标记,这些数据值由模式中相应标记匹配的数据值确定。结果是一个完整的语句字符串。插件要求服务器解析它,并将重写后的语句表示返回给服务器。
添加并加载规则后,请检查是否根据语句是否与规则模式匹配而进行重写:
mysql> SELECT PI();
+----------+
| PI() |
+----------+
| 3.141593 |
+----------+
1 row in set (0.01 sec)
mysql> SELECT 10;
+--------+
| 10 + 1 |
+--------+
| 11 |
+--------+
1 row in set, 1 warning (0.00 sec)
第一个SELECT语句不会进行重写,但第二个会。第二个语句说明了当Rewriter插件重写语句时,会生成警告消息。要查看消息,请使用SHOW WARNINGS:
mysql> SHOW WARNINGS\G
*************************** 1\. row ***************************
Level: Note
Code: 1105
Message: Query 'SELECT 10' rewritten to 'SELECT 10 + 1' by a query rewrite plugin
语句不必重写为相同类型的语句。以下示例加载一个将DELETE语句重写为UPDATE语句的规则:
INSERT INTO query_rewrite.rewrite_rules (pattern, replacement)
VALUES('DELETE FROM db1.t1 WHERE col = ?',
'UPDATE db1.t1 SET col = NULL WHERE col = ?');
CALL query_rewrite.flush_rewrite_rules();
要启用或禁用现有规则,请修改其enabled列并重新加载表到插件中。要禁用规则 1:
UPDATE query_rewrite.rewrite_rules SET enabled = 'NO' WHERE id = 1;
CALL query_rewrite.flush_rewrite_rules();
这使您可以停用规则而无需将其从表中删除。
要重新启用规则 1:
UPDATE query_rewrite.rewrite_rules SET enabled = 'YES' WHERE id = 1;
CALL query_rewrite.flush_rewrite_rules();
rewrite_rules表包含一个pattern_database列,Rewriter用于匹配未使用数据库名称限定的表名:
-
语句中的限定表名仅在相应数据库和表名相同的情况下与模式中的限定名称匹配。
-
语句中的未限定表名仅在默认数据库与
pattern_database相同且表名相同的情况下与模式中的未限定名称匹配。
假设一个名为appdb.users的表有一个名为id的列,并且应用程序预期使用以下形式之一的查询从表中选择行,第二种形式可以在appdb是默认数据库时使用:
SELECT * FROM users WHERE appdb.id = *id_value*;
SELECT * FROM users WHERE id = *id_value*;
还假设id列被重命名为user_id(也许必须修改表以添加另一种 ID,并且有必要更明确地指示id列代表什么类型的 ID)。
更改意味着应用程序必须在WHERE子句中引用user_id而不是id,但无法更新的旧应用程序将不再正常工作。Rewriter插件可以通过匹配和重写有问题的语句来解决此问题。要将语句SELECT * FROM appdb.users WHERE id = *value*匹配并重写为SELECT * FROM appdb.users WHERE user_id = *value*,您可以向重写规则表中插入代表替换规则的行。如果还想使用未限定表名匹配此SELECT,还需要添加一个显式规则。使用?作为值占位符,需要的两个INSERT语句如下:
INSERT INTO query_rewrite.rewrite_rules
(pattern, replacement) VALUES(
'SELECT * FROM appdb.users WHERE id = ?',
'SELECT * FROM appdb.users WHERE user_id = ?'
);
INSERT INTO query_rewrite.rewrite_rules
(pattern, replacement, pattern_database) VALUES(
'SELECT * FROM users WHERE id = ?',
'SELECT * FROM users WHERE user_id = ?',
'appdb'
);
添加两个新规则后,执行以下语句使其生效:
CALL query_rewrite.flush_rewrite_rules();
Rewriter使用第一条规则匹配使用限定表名的语句,第二条规则匹配使用未限定名称的语句。只有当appdb是默认数据库时,第二条规则才有效。
语句匹配原理
Rewriter插件使用语句摘要和摘要哈希值来在各个阶段匹配传入语句与重写规则。max_digest_length系统变量确定用于计算语句摘要的缓冲区大小。较大的值可以计算出区分较长语句的摘要。较小的值使用更少的内存,但增加了较长语句与相同摘要值发生冲突的可能性。
该插件将每个语句与重写规则进行匹配:
-
计算语句摘要哈希值并将其与规则摘要哈希值进行比较。这可能会产生误报,但可以作为快速拒绝测试。
-
如果语句摘要哈希值与任何模式摘要哈希值匹配,则将语句的规范形式(语句摘要)与匹配规则模式的规范形式进行匹配。
-
如果规范语句与规则匹配,则比较语句和模式中的文字值。模式中的
?字符匹配语句中的任何文字值。如果语句准备了一个语句,则模式中的?也匹配语句中的?。否则,相应的文字值必须相同。
如果多个规则匹配一个语句,则插件使用哪个规则重写语句是不确定的。
如果模式包含的标记比替换多,插件会丢弃多余的数据值。如果模式包含的标记比替换少,这将是一个错误。当加载规则表时,插件会注意到这一点,将错误消息写入规则行的message列以传达问题,并将Rewriter_reload_error状态变量设置为ON。
重写预处理语句
预处理语句在解析时(即在准备时)重写,而不是在稍后执行时。
预处理语句与非预处理语句的区别在于,它们可能包含?字符作为参数标记。要匹配预处理语句中的?,Rewriter模式必须在相同位置包含?。假设重写规则具有以下模式:
SELECT ?, 3
以下表格显示了几个预处理SELECT语句以及规则模式是否匹配它们。
| 预处理语句 | 匹配语句的模式是否匹配 |
|---|---|
PREPARE s AS 'SELECT 3, 3' | 是 |
PREPARE s AS 'SELECT ?, 3' | 是 |
PREPARE s AS 'SELECT 3, ?' | 否 |
PREPARE s AS 'SELECT ?, ?' | 否 |
重写插件操作信息
Rewriter插件通过几个状态变量提供有关其操作的信息:
mysql> SHOW GLOBAL STATUS LIKE 'Rewriter%';
+-----------------------------------+-------+
| Variable_name | Value |
+-----------------------------------+-------+
| Rewriter_number_loaded_rules | 1 |
| Rewriter_number_reloads | 5 |
| Rewriter_number_rewritten_queries | 1 |
| Rewriter_reload_error | ON |
+-----------------------------------+-------+
有关这些变量的描述,请参阅 Section 7.6.4.3.4, “Rewriter Query Rewrite Plugin Status Variables”。
当通过调用flush_rewrite_rules()存储过程加载规则表时,如果某个规则出现错误,则CALL语句会产生错误,并且插件将Rewriter_reload_error状态变量设置为ON:
mysql> CALL query_rewrite.flush_rewrite_rules();
ERROR 1644 (45000): Loading of some rule(s) failed.
mysql> SHOW GLOBAL STATUS LIKE 'Rewriter_reload_error';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Rewriter_reload_error | ON |
+-----------------------+-------+
在这种情况下,检查rewrite_rules表中具有非NULL message列值的行,以查看存在哪些问题。
重写插件使用字符集
当rewrite_rules表加载到Rewriter插件中时,插件使用当前全局值的character_set_client系统变量解释语句。如果随后更改了全局character_set_client值,则必须重新加载规则表。
客户端必须具有与加载规则表时全局值相同的会话character_set_client值,否则该客户端的规则匹配将无法工作。
原文:
dev.mysql.com/doc/refman/8.0/en/rewriter-query-rewrite-plugin-reference.html
7.6.4.3 Rewriter查询重写插件参考
以下讨论作为与Rewriter查询重写插件相关的这些元素的参考:
-
query_rewrite数据库中的Rewriter规则表 -
Rewriter过程和函数 -
Rewriter系统和状态变量
7.6.4.3.1 Rewriter查询重写插件规则表
query_rewrite数据库中的rewrite_rules表为Rewriter插件提供了规则的持久存储,用于决定是否重写语句。
用户通过修改存储在此表中的规则集与插件进行通信。插件通过设置表的message列向用户传递信息。
注意
通过flush_rewrite_rules存储过程将规则表加载到插件中。除非在最近的表修改后调用了该过程,否则表内容不一定对应插件正在使用的规则集。
rewrite_rules表具有以下列:
-
id规则 ID。此列是表的主键。您可以使用 ID 唯一标识任何规则。
-
pattern指示规则匹配语句模式的模板。使用
?表示匹配数据值的参数标记。 -
pattern_database用于匹配语句中未限定表名的数据库。语句中的限定表名仅在默认数据库与
pattern_database相同且表名相同时,才与模式中的限定名称匹配。语句中的未限定表名仅在默认数据库与pattern_database相同且表名相同时,才与模式中的未限定名称匹配。 -
replacement指示如何重写与
pattern列值匹配的语句的模板。使用?表示匹配数据值的参数标记。在重写的语句中,插件使用replacement中的?参数标记,使用与pattern中相应标记匹配的数据值进行替换。 -
enabled是否启用规则。加载操作(通过调用
flush_rewrite_rules()存储过程执行)仅在此列为YES时,将规则从表加载到Rewriter内存缓存中。此列使得可以在不移除规则的情况下停用规则:将列设置为非
YES的值,并重新加载表到插件中。 -
message插件使用此列与用户进行通信。如果在将规则表加载到内存时没有发生错误,则插件将
message列设置为NULL。非NULL值表示错误,列内容为错误消息。错误可能发生在以下情况下:-
要么模式,要么替换是产生语法错误的不正确 SQL 语句。
-
替换包含比模式更多的
?参数标记。
如果发生加载错误,插件还会将
Rewriter_reload_error状态变量设置为ON。 -
-
pattern_digest此列用于调试和诊断。如果在将规则表加载到内存时存在该列,则插件将使用模式摘要更新它。如果您试图确定某个语句未能重写的原因,此列可能会有用。
-
normalized_pattern此列用于调试和诊断。如果在将规则表加载到内存时存在该列,则插件将使用模式的规范形式更新它。如果您试图确定某个语句未能重写的原因,此列可能会有用。
7.6.4.3.2 Rewriter 查询重写插件的过程和函数
Rewriter插件操作使用一个存储过程,将规则表加载到其内存缓存中,并使用一个辅助可加载函数。在正常操作下,用户只调用存储过程。该函数旨在由存储过程调用,而不是直接由用户调用。
-
flush_rewrite_rules()此存储过程使用
load_rewrite_rules()函数将rewrite_rules表的内容加载到Rewriter内存缓存中。调用
flush_rewrite_rules()意味着COMMIT。在修改规则表后调用此存储过程,使插件从新表内容更新其缓存。如果发生任何错误,插件将为表中适当规则行设置
message列,并将Rewriter_reload_error状态变量设置为ON。 -
load_rewrite_rules()此函数是由
flush_rewrite_rules()存储过程使用的辅助程序。
7.6.4.3.3 Rewriter 查询重写插件系统变量
Rewriter查询重写插件支持以下系统变量。仅当安装了插件时才可用这些变量(请参阅第 7.6.4.1 节,“安装或卸载 Rewriter 查询重写插件”)。
-
rewriter_enabled系统变量 rewriter_enabled范围 全局 动态 是 SET_VAR提示适用否 类型 布尔值 默认值 ON有效值 OFF是否启用了
Rewriter查询重写插件。 -
rewriter_enabled_for_threads_without_privilege_checks引入版本 8.0.31 系统变量 rewriter_enabled_for_threads_without_privilege_checks作用域 全局 动态 是 SET_VAR提示适用否 类型 布尔值 默认值 ON有效值 OFF是否应用于执行时禁用权限检查的复制线程的重写。如果设置为
OFF,则跳过此类重写。需要SYSTEM_VARIABLES_ADMIN权限或SUPER权限来设置。如果
rewriter_enabled为OFF,则此变量无效。 -
rewriter_verbose系统变量 rewriter_verbose作用域 全局 动态 是 SET_VAR提示适用否 类型 整数 供内部使用。
7.6.4.3.4 Rewriter 查询重写插件状态变量
Rewriter查询重写插件支持以下状态变量。仅当插件已安装时才可用(请参阅第 7.6.4.1 节,“安装或卸载 Rewriter 查询重写插件”)。
-
Rewriter_number_loaded_rules成功从
rewrite_rules表加载到内存中供Rewriter插件使用的重写插件重写规则数量。 -
Rewriter_number_reloadsrewrite_rules表被加载到Rewriter插件使用的内存缓存中的次数。 -
Rewriter_number_rewritten_queriesRewriter查询重写插件自加载以来重写的查询次数。 -
Rewriter_reload_error上次将
rewrite_rules表加载到Rewriter插件使用的内存缓存中时是否发生错误。如果值为OFF,则未发生错误。如果值为ON,则发生了错误;请检查rewriter_rules表的message列以获取错误消息。
7.6.5 ddl_rewriter 插件
7.6.5.1 安装或卸载 ddl_rewriter
7.6.5.2 ddl_rewriter 插件选项
MySQL 8.0.16 及更高版本包括一个ddl_rewriter插件,该插件在服务器解析和执行之前修改接收到的CREATE TABLE语句。该插件删除ENCRYPTION、DATA DIRECTORY和INDEX DIRECTORY子句,这在从加密数据库或将表存储在数据目录之外的数据库创建的 SQL 转储文件中恢复表时可能会有所帮助。例如,该插件可以使这些转储文件能够恢复到未加密实例或在路径在数据目录之外不可访问的环境中。
在使用ddl_rewriter插件之前,请根据第 7.6.5.1 节“安装或卸载 ddl_rewriter”中提供的说明进行安装。
ddl_rewriter在服务器解析之前检查接收到的 SQL 语句,根据这些条件对其进行重写:
-
ddl_rewriter仅考虑CREATE TABLE语句,只有当它们是独立的语句,并且出现在输入行的开头或准备语句文本的开头时。ddl_rewriter不考虑存储程序定义中的CREATE TABLE语句。语句可以跨越多行。 -
在进行重写考虑的语句中,以下子句的实例将被重写,并且每个实例将被单个空格替换:
-
ENCRYPTION -
DATA DIRECTORY(在表和分区级别) -
INDEX DIRECTORY(在表和分区级别)
-
-
重写不依赖于大小写。
如果ddl_rewriter重写了一个语句,它会生成一个警告:
mysql> CREATE TABLE t (i INT) DATA DIRECTORY '/var/mysql/data';
Query OK, 0 rows affected, 1 warning (0.03 sec)
mysql> SHOW WARNINGS\G
*************************** 1\. row ***************************
Level: Note
Code: 1105
Message: Query 'CREATE TABLE t (i INT) DATA DIRECTORY '/var/mysql/data''
rewritten to 'CREATE TABLE t (i INT) ' by a query rewrite plugin 1 row in set (0.00 sec)
如果启用了一般查询日志或二进制日志,服务器会在任何ddl_rewriter重写后将语句写入其中。
安装后,ddl_rewriter会为跟踪插件内存使用情况暴露性能模式memory/rewriter/ddl_rewriter。请参阅第 29.12.20.10 节“内存摘要表”