目录
MySQL支持用于创建服务器组件的插件API。可以在服务器启动时加载插件,也可以在运行时加载和卸载插件,而无需重新启动服务器。该接口支持的组件包括但不限于存储引擎,INFORMATION_SCHEMA
表,全文分析器插件,分区支持和服务器扩展。
MySQL发行版包含几个实现服务器扩展的插件:
-
用于验证客户端尝试连接到MySQL Server的插件。插件可用于多种身份验证协议。请参见第6.2.13节“可插入身份验证”。
-
一个连接控制插件,使管理员可以在一定数量的连续失败的客户端连接尝试之后引入增加的延迟。请参见 第6.4.2节“连接控制插件”。
-
密码验证插件可实施密码强度策略并评估潜在密码的强度。请参见 第6.4.3节“密码验证插件”。
-
半同步复制插件实现了复制功能的接口,只要至少一个从属已响应每个事务,复制功能就允许主控继续进行。请参见第16.3.9节“同步复制”。
-
使用组复制,您可以跨一组MySQL服务器实例创建高度可用的分布式MySQL服务,并具有内置的数据一致性,冲突检测和解决以及组成员资格服务。请参见 第17章,组复制。
-
MySQL Enterprise Edition包含一个线程池插件,该插件通过有效管理大量客户端连接的语句执行线程来管理连接线程,从而提高服务器性能。请参见第5.5.3节“ MySQL企业线程池”。
-
MySQL Enterprise Edition包含一个审计插件,用于监视和记录连接和查询活动。请参见第6.4.5节“ MySQL企业审核”。
-
MySQL Enterprise Edition包含一个防火墙插件,该插件实现了应用程序级防火墙,以使数据库管理员可以根据与接受的语句模式的白名单匹配来允许或拒绝SQL语句执行。请参见 第6.4.6节“ MySQL企业防火墙”。
-
一个查询重写插件会检查MySQL Server收到的语句,并可能在服务器执行它们之前对其进行重写。请参见第5.5.4节“重写器查询重写插件”
-
通过版本令牌,可以创建服务器令牌并围绕服务器令牌进行同步,应用程序可以使用这些令牌来防止访问不正确或过时的数据。Version Tokens 基于一个插件库,它实现了一个
version_tokens
插件和一组 user-defined 函数 。请参见第5.5.5节“版本令牌”。 -
密钥环插件可为敏感信息提供安全的存储。请参见第6.4.4节“ MySQL密钥环”。
-
X插件扩展了MySQL Server,使其能够充当文档存储。运行X插件使MySQL服务器可以使用X协议与客户端通信,该协议旨在将MySQL的ACID兼容存储功能公开为文档存储。请参见第19.7节“ X插件”。
-
测试框架插件测试服务器服务。有关这些插件的信息,请参见MySQL Server Doxygen文档的“用于测试插件服务的插件”部分,网址为 https://dev.mysql.com/doc/index-other.html。
以下各节介绍如何安装和卸载插件,以及如何在运行时确定安装了哪些插件并获取有关它们的信息。有关编写插件的信息,请参见第28.2节“ MySQL插件API”。
安装和卸载插件
必须先将服务器插件加载到服务器中,然后才能使用它们。MySQL支持在服务器启动和运行时加载插件。也可以在启动时控制已加载插件的激活状态,并在运行时卸载它们。
加载插件时,可从INFORMATION_SCHEMA.PLUGINS
表和SHOW PLUGINS
语句中获得有关它的信息。请参见 第5.5.2节“获取服务器插件信息”。
安装插件
在使用服务器插件之前,必须使用以下方法之一安装它。在说明中, plugin_name
表示插件的名称,例如innodb
,csv
或 validate_password
。
内置插件:
服务器会自动识别一个内置插件。通常,服务器在启动时启用插件。一些内置插件允许使用--plugin_name[=activation_state]
选项更改此设置。
在mysql.plugin
系统表中注册的插件 :
mysql
系统数据库中的plugin
table 充当插件的注册表(built-in 插件除外,无需注册)。在启动时,服务器将加载表中列出的每个插件。通常,对于从mysql.plugin
表中加载的插件 ,服务器也会启用该插件。可以使用--plugin_name[=activation_state]
选项更改此设置。
如果服务器使用该--skip-grant-tables
选项启动 ,则它不会查询该mysql.plugin
表,也不会加载该表中列出的插件。
用命令行选项命名的插件:
位于插件 library 文件中的插件可以在服务器启动时使用--plugin-load,--plugin-load-add或(从 MySQL 5.7.11)--early-plugin-load选项加载。通常,对于启动时加载的插件,服务器也会启用插件。可以使用--plugin_name[=activation_state]
选项更改此设置。
在服务器启动过程中初始化内置插件和存储引擎之后 ,--plugin-load
和 --plugin-load-add
选项会加载插件。该 --early-plugin-load
选项用于加载在初始化内置插件和存储引擎之前必须可用的插件。
每个插件加载的选项值来源于以逗号分隔的name
=
plugin_library
和 plugin_library
值,每个name
是要加载的插件的名,plugin_library
是包含插件代码的库文件的名。如果插件库的名称前面没有任何插件名,则服务器会加载库中的所有插件。服务器在plugin_dir系统变量指定的目录中查找插件 library files
插件加载选项不会在mysql.plugin
表中注册任何插件 。对于后续重新启动时,仅当再次给出--plugin-load,--plugin-load-add或--early-plugin-load时,服务器才会再次加载插件。也就是说,该选项会生成一次插件安装操作,该操作会在单次服务器调用时保持不变。
--plugin-load,--plugin-load-add和--early-plugin-load即使在给出--skip-grant-tables时也会加载插件(这会导致服务器忽略mysql.plugin
table)。 --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-add可以在没有--plugin-load的情况下给出,但是在--plugin-load之前出现的--plugin-load-add的任何实例都没有效果,因为--plugin-load重置了要加载的插件集。
例如,这些选项:
--plugin-load=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
特权 。
插件库文件的基本名称取决于您的平台。通用后缀.so适用于Unix和类Unix系统,.dll
适用于Windows。
要从名为somepluglib.so
的插件 library 文件安装名为myplugin
的插件,请在my.cnf
文件中使用这些语句:
[mysqld]
plugin-load=myplugin=somepluglib.so
在这种情况下,插件未在mysql.plugin
中注册。在没有--plugin-load选项的情况下重新启动服务器会导致启动时不加载插件。
或者,该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选项命名或在mysql.plugin
table 中注册),服务器默认加载并启用插件。可以使用--plugin_name[=activation_state]
启动选项控制此类插件的激活 state,其中plugin_name
是要影响的插件的 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
进行操作,则会发生错误。
插件激活状态在INFORMATIONSCHEMA.PLUGINS table 的LOAD_OPTION
列中可见。
假设CSV
,BLACKHOLE
和ARCHIVE
是 built-in 可插入存储引擎,并且您希望服务器在启动时加载它们,受以下条件限制:如果CSV
初始化失败,则允许服务器 run,必须要求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
table 的删除权限。由于插件不再在 table 中注册,服务器不会自动加载插件以便后续重新启动。
UNINSTALL PLUGIN
可以在满足以下条件的情况下卸载插件,无论它是在运行时使用INSTALL PLUGIN
加载还是在启动时使用 plugin-loading 选项加载,具体取决于以下条件:
-
它无法卸载内置于服务器的插件。它们在
INFORMATION_SCHEMA.PLUGINS
orSHOW PLUGINS输出中库名为NULL
。
-
它无法卸载服务器以
--plugin_name=FORCE_PLUS_PERMANENT
启动的插件,这会阻止插件在运行时卸载。这些可以从INFORMATIONSCHEMA.PLUGINS 表的LOAD_OPTION
列中识别。
要卸载当前在服务器启动时使用 plugin-loading 选项加载的插件,请使用此过程。
-
从
my.cnf
文件中删除与插件相关的所有选项。 -
重启服务器。
-
插件通常在启动时使用 plugin-loading 选项安装,或在运行时使用
INSTALL PLUGIN
安装,但不能同时安装。但是,如果在某些时候使用INSTALL PLUGIN安装
,则从my.cnf
文件中删除插件的选项可能不足以卸载它。如果插件仍然出现在INFORMATIONSCHEMA.PLUGINS或SHOW PLUGINS
的输出中,请使用UNINSTALL PLUGIN
将其从mysql.plugin
table 中删除。然后再次重启服务器。
获取服务器插件信息
有几种方法可以确定服务器中安装了哪些插件:
- 该
INFORMATION_SCHEMA.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: MySQL AB
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: Innobase Oy
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
语句一样多的信息。
MySQL企业线程池
注意
MySQL Enterprise Thread Pool 是 MySQL Enterprise Edition 中的扩展,是企业产品。要了解有关商业产品的更多信息,请https://www.mysql.com/products/。
MySQL Enterprise Edition包含使用服务器插件实现的MySQL Enterprise Thread Pool。MySQL Server中的默认线程处理模型在每个客户端连接中使用一个线程执行语句。随着越来越多的客户端连接到服务器并执行语句,整体性能会下降。线程池插件提供了另一种线程处理模型,旨在减少开销并提高性能。该插件实现了一个线程池,可通过有效地管理大量客户端连接的语句执行线程来提高服务器性能。
线程池解决了每个连接使用一个线程的模型的几个问题:
- 太多的线程堆栈使CPU缓存在高度并行执行工作负载中几乎无用。线程池促进线程堆栈的重用,以最大程度地减少CPU缓存的占用。
- 由于并行执行的线程过多,上下文切换开销很高。这也给操作系统调度程序提出了艰巨的任务。线程池控制活动线程的数量,以将MySQL服务器内的并行性保持在它可以处理的水平,并且适用于 MySQL 正在执行的服务器主机。
- 并行执行的事务过多会增加资源争用。在
InnoDB
中,这会增加保持中央互斥锁所花费的时间。线程池控制事务何时开始,以确保并行执行的事务不太多。
线程池组件
线程池功能包括以下组件:
- 插件 library 文件实现了线程池 code 的插件以及提供有关线程池操作的信息的几个相关监视表。
INFORMATION_SCHEMA
表中TP_THREADSTATE,TP_THREADGROUPSTATE和TP_THREADGROUP_STATS这些表,提供有关线程池操作的信息。有关更多信息,请参阅第 24.33 节,“INFORMATIONSCHEMA 线程池表”。 - 几个系统变量与线程池相关。当服务器成功加载线程池插件时,thread_handling系统变量的 value 为
loaded-dynamically
。
其他相关变量由线程池插件实现;除非启用它们,否则它们不可用:
-
thread_pool_algorithm
:用于调度的并发算法。 -
thread_pool_high_priority_connection
:如何安排会话的语句执行。 -
thread_pool_prio_kickup_timer
:线程池将等待执行的语句从低优先级队列移至高优先级队列的时间。 -
thread_pool_max_unused_threads
:允许多少个睡眠线程。 -
thread_pool_size
:线程池中的线程组数。这是控制线程池性能的最重要参数。 -
thread_pool_stall_limit
:执行语句被认为停止之前的时间。
如果在启动时将插件实现的任何变量设置为非法值,则插件初始化失败,并且不会加载插件。
- 性能架构( Performance Schema)的工具公开了有关线程池的信息,可用于调查操作性能。要识别它们,请使用以下查询:
SELECT * FROM performance_schema.setup_instruments
WHERE NAME LIKE '%thread_pool%';
有关更多信息,请参阅第 25 章,MySQL Performance Schema。
线程池安装
本节介绍如何安装MySQL Enterprise Thread Pool。有关安装插件的一般信息,请参见 第5.5.1节“安装和卸载插件”。
要使服务器可以使用,插件 library 文件必须位于 MySQL 插件目录(由plugin_dir系统变量命名的目录)中。如有必要,通过在服务器启动时设置plugin_dir的 value 来配置插件目录位置。
插件库文件的基本名称为 thread_pool
。每个平台的文件名后缀都不同(例如,.so
对于Unix和类似Unix的系统,.dll
对于Windows)。
[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
如果需要,您可以从 library 文件加载单个插件。要加载线程池插件但不加载INFORMATION_SCHEMA
表,请使用如下选项:
[mysqld]
plugin-load-add=thread_pool=thread_pool.so
要加载线程池插件并仅加载INFORMATION_SCHEMA
库中TP_THREADSTATE 表,请使用以下选项:
[mysqld]
plugin-load-add=thread_pool=thread_pool.so
plugin-load-add=tp_thread_state=thread_pool.so
注意
如果未加载所有INFORMATION_SCHEMA
表,则部分或全部 MySQL Enterprise Monitor 线程池图将为空。
要验证插件安装,请检查 INFORMATION_SCHEMA.PLUGINS
表或使用以下SHOW PLUGINS
语句
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
。
如果插件未能初始化,请检查服务器错误日志以获取诊断消息。
线程池操作
线程池由多个线程组组成,每个线程组管理一组客户端连接。建立连接后,线程池将以循环方式将它们分配给线程组。
线程组的数量可以使用thread_pool_size
系统变量进行配置 。组的默认数目为16。有关设置此变量的准则,请参见第5.5.3.4节“线程池调整”。
每个组的最大线程数为4096(在内部使用一个线程的某些系统上为4095)。
线程池将连接和线程分开,因此连接与执行从那些连接接收的语句的线程之间没有固定的关系。这不同于默认的线程处理模型,该模型将一个线程与一个连接相关联,以使给定线程执行连接时的所有语句。
线程池尝试确保在任何时候在每个组中最多执行一个线程,但是有时允许更多线程临时执行以达到最佳性能。该算法以以下方式工作:
- 每个线程 group 都有一个 listener 线程,它从分配给 group 的连接中侦听传入的 statements。当一个语句到达时,线程 group 要么立即开始执行它,要么将它排队等待以后执行:
-
如果该语句是唯一接收到的语句,并且没有语句排队或当前正在执行,则立即执行。
-
如果语句不能立即开始执行,则会发生排队。
- 如果立即执行,则由 listener 线程执行。这意味着 group 中暂时没有线程被监听,如果语句快速完成,执行线程将返回监听 语句。否则,线程池认为语句停止并启动另一个线程作为 listener 线程(如果需要,创建它)。为了确保没有线程 group 被停滞的 statements 阻塞,线程池有一个后台线程,定期监视线程 group 状态。 通过使用侦听线程执行可以立即开始的语句,如果该语句快速完成,则无需创建其他线程。这样可以确保在并发线程数较少的情况下尽可能高效地执行。
当线程池插件启动时,它会为每个 group创建一个线程(listener 线程)加上后台线程。必要时会创建其他线程来执行语句。
- thread_pool_stall_limit系统变量的值决定着前一个项目“快速完成”的时间。线程被视为停顿之前的默认 time 是 60ms,但可以设置为最大 6s。此参数是可配置的,使您可以平衡服务器的工作负载。较短的等待值使线程可以更快地启动。较短的值也可以更好地避免死锁情况。长等待值对于包含长时间运行的语句的工作负载很有用,以避免在当前语句执行时启动太多新语句。
- 线程池的重点是限制并发短运行语句的数量。在执行语句到达停顿时间之前,它会阻止其他语句开始执行。如果该语句执行超过停顿时间,则可以继续执行该语句,但不再阻止其他语句启动。通过这种方式,线程池将尝试确保每个线程组中的短运行语句不会超过一个,尽管可能会有多个长运行语句。让长时间运行的语句阻止其他语句执行是不可取的,因为对等待的数量没有限制。例如,在复制 master 上,将二进制 log events 发送到从站的线程有效地永远运行。
- 如果语句遇到磁盘I / O操作或用户级别的锁(行锁或表锁),则该语句将被阻塞。该块将导致线程组变为未使用状态,因此对线程池进行了回调,以确保线程池可以立即启动该组中的新线程来执行另一条语句。当阻塞的线程返回时,线程池允许它立即重新启动。
- 有两个队列,一个高优先级队列和一个低优先级队列。事务中的第一条语句进入低优先级队列。如果事务正在进行(事务的语句已开始执行),则该事务的以下任何语句都将进入高优先级队列,否则,将进入低优先级队列。启用
thread_pool_high_priority_connection
系统变量会影响队列分配 ,这会导致会话的所有排队语句进入高优先级队列。 - 非事务存储引擎的语句,或者开启了
autocommit
的事务型存储引擎的语句,将被视为低优先级语句( low-priority statements),因为在这种情况下,每个语句都是 transaction。因此,给了一条涉及InnoDB
和MyISAM
表的混合语句,则在线程池中的优先级InnoDB高于MyISAM,除非自动提交开启。
启用自动提交后,所有 statements 都将为低优先级。 - 当线程 group 选择要执行的排队语句时,它首先查找 high-priority 队列,然后查找 low-priority 队列。如果找到一个语句,它将从队列中删除并开始执行。
- 如果语句在 low-priority 队列中停留的时间太长,则线程池将移至 high-priority 队列。 thread_pool_prio_kickuptimer系统变量的 value 控制移动前的 time。对于每个线程组,每条语句的最大值10ms或每秒100条将从低优先级队列移至高优先级队列。
- 线程池重用最活跃的线程,以更好地利用CPU缓存。这是一个很小的调整,会对性能产生重大影响。
- 当线程从用户连接执行语句时,Performance Schema 检测会将线程活动记录到用户连接。否则,Performance Schema 会将活动记入线程池。
以下是一个线程组可能有多个线程开始执行语句的条件示例:
-
一个线程开始执行一条语句,但是运行了足够长的时间才被认为已停止。即使第一个线程仍在执行,线程组也允许另一个线程开始执行另一个语句。
-
一个线程开始执行一条语句,然后被阻塞,并将其报告回线程池。线程组允许另一个线程开始执行另一个语句。
-
一个线程开始执行一条语句,被阻塞,但是没有报告该语句被阻塞,因为在使用线程池回调进行检测的代码中不会发生阻塞。在这种情况下,线程在线程组中似乎仍在运行。如果该块的持续时间足够长,以致该语句被认为已停止,则该组将允许另一个线程开始执行另一个语句。
线程池被设计为可在越来越多的连接上进行扩展。它还旨在避免因限制正在执行的语句数而引起的死锁。重要的是,不向线程池报告的线程不会阻止其他语句的执行,从而导致线程池死锁。此类声明的示例如下:
-
长时间运行的语句。这些将导致仅少数语句使用所有资源,并且它们可能阻止所有其他语句访问服务器。
-
二进制日志转储线程读取二进制日志并将其发送到从属服务器。这是一种运行 时间很长的“ 语句 ”,不应阻止其他语句的执行。
-
在行锁,表锁,睡眠或任何其他未被MySQL Server或存储引擎报告回线程池的阻塞活动中被阻塞的语句。
在每种情况下,为了防止死锁,当语句没有快速完成时,语句将被移动到停滞的类别,以便线程 group 可以允许另一个语句开始执行。使用此设计,当线程执行或阻塞很长时间时,线程池会将线程移动到停滞的类别,并且对于执行语句的剩余部分,它不会阻止其他 statements 执行。
可能出现的最大线程数是max_connections和thread_pool_size的总和。这种情况可能发生在所有连接都处于执行模式并且每个 group 创建额外线程以侦听更多 statements 的情况下。这不一定是经常发生的 state,但理论上是可行的。
线程池调整
本节提供有关设置线程池系统变量以获得最佳性能的准则,该准则使用诸如每秒事务之类的度量标准进行度量。
thread_pool_size
是控制线程池性能的最重要参数。只能在服务器启动时设置。我们在测试线程池方面的经验表明:
-
如果主存储引擎是
InnoDB
,则thread_pool_size
最佳设置可能在16到36之间,最常见的最佳值往往在24到36之间。我们没有看到任何设置超过36的最佳情况。可能存在小于 16 的 value 是最佳的特殊情况。对于诸如DBT2和Sysbench之类的工作负载,最佳
InnoDB
似乎通常约为36。对于非常密集的写工作负载,最佳设置有时可能会更低。 -
如果主存储引擎为
MyISAM
,则该thread_pool_size
设置应该相当低。通常可以从4到8看到最佳性能。较高的值往往会对性能产生轻微的负面影响,但影响不大。
另一个系统变量 thread_pool_stall_limit
对处理阻塞的和长时间运行的语句很重要。如果所有阻止MySQL服务器的调用都报告给线程池,它将始终知道何时阻止执行线程。但是,这可能并不总是正确的。例如,块(blocks)可能出现在 code 中,但尚未使用线程池回调进行检测。对于这种情况,线程池必须能够识别看似被阻止的线程。这是通过超时完成的,超时可以使用thread_pool_stall_limit系统变量调整其长度。此参数可确保服务器不会被完全阻止。 thread_pool_stall_limit的 value 的上限为 6 秒,以防止服务器死锁的风险。
thread_pool_stall_limit
还使线程池能够处理长时间运行的语句。如果允许长时间运行的语句阻止线程组,则分配给该组的所有其他连接都将被阻止,并且在长时间运行的语句完成之前无法开始执行。在最坏的情况下,这可能需要数小时甚至数天。
thread_pool_stall_limit是需要去设置的,
使得执行时间长于此值的 statements 被认为是停滞的。暂停语句会产生大量额外的开销,因为它们涉及额外的上下文切换,在某些情况下甚至会产生额外的线程创建。另一方面,将thread_pool_stall_limit参数设置得太高意味着长时间运行的语句阻塞短时间运行的语句的数量将会超过预期。短等待值允许线程更快地启动。短值也可以更好地避免死锁情况。 长等待值对包含 long-running statements 的工作负载很有用,以避免在当前执行时启动过多的新语句。
假设服务器执行工作负载,其中 99.9%statements 在 100ms 内完成,即使服务器已加载,剩余的语句在 100ms 到 2 小时之间相当均匀地传播。在这种情况下,将thread_pool_stall_limit设置为 10(意味着 100ms)是有意义的。默认的 value 为 60ms 适用于主要执行非常简单的语句的服务器。
可以在运行时更改thread_pool_stall_limit参数,以使您能够获得适合服务器工作负载的平衡。假设启用了TP_THREAD_GROUP_STATS
表,您可以使用以下查询来确定已执行语句被阻塞的比例:
SELECT SUM(STALLED_QUERIES_EXECUTED) / SUM(QUERIES_EXECUTED)
FROM INFORMATION_SCHEMA.TP_THREAD_GROUP_STATS;
这个数字应该尽可能低。要减少 statements 停止的可能性,请增加thread_pool_stall_limit的 value。
当一个语句到达时,它在实际开始执行之前可以延迟的最大 time 是多少?假设以下条件适用:
-
low-priority 队列中有 200 个 statements 排队。
-
high-priority 队列中有 10 个 statements 排队。
-
thread_pool_prio_kickuptimer设置为 10000(10 秒)。
-
thread_pool_stall_limit设置为 100(1 秒)。
在最坏的情况下,这10个高优先级语句表示10个长时间持续执行的事务。因此,在最坏的情况下,不会将任何语句移到高优先级队列,因为它始终已经包含等待执行的语句。10秒钟后,新语句可以移动到高优先级队列中。但是,在可以移动它之前,必须先移动它之前的所有语句。这可能还需要2秒钟,因为每秒最多将100条语句移到高优先级队列。现在,当语句到达高优先级队列时,它前面可能会存在许多长时间运行的语句。在最坏的情况下 这些语句中的每一个都将停止,对于每个语句,将需要1秒钟才能从高优先级队列中检索下一条语句。因此,在这种情况下,将需要222秒才能开始执行新语句。
此示例显示了应用程序的最坏情况。如何处理取决于应用程序。如果应用程序对响应时间有很高的要求,则它很可能应该将用户自身限制在较高级别。否则,它可以使用线程池配置参数来设置某种最大等待时间。
重写器-查询重写插件
MySQL支持查询重写插件,该插件可以在服务器执行SQL语句之前检查并可能修改服务器收到的SQL语句。请参阅查询重写插件。
MySQL 发行版包括一个名为Rewriter
的 postparse 查询 rewrite 插件和用于安装插件及其相关组件的脚本。这些组件协同工作以提供SELECT
重写功能:
- 一个名为
Rewriter
的 server-side 插件会基于内存中缓存的重写规则,检查SELECT
语句并且可能重写他们。准备好的语句中SELECT
语句和独立的SELECT
语句可能会被重写,视图定义或存储程序中出现的SELECT
语句不会被重写。 Rewriter
插件使用名为query_rewrite
的数据库,其中包含名为rewrite_rules
的表。表为插件用于决定是否重写语句的规则提供持久存储。用户通过修改存储在此 table 中的规则集与插件进行通信。该插件通过设置 table 行的message
列与用户进行通信。query_rewrite
数据库包含一个名为flush_rewrite_rules()
的存储过程,它将表规则的内容加载到插件中。- 名为loadrewrite_rules()的 user-defined 函数由
flush_rewrite_rules()
存储过程使用。 Rewriter
插件使用了启用插件配置的系统变量,和运行时操作信息的状态变量
以下部分描述如何安装和使用Rewriter
插件,并为其关联的组件提供 reference 信息。
安装或卸载重写器查询 Rewrite 插件
注意
如果已安装,Rewriter
插件即使在禁用时也会涉及一些开销。为避免此开销,请不要安装插件,除非您打算使用它。
要安装或卸载Rewriter
query rewrite 插件,请选择位于 MySQL 安装的share
目录中的相应脚本:
-
install_rewriter.sql
:选择此脚本以安装Rewriter
插件及其相关组件。 -
uninstall_rewriter.sql
:选择此脚本以卸载Rewriter
插件及其相关组件。
运行所选脚本如下:
shell> 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 |
+------------------+-------+
有关使用说明,请参阅第 5.5.4.2 节,“使用重写器查询 Rewrite 插件”。有关参考信息,请参阅Section 5.5.4.3,“Rewriter Query Rewrite Plugin Reference”。
使用重写器查询 Rewrite 插件
要启用或禁用插件,请启用或禁用rewriter_enabled系统变量。默认情况下,安装时会启用Rewriter
插件(请参阅部分 5.5.4.1,“安装或卸载重写器查询 Rewrite 插件”)。要显式设置初始插件 state,可以在服务器启动时设置变量。例如,要在选项文件中启用插件,请使用以下 lines:
[mysqld]
rewriter_enabled=ON
也可以在运行时启用或禁用插件:
SET GLOBAL rewriter_enabled = ON;
SET GLOBAL rewriter_enabled = OFF;
重写器查询重写插件参考
略
版本令牌
MySQL包括版本令牌,该功能可用于创建服务器令牌并围绕服务器令牌进行同步,应用程序可使用该服务器令牌来防止访问不正确或过时的数据。
版本令牌接口具有以下特征:
-
版本令牌是由用作键或标识符的名称以及一个值组成的对。
-
版本令牌可以被锁定。应用程序可以使用令牌锁向其他合作的应用程序指示令牌正在使用中且不应被修改。
-
每个服务器都建立了版本令牌列表(例如,用于指定服务器分配或操作状态)。此外,与服务器通信的应用程序可以注册其自己的令牌列表,这些令牌指示需要服务器进入的状态。该应用程序发送到不在所需状态中的服务器的 SQL 语句会产生错误。这是应用程序的一个信号,表明它它应该在所需的状态中寻找不同的服务器来接收 SQL 语句。。
以下各节描述了版本令牌的组件,讨论了如何安装和使用它,并提供了其组件的参考信息。
Version 令牌组件
版本令牌基于实现以下组件的插件库:
-
名为
version_tokens
的 server-side 插件包含与服务器关联的 version 令牌列表,并为语句执行 events 订阅通知。version_tokens
插件使用 audit plugin API 来监视来自 clients 的传入 statements,并将每个 client 的 session-specific version 令牌列表与服务器 version 令牌列表进行匹配。如果有 match,则插件允许语句通过,服务器继续 process 处理它。否则,插件会向 client 返回错误,并且语句失败。 -
一组 user-defined 函数(UDF)提供 SQL-level API,用于操作和检查插件维护的服务器 version 令牌列表。需要
SUPER
特权来调用任何 Version 令牌 UDF。 -
系统变量使 clients 能够指定注册所需服务器 state 的 version 令牌列表。如果 client 发送语句时服务器具有不同的 state,则 client 会收到错误。
安装或卸载 Version 令牌
如果安装了版本令牌,则会涉及一些开销。为避免这种开销,除非计划使用它,否则不要安装它。
本节介绍如何安装或卸载版本令牌,该版本令牌是在包含插件和用户定义函数(UDF)的插件库文件中实现的。有关安装或卸载插件和UDF的一般信息,请参见第5.5.1节“安装和卸载插件”和 第5.6.1节“安装和卸载用户定义的功能”。
要使服务器可以使用,插件库文件必须位于MySQL插件目录(由plugin_dir
系统变量命名的目录)中,如有必要,通过设置plugin_dir
服务器启动时的值来配置插件目录位置 。
插件库文件的基本名称为 version_tokens
。每个平台的文件名后缀都不同(例如,.so
对于Unix和类似Unix的系统,.dll
对于Windows)。
要安装版本令牌插件和UDF,请使用 INSTALL PLUGIN
和 CREATE FUNCTION
语句(.so
根据需要调整平台的后缀)
INSTALL PLUGIN version_tokens SONAME 'version_token.so';
CREATE FUNCTION version_tokens_set RETURNS STRING
SONAME 'version_token.so';
CREATE FUNCTION version_tokens_show RETURNS STRING
SONAME 'version_token.so';
CREATE FUNCTION version_tokens_edit RETURNS STRING
SONAME 'version_token.so';
CREATE FUNCTION version_tokens_delete RETURNS STRING
SONAME 'version_token.so';
CREATE FUNCTION version_tokens_lock_shared RETURNS INT
SONAME 'version_token.so';
CREATE FUNCTION version_tokens_lock_exclusive RETURNS INT
SONAME 'version_token.so';
CREATE FUNCTION version_tokens_unlock RETURNS INT
SONAME 'version_token.so';
您必须安装UDF来管理服务器的版本令牌列表,但是还必须安装插件,因为没有它,UDF将无法正常工作。
如果插件和UDF在主复制服务器上使用,请在所有从属服务器上也安装它们,以避免复制问题。
如前所述安装后,插件和UDF将保持安装状态,直到被卸载。要删除它们,请使用 UNINSTALL PLUGIN
and DROP FUNCTION
语句:
UNINSTALL PLUGIN version_tokens;
DROP FUNCTION version_tokens_set;
DROP FUNCTION version_tokens_show;
DROP FUNCTION version_tokens_edit;
DROP FUNCTION version_tokens_delete;
DROP FUNCTION version_tokens_lock_shared;
DROP FUNCTION version_tokens_lock_exclusive;
DROP FUNCTION version_tokens_unlock;
使用版本令牌
在使用 Version Tokens 之前,请按照第 5.5.5.2 节,“安装或卸载 Version 令牌”中提供的说明进行安装。
Version 令牌可能有用的场景是访问 MySQL 服务器集合但需要通过监视它们并根据负载变化调整服务器分配来管理它们以实现负载平衡的系统。这样的系统包括以下组件:
-
要管理的MySQL服务器的集合。
-
与服务器进行通信并将其组织到高可用性组的管理或管理应用程序。组具有不同的用途,每个组中的服务器可能具有不同的分配。特定组中服务器的分配可以随时更改。
-
访问服务器以检索和更新数据的客户端应用程序,根据分配给它们的目的选择服务器。例如,客户端不应将更新发送到只读服务器。
版本令牌允许根据分配来管理服务器访问,而无需客户端重复查询服务器有关其分配的信息:
-
管理应用程序执行服务器分配,并在每个服务器上建立版本令牌以反映其分配。应用程序将缓存此信息,以为其提供中央访问点。
如果某个时候管理应用程序需要更改服务器分配(例如,将其从允许写入更改为只读),它将更改服务器的版本令牌列表并更新其缓存。
-
为了提高性能,客户端应用程序从管理应用程序获取缓存信息,从而使它们避免必须检索有关每个语句的服务器分配的信息。根据要发出的语句类型(例如,读取还是写入),客户端选择适当的服务器并连接到该服务器。
-
另外,客户端向服务器发送自己的客户端特定版本令牌,以注册其所需的服务器分配。对于客户端发送给服务器的每个语句,服务器会将自己的令牌列表与客户端令牌列表进行比较。如果服务器令牌列表包含客户端令牌列表中存在的所有令牌,并且具有相同的值,则存在匹配项,服务器将执行该语句。
另一方面,也许管理应用程序已更改服务器分配及其版本令牌列表。在这种情况下,新的服务器分配现在可能与客户端要求不兼容。服务器和客户端令牌列表之间的令牌不匹配,服务器返回错误以回复该语句。这指示客户端从管理应用程序缓存中刷新其版本令牌信息,并选择要与之通信的新服务器。
用于检测版本令牌错误和选择新服务器的客户端逻辑可以通过以下方式实现:
-
客户端可以自行处理所有版本令牌注册,不匹配检测以及连接切换。
-
这些动作的逻辑可以在管理客户端和MySQL服务器之间的连接的连接器中实现。这样的连接器可能会处理不匹配错误检测并重新发送自身的语句,也可能会将错误传递给应用程序,然后将其留给应用程序以重新发送该语句。
下面的示例以更具体的形式说明了前面的讨论。
当版本令牌在给定服务器上初始化时,该服务器的版本令牌列表为空。令牌列表维护通过调用用户定义函数(UDF)来执行。调用任何版本令牌UDF都需要该SUPER
特权,因此,令牌列表修改应由具有该特权的管理或管理应用程序完成。
假设管理应用程序与客户端查询的一组服务器进行通信,以访问员工和产品数据库(分别命名为emp
和 prod
)。允许所有服务器处理数据检索语句,但只允许其中一些进行数据库更新。为了在特定于数据库的基础上处理此问题,管理应用程序在每个服务器上建立一个版本令牌的列表。在给定服务器的令牌列表中,令牌名称表示数据库名称,令牌值为read
或write
,具体取决于数据库是否必须以 read-only 方式使用,或者是否可以进行读取和写入。
客户端应用程序通过设置系统变量来注册它们要求服务器匹配的版本令牌列表。变量设置是基于特定于客户端的,因此不同的客户端可以注册不同的需求。默认情况下,客户端令牌列表为空,与任何服务器令牌列表匹配。当客户端将其令牌列表设置为非空值时,匹配可能成功或失败,具体取决于服务器版本的令牌列表。
要定义服务器的版本令牌列表,管理应用程序将调用 version_tokens_set()
UDF。(还有用于修改和显示令牌列表的UDF,如后所述。)例如,应用程序可能会将这些语句发送到三个服务器组成的一个组:
服务器1:
mysql> SELECT version_tokens_set('emp=read;prod=read');
+------------------------------------------+
| version_tokens_set('emp=read;prod=read') |
+------------------------------------------+
| 2 version tokens set. |
+------------------------------------------+
服务器 2:
mysql> SELECT version_tokens_set('emp=write;prod=read');
+-------------------------------------------+
| version_tokens_set('emp=write;prod=read') |
+-------------------------------------------+
| 2 version tokens set. |
+-------------------------------------------+
服务器 3:
mysql> SELECT version_tokens_set('emp=read;prod=write');
+-------------------------------------------+
| version_tokens_set('emp=read;prod=write') |
+-------------------------------------------+
| 2 version tokens set. |
+-------------------------------------------+
在每种情况下,令牌列表都指定为以分号分隔的
对列表 。结果令牌列表值将导致以下服务器分配:name
=value
-
任何服务器都接受对任一数据库的读取。
-
仅服务器2接受
emp
数据库更新。 -
仅服务器3接受
prod
数据库更新 。
除了为每个服务器分配版本令牌列表以外,管理应用程序还维护反映服务器分配的缓存。
在与服务器进行通信之前,客户端应用程序联系管理应用程序并检索有关服务器分配的信息。然后,客户端根据这些分配选择服务器。假设客户端要在emp
数据库上执行读取和写入操作。根据前面的分配,只有服务器2合格。客户端连接到服务器2并通过设置其version_tokens_session
系统变量在其中注册其服务器要求 :
mysql> SET @@SESSION.version_tokens_session = 'emp=write';
对于客户端发送到服务器2的后续语句,服务器将自己的版本令牌列表与客户端列表进行比较,以检查它们是否匹配。如果是这样,则语句将正常执行:
mysql> UPDATE emp.employee SET salary = salary * 1.1 WHERE id = 4981;
Query OK, 1 row affected (0.07 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT last_name, first_name FROM emp.employee WHERE id = 4981;
+-----------+------------+
| last_name | first_name |
+-----------+------------+
| Smith | Abe |
+-----------+------------+
1 row in set (0.01 sec)
服务器和客户端版本令牌列表之间的差异可以通过两种方式发生:
-
该
version_tokens_session
值中的令牌名称 不在服务器令牌列表中。在这种情况下,将发生ER_VTOKEN_PLUGIN_TOKEN_NOT_FOUND
错误。 -
在version_tokens_session
中的令牌值与服务器令牌列表中相应令牌的 值不同。在这种情况下,将ER_VTOKEN_PLUGIN_TOKEN_MISMATCH
发生错误。
只要服务器2的分配没有更改,客户端就会继续使用它进行读取和写入。但是,假设管理应用程序想要更改服务器分配,以便必须将emp
数据库的写入发送到服务器 1 而不是服务器 2。为此,它使用version_tokens_edit()修改两个服务器上的emp
令牌值(并更新其服务器缓存):
服务器1:
mysql> SELECT version_tokens_edit('emp=write');
+----------------------------------+
| version_tokens_edit('emp=write') |
+----------------------------------+
| 1 version tokens updated. |
+----------------------------------+
服务器 2:
mysql> SELECT version_tokens_edit('emp=read');
+---------------------------------+
| version_tokens_edit('emp=read') |
+---------------------------------+
| 1 version tokens updated. |
+---------------------------------+
version_tokens_edit()修改服务器令牌列表中的命名令牌,并保持其他令牌不变。
客户端下一次向服务器2发送一条语句时,其自己的令牌列表不再与服务器令牌列表匹配,并且发生错误:
mysql> UPDATE emp.employee SET salary = salary * 1.1 WHERE id = 4982;
ERROR 3136 (42000): Version token mismatch for emp. Correct value read
在这种情况下,客户端应联系管理应用程序以获取有关服务器分配的更新信息,选择新服务器,然后将失败的语句发送到新服务器。
每个客户端必须通过仅根据其在给定服务器中注册的令牌列表发送语句来与版本令牌合作。例如,如果客户端注册的令牌列表
'emp=read'
,则版本令牌中将没有任何内容可阻止客户端发送emp
数据库更新。客户本身必须避免这样做。
对于从客户端收到的每个语句,服务器将隐式使用锁定,如下所示:
-
为客户端令牌列表(即
version_tokens_session
值)中 命名的每个令牌获取共享锁 -
执行服务器和客户端令牌列表之间的比较
-
根据比较结果执行语句或产生错误
-
释放锁
服务器使用共享锁,因此可以在不阻塞的情况下进行多个会话的比较,同时防止任何在尝试操作服务器令牌列表中相同名称的令牌之前尝试获取排他锁的会话的令牌更改。
前面的示例仅使用了版本令牌插件库中包括的一些用户定义,但还有其他一些。一组UDF允许对服务器的版本令牌列表进行操作和检查。另一组UDF允许版本令牌被锁定和解锁。
这些UDF允许创建,更改,删除和检查服务器的版本令牌列表:
-
version_tokens_set()完全替换当前列表并分配新列表。参数是
name=value
对的 semicolon-separated 列表。 -
version_tokens_edit()启用对当前列表的部分修改。它可以添加新标记或更改现有标记的值。参数是
name=value
对的 semicolon-separated 列表。 -
version_tokens_delete()从当前列表中删除令牌。参数是令牌名称的 semicolon-separated 列表。
-
version_tokens_show()显示当前令牌列表。它不需要争论。
这些函数中的每个函数(如果成功)都将返回一个二进制字符串,指示发生了什么操作。以下示例建立服务器令牌列表,通过添加新令牌对其进行修改,删除一些令牌,然后显示结果令牌列表:
mysql> SELECT version_tokens_set('tok1=a;tok2=b');
+-------------------------------------+
| version_tokens_set('tok1=a;tok2=b') |
+-------------------------------------+
| 2 version tokens set. |
+-------------------------------------+
mysql> SELECT version_tokens_edit('tok3=c');
+-------------------------------+
| version_tokens_edit('tok3=c') |
+-------------------------------+
| 1 version tokens updated. |
+-------------------------------+
mysql> SELECT version_tokens_delete('tok2;tok1');
+------------------------------------+
| version_tokens_delete('tok2;tok1') |
+------------------------------------+
| 2 version tokens deleted. |
+------------------------------------+
mysql> SELECT version_tokens_show();
+-----------------------+
| version_tokens_show() |
+-----------------------+
| tok3=c; |
+-----------------------+
如果令牌列表格式错误,则会出现警告:
mysql> SELECT version_tokens_set('tok1=a; =c');
+----------------------------------+
| version_tokens_set('tok1=a; =c') |
+----------------------------------+
| 1 version tokens set. |
+----------------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
Level: Warning
Code: 42000
Message: Invalid version token pair encountered. The list provided
is only partially updated.
1 row in set (0.00 sec)
如前所述,version 标记是使用 semicolon-separated 对的 semicolon-separated 列表定义的。考虑这个version_tokens_set()的调用:
mysql> SELECT version_tokens_set('tok1=b;;; tok2= a = b ; tok1 = 1\'2 3"4')
+---------------------------------------------------------------+
| version_tokens_set('tok1=b;;; tok2= a = b ; tok1 = 1\'2 3"4') |
+---------------------------------------------------------------+
| 3 version tokens set. |
+---------------------------------------------------------------+
Version Tokens 将参数解释如下:
-
名称和值周围的空格将被忽略。名称和值中允许使用空格。(对于
version_tokens_delete()
,它使用没有值的名称列表,名称周围的空格将被忽略。) -
没有引用机制。
-
令牌的顺序无关紧要,除非令牌列表包含给定令牌名称的多个实例,最后一个值优先于较早的值。
根据这些规则,前面的version_tokens_set()调用会产生一个带有两个标记的标记列表:tok1
具有 value 1'2 3"4
,而tok2
具有 value a = b
。要验证这一点,请调用version_tokens_show():
mysql> SELECT version_tokens_show();
+--------------------------+
| version_tokens_show() |
+--------------------------+
| tok2=a = b;tok1=1'2 3"4; |
+--------------------------+
如果令牌列表包含两个令牌,为什么 version_tokens_set()
返回该值3 version tokens set
?发生这种情况是因为原始令牌列表包含的两个定义 tok1
,而第二个定义替换了第一个定义。
Version Tokens token-manipulation UDF 将这些约束放在令牌名称和值上:
-
令牌名称不能包含
=
或;
字符,最大长度为 64 个字符。 -
令牌值不能包含
;
个字符。值的长度受max_allowed_packet系统变量的 value 约束。 -
Version Tokens 将标记名称和值视为二进制字符串,因此比较区分大小写。
版本令牌还包括一组UDF,使令牌可以被锁定和解锁:
-
version_tokens_lock_exclusive()
获取独占版本令牌锁。它包含一个或多个锁名称和超时值的列表。 -
version_tokens_lock_shared()
获取共享版本令牌锁。它包含一个或多个锁名称和超时值的列表。 -
version_tokens_unlock()
释放版本令牌锁(独占和共享)。不用争论。
每个锁定函数都返回非零值以表示成功。否则,将发生错误:
mysql> SELECT version_tokens_lock_shared('lock1', 'lock2', 0);
+-------------------------------------------------+
| version_tokens_lock_shared('lock1', 'lock2', 0) |
+-------------------------------------------------+
| 1 |
+-------------------------------------------------+
mysql> SELECT version_tokens_lock_shared(NULL, 0);
ERROR 3131 (42000): Incorrect locking service lock name '(null)'.
建议使用“版本令牌”锁定功能进行锁定。应用程序必须同意合作。
可以锁定不存在的令牌名称。这不会创建令牌。
注意
Version Tokens 锁定函数基于第 28.3.1 节,“锁定服务”中描述的锁定服务,因此对于共享锁和排它锁具有相同的语义。 (Version Tokens 使用内置于服务器中的锁定服务例程,而不是锁定服务 UDF 接口,因此不需要安装那些 UDF 来使用 Version Tokens.) Version 令牌获取的锁使用version_token_locks
的锁定服务命名空间。可以监视锁定服务锁使用 Performance Schema,因此对于 Version Tokens 锁也是 true。有关详细信息,请参阅第 28.3.1.2.3 节,“锁定服务监控”。
对于 Version Tokens 锁定函数,token name arguments 完全按照指定使用。不忽略周围的空格,允许使用=
和;
字符。这是因为 Version Tokens 只是将令牌名称传递给锁定服务。
版本令牌参考
以下讨论可作为对这些版本令牌组件的参考:
版本令牌功能
版本令牌插件库包括几个用户定义的函数。一组UDF允许对服务器的版本令牌列表进行操作和检查。另一组UDF允许版本令牌被锁定和解锁。需要SUPER
特权来调用任何 Version Tokens UDF
以下UDF允许创建,更改,删除和检查服务器的版本令牌列表。token_list
arguments(包括空格处理)的解释如第 5.5.5.3 节,“使用 Version 令牌”中所述,它提供了有关指定标记的语法的详细信息,以及其他示例。
使用name_list
参数从服务器的版本令牌列表中删除令牌,并返回指示操作结果的二进制字符串。name_list
是要删除的版本令牌名称的列表,以分号分隔。
mysql> SELECT version_tokens_delete('tok1;tok3');
+------------------------------------+
| version_tokens_delete('tok1;tok3') |
+------------------------------------+
| 2 version tokens deleted. |
+------------------------------------+
NULL
的参数被视为空字符串,它对令牌列表没有影响。
version_tokens_delete()
删除在其参数中命名的标记(如果存在)。(删除不存在的令牌不是错误。)要完全清除令牌列表而又不知道列表中有哪些令牌,请将NULL
或不包含令牌的字符串传递给 version_tokens_set()
:
mysql> SELECT version_tokens_set(NULL);
+------------------------------+
| version_tokens_set(NULL) |
+------------------------------+
| Version tokens list cleared. |
+------------------------------+
mysql> SELECT version_tokens_set('');
+------------------------------+
| version_tokens_set('') |
+------------------------------+
| Version tokens list cleared. |
+------------------------------+
使用token_list
参数修改服务器的版本令牌列表, 并返回指示操作结果的二进制字符串. token_list
是用分号分隔的name=value
对列表,用于指定要定义的每个标记的名称及其值。如果存在令牌,则将其值更新为给定值。如果令牌不存在,则使用给定值创建令牌。如果参数是NULL
或字符串不包含令牌,则令牌列表保持不变。
mysql> SELECT version_tokens_set('tok1=value1;tok2=value2');
+-----------------------------------------------+
| version_tokens_set('tok1=value1;tok2=value2') |
+-----------------------------------------------+
| 2 version tokens set. |
+-----------------------------------------------+
mysql> SELECT version_tokens_edit('tok2=new_value2;tok3=new_value3');
+--------------------------------------------------------+
| version_tokens_edit('tok2=new_value2;tok3=new_value3') |
+--------------------------------------------------------+
| 2 version tokens updated. |
+--------------------------------------------------------+
使用token_list
参数中定义的标记替换服务器的 version 标记列表,并返回指示操作结果的二进制字符串。token_list
是用分号分隔的name=value 成对列表,用于指定要定义的每个标记的名称及其值。如果参数是NULL或不包含令牌的字符串,则清除令牌列表。
mysql> SELECT version_tokens_set('tok1=value1;tok2=value2');
+-----------------------------------------------+
| version_tokens_set('tok1=value1;tok2=value2') |
+-----------------------------------------------+
| 2 version tokens set. |
+-----------------------------------------------+
以二进制字符串形式返回服务器的版本令牌列表,其中包含用分号分隔的
对列表 。name
=value
mysql> SELECT version_tokens_show();
+--------------------------+
| version_tokens_show() |
+--------------------------+
| tok2=value2;tok1=value1; |
+--------------------------+
以下 UDF 允许锁定和解锁 version 令牌:
获取一个或多个版本令牌的排他锁,这些令牌由名称指定为字符串,如果未在给定的超时值内获取锁,则会超时并显示错误。
mysql> SELECT version_tokens_lock_exclusive('lock1', 'lock2', 10);
+-----------------------------------------------------+
| version_tokens_lock_exclusive('lock1', 'lock2', 10) |
+-----------------------------------------------------+
| 1 |
+-----------------------------------------------------+
获取一个或多个版本令牌的共享锁,这些令牌由名称指定为字符串,如果未在给定的超时值内获取锁,则会超时并显示错误。
mysql> SELECT version_tokens_lock_shared('lock1', 'lock2', 10);
+--------------------------------------------------+
| version_tokens_lock_shared('lock1', 'lock2', 10) |
+--------------------------------------------------+
| 1 |
+--------------------------------------------------+
释放使用version_tokens_lock_exclusive()和version_tokens_lock_shared()在当前 session 中获取的所有锁。
mysql> SELECT version_tokens_unlock();
+-------------------------+
| version_tokens_unlock() |
+-------------------------+
| 1 |
+-------------------------+
锁定功能具有以下特征:
-
成功的返回值非零。否则,将发生错误。
-
令牌名称是字符串。
-
与操纵服务器令牌列表的UDF的参数处理相反,令牌名称参数周围的空格不会被忽略,并且允许使用
=
和;
字符。 -
可以锁定不存在的令牌名称。这不会创建令牌。
-
超时值是非负整数,表示错误超时之前等待获取锁的时间(以秒为单位)。如果超时为0,则没有等待,如果无法立即获取锁,该函数将产生错误。
-
版本令牌锁定功能基于第28.3.1节“锁定服务”中描述的锁定服务。
版本令牌系统变量
版本令牌支持以下系统变量。除非安装了版本令牌插件,否则这些变量不可用(请参见第5.5.5.2节“安装或卸载版本令牌”)。
系统变量:
属性 | 值 |
---|---|
命令行格式 | --version-tokens-session=value |
介绍 | 5.7.8 |
系统变量 | version_tokens_session |
范围 | Global, Session |
动态 | 是 |
类型 | 串 |
默认值 | NULL |
此变量的会话值指定客户端版本令牌列表,并指示客户端会话要求服务器版本令牌列表具有的令牌。
如果 version_tokens_session
变量是NULL
(默认值)或空值,则任何服务器版本令牌列表都匹配。(实际上,空值将禁用匹配要求。)
如果 version_tokens_session
变量具有非空值,则其值与服务器版本令牌列表之间的任何不匹配都会导致会话发送到服务器的任何语句均出错。在以下情况下会发生不匹配:
-
该version_tokens_session
值中的令牌名称 不在服务器令牌列表中。在这种情况下,将ER_VTOKEN_PLUGIN_TOKEN_NOT_FOUND
发生错误。 -
值中的令牌值
version_tokens_session
与服务器令牌列表中相应令牌的 值不同。在这种情况下,将ER_VTOKEN_PLUGIN_TOKEN_MISMATCH
发生错误。
服务器版本令牌列表包括未在version_tokens_session
值中命名的令牌,这并非不匹配 。
假设管理应用程序已按如下所示设置服务器令牌列表:
mysql> SELECT version_tokens_set('tok1=a;tok2=b;tok3=c');
+--------------------------------------------+
| version_tokens_set('tok1=a;tok2=b;tok3=c') |
+--------------------------------------------+
| 3 version tokens set. |
+--------------------------------------------+
客户端通过设置其version_tokens_session
值来注册要求服务器匹配的令牌 。然后,对于客户端发送的每个后续语句,服务器将根据客户端version_tokens_session
值检查其令牌列表, 如果不匹配,则会产生错误:
mysql> SET @@SESSION.version_tokens_session = 'tok1=a;tok2=b';
mysql> SELECT 1;
+---+
| 1 |
+---+
| 1 |
+---+
mysql> SET @@SESSION.version_tokens_session = 'tok1=b';
mysql> SELECT 1;
ERROR 3136 (42000): Version token mismatch for tok1. Correct value a
第一个SELECT
成功,因为客户端令牌tok1
和 tok2
存在于服务器令牌列表中,并且每个令牌在服务器列表中具有相同的值。第二个SELECT
失败,因为尽管tok1
它存在于服务器令牌列表中,但其值与客户端指定的值不同。
除非服务器令牌列表发生更改,使其再次匹配,否则客户端发送的任何语句都会失败。假设管理应用程序更改服务器令牌列表,如下所示:
mysql> SELECT version_tokens_edit('tok1=b');
+-------------------------------+
| version_tokens_edit('tok1=b') |
+-------------------------------+
| 1 version tokens updated. |
+-------------------------------+
mysql> SELECT version_tokens_show();
+-----------------------+
| version_tokens_show() |
+-----------------------+
| tok3=c;tok1=b;tok2=b; |
+-----------------------+
现在,客户端 version_tokens_session
值与服务器令牌列表匹配,并且客户端可以再次成功执行语句:
mysql> SELECT 1;
+---+
| 1 |
+---+
| 1 |
+---+
属性 | 值 |
---|---|
命令行格式 | --version-tokens-session-number=# |
介绍Introduced | 5.7.8 |
系统变量 | version_tokens_session_number |
范围 | Global, Session |
动态 | No |
类型 | Integer |
默认值 | 0 |
此变量仅供内部使用。