第29章扩展MySQL

目录

29.1 MySQL内部
29.1.1 MySQL线程
29.1.2 MySQL测试套件
29.2 MySQL插件API
29.2.1插件类型
29.2.2插件API特性
29.2.3插件API组件
29.2.4编写插件
29.3用于插件的MySQL服务
29.3.1锁定服务
29.3.2密钥环服务
29.4向MySQL添加新功能
29.4.1用户自定义功能接口的功能
29.4.2添加新的用户定义函数
29.4.3添加新的本机功能
29.5调试和移植MySQL
29.5.1调试MySQL服务器
29.5.2调试MySQL客户端
29.5.3 LOCK_ORDER工具
29.5.4 DBUG包

29.1 MySQL内部

本章介绍了处理MySQL代码时需要了解的许多内容。 要跟踪或促成MySQL开发,请按照 第2.9.3节“使用开发源树安装MySQL”中的说明进行操作 如果您对MySQL内部感兴趣,您还应该订阅我们的 internals 邮件列表。 此列表的流量相对较低。 有关如何订阅的详细信息,请参见 第1.6.2节“MySQL邮件列表” Oracle公司的许多MySQL开发人员都在 internals 列表,我们帮助其他正在研究MySQL代码的人。 您可以随意使用此列表来询问有关代码的问题,并发送您想要为MySQL项目贡献的补丁!

注意

MySQL源代码包含使用Doxygen编写的内部文档。 本文档对于从开发人员的角度理解MySQL的工作原理非常有用。 生成的Doxygen内容可从 https://dev.mysql.com/doc/index-other.html获得 也可以使用 第2.9.7节“生成MySQL Doxygen文档内容”中 的说明从MySQL源代码分发本地生成此内容

29.1.1 MySQL线程

MySQL服务器创建以下线程:

  • 连接管理器线程处理服务器侦听的网络接口上的客户端连接请求。 在所有平台上,一个管理器线程处理TCP / IP连接请求。 在Unix上,此管理器线程还处理Unix套接字文件连接请求。 在Windows上,管理器线程处理共享内存连接请求,另一个处理命名管道连接请求。 服务器不会创建线程来处理它不听的接口。 例如,不支持启用命名管道连接的Windows服务器不会创建处理它们的线程。

  • 连接管理器线程将每个客户端连接与专用于它的线程相关联,该线程处理该连接的身份验证和请求处理。 管理器线程在必要时创建一个新线程,但首先通过查询线程缓存来查看它是否包含可用于连接的线程,从而避免这样做。 当连接结束时,如果缓存未满,则将其线程返回到线程缓存。

    有关调整控制线程资源的参数的信息,请参见 第8.12.4.1节“MySQL如何处理客户端连接”

  • 在主复制服务器上,来自从属服务器的连接像客户端连接一样处理:每个连接的从属服务器都有一个线程。

  • 在从属复制服务器上,启动I / O线程以连接到主服务器并从中读取更新。 启动SQL线程以应用从主服务器读取的更新。 这两个线程独立运行,可以独立启动和停止。

  • 信号线程处理所有信号。 此线程通常还处理警报和调用 process_alarm() 以强制在空闲时间过长的连接上超时。

  • 如果 InnoDB 使用,默认情况下会有其他读写线程。 这些数量由 innodb_read_io_threads innodb_write_io_threads 参数 控制 请参见 第15.13节“InnoDB启动选项和系统变量”

  • 如果使用该 选项 启动服务器 ,则会创建专用线程以每秒刷新所有表 --flush_time=val val

  • 如果事件调度程序处于活动状态,则调度程序有一个线程,当前正在运行的每个事件都有一个线程。 请参见 第24.4.1节“事件调度程序概述”

mysqladmin processlist 仅显示连接,复制和事件线程。

29.1.2 MySQL测试套件

Unix源代码和二进制分发版中包含的测试系统使用户和开发人员可以对MySQL代码执行回归测试。 这些测试可以在Unix上运行。

您也可以编写自己的测试用例。 有关信息(包括系统要求),请参阅MySQL服务器Doxygen文档中的MySQL测试框架,可从 https://dev.mysql.com/doc/index-other.html获取

当前的一组测试用例并未测试MySQL中的所有内容,但它应该捕获SQL处理代码,操作系统或库问题中最明显的错误,并且在测试复制方面非常彻底。 我们的目标是让测试覆盖100%的代码。 我们欢迎对我们的测试套件的贡献。 您可能特别希望提供检查系统关键功能的测试,因为这可确保所有未来的MySQL版本都能很好地与您的应用程序配合使用。

测试系统包括一个测试语言解释器( mysqltest ),一个运行所有测试的Perl脚本( mysql-test-run.pl ),用特殊测试语言编写的实际测试用例,以及它们的预期结果。 要在构建后在系统上运行测试套件,请 从源根目录 键入 make test ,或将位置更改为 mysql-test 目录并键入 ./mysql-test-run.pl 如果已安装二进制分发版,请将位置更改 mysql-test 为安装根目录下的目录(例如, /usr/local/mysql/mysql-test ),然后运行 ./mysql-test-run.pl 所有测试都应该成功。 如果没有,请随时尝试找出原因并报告问题,如果它表明MySQL存在错误。 请参见 第1.7节“如何报告错误或问题”

如果一个测试失败,您应该运行 mysql-test-run.pl --force 选择检查是否有其他测试失败。

如果你 在要运行测试套件的机器上运行 mysqld 的副本, 则只要它不使用端口 9306 ,就不必停止它 9307 如果采用这些端口中的任何一个,则应将 MTR_BUILD_THREAD 环境变量设置为适当的值,并且测试套件将为master,slave和NDB使用不同的端口集。 例如:

shell> export MTR_BUILD_THREAD = 31
shell> ./mysql-test-run.pl [ options] [ test_name]

mysql-test 目录中,您可以使用 ./mysql-test-run.pl test_name 运行单个测试用例

如果您对测试套件有疑问,或者要提供测试用例,请向MySQL internals 邮件列表 发送电子邮件 请参见 第1.6.2节“MySQL邮件列表”

29.2 MySQL插件API

MySQL支持一个插件API,可以创建服务器组件。 插件可以在服务器启动时加载,也可以在运行时加载和卸载,而无需重新启动服务器。 API是通用的,不指定插件可以执行的操作。 此接口支持的组件包括但不限于存储引擎,全文解析器插件和服务器扩展。

例如,全文解析器插件可用于替换或扩充内置的全文解析器。 插件可以使用与内置解析器使用的规则不同的规则将文本解析为单词。 如果您需要解析具有与内置解析器所期望的特征不同的特性的文本,这将非常有用。

插件接口比旧的用户​​定义函数(UDF)接口更通用。

插件接口使用 数据库中 plugin mysql 来记录有关使用该 INSTALL PLUGIN 语句 永久安装的插件的信息 此表是作为MySQL安装过程的一部分创建的。 也可以使用该 --plugin-load 选项 为单个服务器调用安装插件 以这种方式安装的插件不会记录在 plugin 表中。 请参见 第5.6.1节“安装和卸载插件”

除了服务器插件之外,MySQL还支持客户端插件的API。 例如,这由身份验证插件使用,其中服务器端插件和客户端插件协作以使客户端能够通过各种身份验证方法连接到服务器。

注意

MySQL源代码包含使用Doxygen编写的内部文档。 本文档对于从开发人员的角度理解MySQL的工作原理非常有用。 生成的Doxygen内容可从 https://dev.mysql.com/doc/index-other.html获得 也可以使用 第2.9.7节“生成MySQL Doxygen文档内容”中 的说明从MySQL源代码分发本地生成此内容

其他资源

Sergei Golubchik和Andrew Hutchings 撰写的 MySQL 5.1插件开发 提供了有关插件API的大量详细信息。 尽管本书的标题涉及MySQL Server 5.1,但其中的大部分信息也适用于更高版本。

29.2.1插件类型

插件API支持创建实现多种功能的插件:

以下部分概述了这些插件类型。

存储引擎插件

MySQL服务器使用的可插拔存储引擎架构使存储引擎可以作为插件编写,并从正在运行的服务器加载和卸载。 有关此体系结构的说明,请参见 第16.11节“MySQL存储引擎体系结构概述”

有关如何使用插件API编写存储引擎的信息,请参阅 MySQL内部:编写自定义存储引擎

全文解析器插件

MySQL有一个内置的解析器,默认情况下它用于全文操作(解析要编入索引的文本,或解析查询字符串以确定用于搜索的术语)。 内置的全文解析器由 InnoDB MyISAM 支持

MySQL还有一个基于字符的ngram全文解析器,支持中文,日文和韩文(CJK),以及一个支持日语的基于单词的MeCab解析器插件,用于 InnoDB MyISAM 表。

对于全文处理, 解析 意味着 从文本或查询字符串中 提取单词(或 令牌 ,在基于n-gram字符的解析器的情况下),基于定义哪些字符序列构成单词的规则和字边界所在。

解析索引时,解析器会将每个单词传递给服务器,然后将其添加到全文索引中。 解析查询字符串时,解析器会将每个单词传递给服务器,服务器会累积用于搜索的单词。

第12.9节“全文搜索功能” 中介绍了内置全文本解析器的解析属性 这些属性包括用于确定如何从文本中提取单词的规则。 解析器受某些系统变量的影响,这些系统变量导致短的或更长的单词被排除,并且通过用于标识要忽略的常用单词的禁用词列表。 有关更多信息,请参见 第12.9.4节“全文停用词” 第12.9.6节“微调MySQL全文搜索”

插件API使您可以使用除默认内置全文解析器之外的全文解析器。 例如,如果您使用日语,则可以选择使用MeCab全文解析器。 插件API还允许您提供自己的全文解析器,以便您可以控制解析器的基本职责。 解析器插件可以以两种角色运行:

  • 该插件可以取代内置的解析器。 在此角色中,插件读取要解析的输入,将其拆分为单词,并将单词传递给服务器(用于索引或用于标记累积)。 ngram和MeCab解析器用作内置全文解析器的替代品。

    如果您需要使用与内置解析器不同的规则来确定如何将输入拆分为单词,则可以选择提供自己的全文解析器。 例如,内置解析器认为文本 区分大小写 由两个单词 case sensitive ”组成, 而应用程序可能需要将文本视为单个单词。

  • 该插件可以作为前端解析器与内置解析器一起使用。 在此角色中,插件从输入中提取文本并将文本传递给解析器,解析器使用其正常的解析规则将文本拆分为单词。 此解析受 系统变量和禁用词列表的影响。 innodb_ft_xxx ft_xxx

    以这种方式使用解析器的一个原因是您需要索引PDF文档,XML文档或 .doc 文件等内容。 内置解析器不适用于那些类型的输入,但插件可以从这些输入源中提取文本并将其传递给内置解析器。

解析器插件也可以在两个角色中运行。 也就是说,它可以从noncleartext输入(前端角色)中提取文本,并将文本解析为单词(从而替换内置解析器)。

全文插件与每个索引的全文索引相关联。 也就是说,当您最初安装解析器插件时,这不会导致它用于任何全文操作。 它变得可用。 例如, WITH PARSER 在创建单个 FULLTEXT 索引 ,可以在 子句中 命名全文解析器插件 要在创建表时创建此类索引,请执行以下操作:

创建表t
  doc CHAR(255),
  带有PARSER parser_name的FULLTEXT INDEX(doc)
)ENGINE = InnoDB;

或者您可以在创建表后添加索引:

ALTER TABLE t使用PARSER parser_name添加FULLTEXT INDEX(doc);

将解析器与索引相关联的唯一SQL更改是 WITH PARSER 子句。 搜索与以前一样指定,查询不需要进行任何更改。

将解析器插件与 FULLTEXT 索引 关联时 ,插件是使用索引所必需的。 如果删除了解析器插件,则与其关联的任何索引都将变得不可用。 任何尝试使用插件不可用的表都会导致错误,尽管 DROP TABLE 仍然可能。

有关全文插件的更多信息,请参见 第29.2.4.4节“编写全文分析器插件” MySQL的8.0支持全文插件与 MyISAM InnoDB

守护进程插件

守护程序插件是一种简单类型的插件,用于应由服务器运行但不与之通信的代码。 MySQL发行版包含一个示例守护程序插件,可将周期性心跳消息写入文件。

有关守护程序插件的更多信息,请参见 第29.2.4.5节“编写守护程序插件”

INFORMATION_SCHEMA插件

INFORMATION_SCHEMA plugins支持创建包含通过 INFORMATION_SCHEMA 数据库 向用户公开的服务器元数据的表 例如, InnoDB 使用 INFORMATION_SCHEMA 插件提供包含有关当前事务和锁的信息的表。

有关 INFORMATION_SCHEMA 插件的 更多信息 ,请参见 第29.2.4.6节“编写INFORMATION_SCHEMA插件”

半同步复制插件

默认情况下,MySQL复制是异步的。 使用半同步复制,在返回执行事务的会话之前,在主端块上执行提交,直到至少一个从端确认已接收并记录事务的事件。 半同步复制是通过互补的主插件和客户端插件实现的。 请参见 第17.3.11节“半同步复制”

有关半同步复制插件的更多信息,请参见 第29.2.4.7节“编写 半同步复制插件

审计插件

MySQL服务器提供可插拔的审计接口,可以将有关服务器操作的信息报告给感兴趣的各方。 这些操作会发生审核通知(尽管接口是通用的,服务器可以修改为报告其他操作):

  • 将消息写入常规查询日志(如果启用了日志)

  • 将消息写入错误日志

  • 将查询结果发送给客户端

审计插件可以向审计接口注册,以接收有关服务器操作的通知。 当服务器内发生可审计事件时,服务器确定是否需要通知。 对于每个已注册的审计插件,服务器会根据插件感兴趣的事件类检查事件,如果匹配则将事件传递给插件。

此接口使审计插件仅接收有关其认为重要的事件类中的操作的通知,并忽略其他操作。 该接口提供将操作分类为事件类,并进一步划分为每个类中的事件子类。

当审计插件被通知可审计事件时,它会收到指向当前THD结构的指针和指向包含有关事件信息的结构的指针。 插件可以检查事件并执行适当的审计操作。 例如,插件可以查看生成结果集或记录的语句,结果中的行数,当前用户的操作对象,或失败操作的错误代码。

有关审计插件的更多信息,请参见 第29.2.4.8节“编写审计插件”

身份验证插件

MySQL支持可插拔身份验证。 服务器端和客户端都存在身份验证插件。 服务器端的插件实现了身份验证方法,供客户端连接到服务器时使用。 客户端上的插件与服务器端插件通信,以提供它所需的身份验证信息。 客户端插件可以与用户交互,执行诸如请求密码或其他认证凭证的任务以发送到服务器。 请参见 第6.2.17节“可插入验证”

可插入身份验证还启用代理用户功能,其中一个用户获取另一个用户的身份。 服务器端身份验证插件可以向连接用户应具有其身份的用户的名称返回服务器。 请参见 第6.2.18节“代理用户”

有关身份验证插件的更多信息,请参见 第29.2.4.9节“编写身份验证插件”

密码验证插件

MySQL服务器提供了一个用于编写测试密码的插件的接口。 这样的插件实现了两个功能:

有关编写此类插件的信息,请参见 第29.2.4.10节“编写密码验证插件”

协议跟踪插件

MySQL支持使用协议跟踪插件:客户端插件,用于实现客户端与使用客户端/服务器协议发生的服务器之间的通信跟踪。

有关协议跟踪插件的更多信息,请参见 第29.2.4.11节“编写协议跟踪插件”

查询重写插件

MySQL Server支持查询重写插件,可以在服务器执行之前检查并可能修改服务器接收的语句。 查询重写插件在服务器解析之前或之后获取语句。

预分析查询重写插件具有以下特征:

  • 该插件允许在服务器处理之前重写到达服务器的SQL语句。

  • 该插件接收一个语句字符串,并可能返回一个不同的字符串。

postparse查询重写插件具有以下特征:

  • 该插件支持基于解析树的语句重写。

  • 服务器解析每个语句并将其解析树传递给插件,插件可以遍历树。 插件可以将原始树返回到服务器以进行进一步处理,或者构造不同的树并返回该树。

  • 该插件可以将 mysql_parser 插件服务用于以下目的:

    • 激活语句摘要计算并获取语句的规范化版本,与性能模式是否生成摘要无关。

    • 遍历解析树。

    • 解析语句。 如果插件从解析树构造新的语句字符串,这将非常有用。 插件可以让服务器解析字符串以生成新树,然后将该树作为重写语句的表示返回。

有关插件服务的更多信息,请参见 第29.3节“插件的MySQL服务”

预填充和postparse查询重写插件共享这些特征:

  • 如果安装了查询重写插件,该 --log-raw 选项会影响语句日志记录,如下所示:

    • 如果没有 --log-raw ,服务器会记录查询重写插件返回的语句。 这可能与收到的声明不同。

    • 使用时 --log-raw ,服务器将原始语句记录为已接收。

  • 如果插件重写语句,服务器将根据重写的语句而不是原始语句决定是将其写入二进制日志(以及因此写入任何复制从属)。 如果插件仅 SELECT SELECT 语句 重写 语句,则对二进制日志记录没有影响,因为服务器不会将 SELECT 语句 写入 二进制日志。

  • 如果插件重写语句,则服务器会生成 Note 客户端可以使用 消息 SHOW WARNINGS 消息具有以下格式,其中 stmt_in 是原始语句,并且 stmt_out 是重写的语句:

    通过查询重写插件查询' stmt_in'重写为' stmt_out'
    

MySQL发行版包括一个名为的postparse查询重写插件 Rewriter 这个插件是基于规则的。 您可以向其规则表中添加行以导致 SELECT 语句重写。 有关更多信息,请参见 第5.6.4节“重写器查询重写插件”

查询重写插件使用与审计插件相同的API。 有关审计插件的更多信息,请参见 第29.2.4.8节“编写审计插件”

密钥环插件

MySQL服务器支持密钥环插件,使内部服务器组件和插件能够安全地存储敏感信息,以便以后检索。

所有MySQL发行版都包含一个名为的密钥环插件 keyring_file MySQL企业版发行版包括其他密钥环插件。 请参见 第6.4.4节“MySQL密钥环”

有关密钥环插件的更多信息,请参见 第29.2.4.12节“编写密钥环插件”

29.2.2插件API特性

服务器插件API具有以下特征:

  • 所有插件都有几个共同点。

    每个插件都有一个可以在SQL语句中引用的名称,以及其他元数据,如作者和提供其他信息的描述。 可以在 INFORMATION_SCHEMA.PLUGINS 表格中或使用 SHOW PLUGINS 语句 检查此信息

  • 插件框架可扩展以适应不同类型的插件。

    虽然插件API的某些方面对于所有类型的插件都是通用的,但API还允许特定于类型的界面元素,以便可以创建不同类型的插件。 具有一个目的的插件可以具有最适合其自身要求的接口,而不是某些其他插件类型的要求。

    存在多种类型插件的接口,例如存储引擎,全文解析器和 INFORMATION_SCHEMA 表。 其他人可以添加。

  • 插件可以向用户公开信息。

    插件可以实现通过 SHOW VARIABLES SHOW STATUS 语句 可用的系统和状态变量

  • 插件API包括版本控制信息。

    插件API中包含的版本信息使插件库及其包含的每个插件能够相对于用于构建库的API版本进行自我识别。 如果API随时间变化,版本号将发生变化,但服务器可以检查给定插件库的版本信息,以确定它是否支持库中的插件。

    版本号有两种类型。 第一个是通用插件框架本身的版本。 每个插件库都包含这种版本号。 第二种类型的版本适用于各个插件。 每种特定类型的插件都有一个版本用于其界面,因此库中的每个插件都有一个特定于类型的版本号。 例如,包含全文解析器插件的库具有通用插件API版本号,插件具有特定于全文插件接口的版本号。

  • 插件API实现了安全限制。

    插件库必须安装在特定的专用目录中,该目录的位置由服务器控制,并且不能在运行时更改。 此外,库必须包含将其标识为插件库的特定符号。 如果没有将插件构建为插件,服务器将不会将其作为插件加载。

  • 插件可以访问服务器服务。

    服务接口公开了插件可以使用普通函数调用访问的服务器功能。 有关详细信息,请参见 第29.3节“用于插件的MySQL服务”

在某些方面,服务器插件API类似于它取代的旧用户定义函数(UDF)API,但插件API比旧接口具有几个优点。 例如,UDF没有版本控制信息。 此外,较新的插件接口消除了旧UDF接口的安全问题。 用于编写非插件UDF的旧接口允许从系统的动态链接器搜索的任何目录加载库,并且标识UDF库的符号是相对非特定的。

客户端插件API具有类似的体系结构特征,但客户端插件无法像服务器插件那样直接访问服务器。

29.2.3插件API组件

服务器插件实现包括几个组件。

SQL语句:

  • INSTALL PLUGIN mysql.plugin 表中 注册一个插件 并加载插件代码。

  • UNINSTALL PLUGIN mysql.plugin 表中 取消注册插件 并卸载插件代码。

  • WITH PARSER 全文索引创建 子句将全文解析器插件与给定 FULLTEXT 索引 相关联

  • SHOW PLUGINS 显示有关服务器插件的信息。

命令行选项和系统变量:

有关插件加载的其他信息,请参见 第5.6.1节“安装和卸载插件”

与插件相关的表:

  • INFORMATION_SCHEMA.PLUGINS 表包含插件信息。

  • mysql.plugin 表列出了安装时使用 INSTALL PLUGIN 的插件以及插件使用所需的插件。 对于新的MySQL安装,此表是在安装过程中创建的。

客户端插件实现更简单:

  • 对于 mysql_options() C API函数, MYSQL_DEFAULT_AUTH MYSQL_PLUGIN_DIR 选项使客户端程序能够加载身份验证插件。

  • 有一些C API函数可以管理客户端插件。

要检查MySQL如何实现插件,请参考MySQL源代码分发中的以下源文件:

  • include/mysql 目录中, plugin.h 公开公共插件API。 任何想要编写插件库的人都应该检查此文件。 文件提供了与特定类型的插件有关的其他信息。 包含特定于客户端插件的信息。 plugin_xxx.h client_plugin.h

  • sql 目录中, sql_plugin.h sql_plugin.cc 包含内部插件实现。 sql_acl.cc 是服务器使用身份验证插件的地方。 插件开发人员无需咨询这些文件。 对于那些想要了解服务器如何处理插件的人来说,它们可能是有趣的。

  • 在该 sql-common 目录中, client_plugin.h 实现C API客户端插件函数,并 client.c 实现客户端身份验证支持。 插件开发人员无需咨询这些文件。 对于那些想要了解服务器如何处理插件的人来说,它们可能是有趣的。

29.2.4编写插件

要创建插件库,您必须提供指示库文件包含的插件所需的描述符信息,并为每个插件编写接口函数。

每个服务器插件必须具有为插件API提供信息的通用描述符,以及特定于类型的描述符,该描述符提供有关给定类型插件的插件接口的信息。 一般描述符的结构对于所有插件类型都是相同的。 特定于类型的描述符的结构因插件类型而异,并且由插件需要执行的操作的要求决定。 服务器插件接口还使插件能够公开状态和系统变量。 这些变量通过 SHOW STATUS SHOW VARIABLES 语句以及相应的 INFORMATION_SCHEMA 变得可见

对于客户端插件,架构有点不同。 每个插件必须有一个描述符,但没有划分为单独的通用描述符和特定于类型的描述符。 相反,描述符以所有客户端插件类型共有的固定成员集开头,并且公共成员后面是实现特定插件类型所需的任何其他成员。

服务器插件包含成为正在运行的服务器的一部分的代码,因此当您编写插件时,您将受到编写服务器代码的任何和所有约束的约束。 例如,如果尝试使用 libstdc++ 库中的 函数,则可能会出现问题 这些约束可能会在服务器的未来版本中发生变化,因此服务器升级可能需要修改最初为旧服务器编写的插件。 有关这些约束的信息,请参见 第2.9.4节“MySQL源配置选项” 第2.9.5节“处理编译MySQL的问题”

客户端插件编写者应该避免依赖于调用应用程序具有哪些符号,因为您无法确定哪些应用程序将使用该插件。

29.2.4.1插件编写概述

这些条件适用于插件编写:

  • 插件使用的MySQL头文件包含C ++代码,因此插件必须编译为C ++代码。

  • 您必须编译包含整个服务器源代码的插件,而不仅仅是库和头文件。

  • 编译的插件在服务器版本之间不兼容。 对于针对MySQL 8.0编译的插件。 X ,不能保证它可以与MySQL 8.0一起使用。 Y 没有重新编译MySQL 8.0的服务器。 Y

  • 插件是动态加载和卸载的,因此您的操作系统必须支持动态加载,并且您必须动态编译调用应用程序(而不是静态编译)。 对于服务器插件,这意味着 必须动态链接 mysqld

以下过程概述了创建插件库所需的步骤。 接下来的部分提供了有关设置插件数据结构和编写特定类型插件的其他详细信息。

  1. 在插件源文件中,包含插件库所需的头文件。 plugin.h 文件是必需的,库也可能需要其他文件。 例如:

    #include <stdlib.h>
    #include <ctype.h>
    #include <mysql / plugin.h>
    
  2. 设置插件库文件的描述符信息。 对于服务器插件,请编写库描述符,该描述符必须包含文件中每个服务器插件的常规插件描述符。 有关更多信息,请参见 第29.2.4.2.1节“服务器插件库和插件描述符” 此外,为库中的每个服务器插件设置特定于类型的描述符。 每个插件的通用描述符指向其特定于类型的描述符。

    对于客户端插件,请编写客户端描述符。 有关更多信息,请参见 第29.2.4.2.3节“客户端插件描述符”

  3. 为每个插件编写插件接口函数。 例如,每个插件的常规插件描述符指向服务器在加载和卸载插件时应调用的初始化和取消初始化函数。 插件的类型特定描述也可以指向接口功能。

  4. 对于服务器插件,请设置状态和系统变量(如果有)。

  5. 将插件库编译为共享库并将其安装在插件目录中。 有关更多信息,请参见 第29.2.4.3节“编译和安装插件库”

  6. 对于服务器插件,请将插件注册到服务器。 有关更多信息,请参见 第5.6.1节“安装和卸载插件”

  7. 测试插件以验证它是否正常工作。

29.2.4.2插件数据结构

插件库文件包括描述符信息以指示它包含哪些插件。

如果插件库包含任何服务器插件,则它必须包含以下描述符信息:

  • 库描述符指示库使用的通用服务器插件API版本号,并包含库中每个服务器插件的通用插件描述符。 要为此描述符提供框架,请从头 plugin.h 文件中 调用两个宏

    mysql_declare_plugin(name... one or more server plugin descriptors here ...
    mysql_declare_plugin_end;
    

    宏扩展为自动提供API版本的声明。 您必须提供插件描述符。

  • 在库描述符中,每个通用服务器插件由 st_mysql_plugin 结构 描述 此插件描述符结构包含每种类型的服务器插件通用的信息:表示插件类型的值; 插件名称,作者,描述和许可证类型; 指向服务器在加载和卸载插件时调用的初始化和取消初始化函数的指针,以及指向插件实现的任何状态或系统变量的指针。

  • 库描述符中的每个通用服务器插件描述符还包含指向特定于类型的插件描述符的指针。 类型特定描述符的结构因插件类型而异,因为每种类型的插件都可以拥有自己的API。 特定于类型的插件描述符包含特定于类型的API版本号以及指向实现该插件类型所需的函数的指针。 例如,全文解析器插件具有初始化和取消初始化函数以及主解析函数。 服务器在使用插件解析文本时调用这些函数。

插件库还包含接口函数,这些函数由库中每个插件的常规描述符和特定于类型的描述符引用。

如果插件库包含客户端插件,则必须包含插件的描述符。 描述符以所有客户端插件通用的固定成员集开头,后跟任何特定于插件类型的成员。 要提供描述符框架,请从头 client_plugin.h 文件中 调用两个宏

mysql_declare_client_plugin(plugin_type
   ... members common to all client plugins...
   ... type-specific extra members...
mysql_end_client_plugin;

插件库还包含客户端描述符引用的任何接口函数。

mysql_declare_plugin() mysql_declare_client_plugin() 宏在它们如何被调用,这对插件库的内容含义有所不同。 以下指南总结了规则:

  • mysql_declare_plugin() 并且 mysql_declare_client_plugin() 可以在同一个源文件中使用,这意味着插件库可以包含服务器和客户端插件。 然而,每一个 mysql_declare_plugin() mysql_declare_client_plugin() 最多可使用一次。

  • mysql_declare_plugin() 允许多个服务器插件声明,因此插件库可以包含多个服务器插件。

  • mysql_declare_client_plugin() 仅允许单个客户端插件声明。 要创建多个客户端插件,必须使用单独的插件库。

当客户端程序查找插件库中没有内置的客户端插件时 libmysqlclient ,它会查找一个基本名称与插件名称相同的文件。 例如,如果程序需要使用 auth_xxx .so 用作库后缀 的系统上 命名的客户端身份验证插件 ,则它会查找名为的文件 auth_xxx.so (在OS X上,程序首先查找 auth_xxx.dylib ,然后查找 auth_xxx.so 。)因此,如果插件库包含客户端插件,则库必须具有与该插件相同的基本名称。

对于包含服务器插件的库,情况也是如此。 --plugin-load 选项和 INSTALL PLUGIN 声明明确提供的库文件名,所以有需要的库名,它包含的任何服务器插件的名称之间没有明确的关系。

29.2.4.2.1服务器插件库和插件描述符

每个包含服务器插件的插件库都必须包含一个库描述符,其中包含文件中每个服务器插件的常规插件描述符。 本节讨论如何编写服务器插件的库和一般描述符。

库描述符必须定义两个符号:

  • _mysql_plugin_interface_version_ 指定通用插件框架的版本号。 这由 MYSQL_PLUGIN_INTERFACE_VERSION 符号 给出,该 符号在 plugin.h 文件中 定义

  • _mysql_plugin_declarations_ 定义一个插件声明数组,由一个声明终止,所有成员都设置为0.每个声明都是 st_mysql_plugin 结构的 一个实例 (也在其中定义 plugin.h )。 对于库中的每个服务器插件,必须有其中一个。

如果服务器在库中找不到这两个符号,则它不会将其作为合法插件库接受并拒绝它并显示错误。 这可以防止将库用于插件目的,除非它是专门构建为插件库的。

定义两个必需符号的传统方法是使用 文件中 mysql_declare_plugin() mysql_declare_plugin_end plugin.h

mysql_declare_plugin(name... one or more server plugin descriptors here ...
mysql_declare_plugin_end;

每个服务器插件必须具有一个通用描述符,用于向服务器插件API提供信息。 通用描述符对于所有插件类型具有相同的结构。 文件中 st_mysql_plugin 结构 plugin.h 定义了这个描述符:

struct st_mysql_plugin
{
  int类型; / *插件类型(MYSQL_XXX_PLUGIN值)* /
  void * info; / *指向特定于类型的插件描述符的指针* /
  const char * name; / *插件名称* /
  const char * author; / *插件作者(适用于I_S.PLUGINS)* /
  const char * descr; / *一般描述性文字(适用于I_S.PLUGINS)* /
  int license; / *插件许可证(PLUGIN_LICENSE_XXX)* /
  int(* init)(void *); / *加载插件时调用的函数* /
  int(* deinit)(void *); / *卸载插件时调用的函数* /
  unsigned int版本; / *插件版本(适用于I_S.PLUGINS)* /
  struct st_mysql_show_var * status_vars;
  struct st_mysql_sys_var ** system_vars;
  void * __reserved1; / *保留用于依赖性检查* /
  无符号长旗; 插件的/ *标志* /
};

st_mysql_plugin 描述符结构构件被使用如下。 char * 成员应指定为以null结尾的字符串。

  • type :插件类型。 这必须是以下插件类型值之一 plugin.h

    / *
      允许的插件类型
    * /
    #define MYSQL_UDF_PLUGIN 0 / *用户自定义函数* /
    #define MYSQL_STORAGE_ENGINE_PLUGIN 1 / *存储引擎* /
    #define MYSQL_FTPARSER_PLUGIN 2 / *全文解析器插件* /
    #define MYSQL_DAEMON_PLUGIN 3 / *守护进程/原始插件类型* /
    #define MYSQL_INFORMATION_SCHEMA_PLUGIN 4 / * I_S插件类型* /
    #define MYSQL_AUDIT_PLUGIN 5 / *审核插件类型* /
    #define MYSQL_REPLICATION_PLUGIN 6 / *复制插件类型* /
    #define MYSQL_AUTHENTICATION_PLUGIN 7 / *身份验证插件类型* /
    ...
    

    例如,对于全文解析器插件, type 值为 MYSQL_FTPARSER_PLUGIN

  • info :指向插件的特定于类型的描述符的指针。 与一般插件描述符结构不同,此描述符的结构取决于特定类型的插件。 出于版本控制的目的,每个插件类型的特定于类型的描述符的第一个成员应该是该类型的接口版本。 这使服务器可以检查每个插件的类型特定版本,无论其类型如何。 在版本号之后,描述符包括所需的任何其他成员,例如回调函数和服务器正确调用插件所需的其他信息。

  • name :一个提供插件名称的字符串。 这是将在 mysql.plugin 表中 列出的名称,您可以通过该名称 引用SQL语句中的插件,例如 INSTALL PLUGIN UNINSTALL PLUGIN ,或使用该 --plugin-load 选项。 该名称在 INFORMATION_SCHEMA.PLUGINS 表格或输出中 也可见 SHOW PLUGINS

    插件名称不应以任何服务器选项的名称开头。 如果是,则服务器将无法初始化它。 例如,服务器有一个 --socket 选项,因此您不应使用插件名称,例如 socket socket_plugin 等等。

  • author :一个命名插件作者的字符串。 这可以是你喜欢的任何东西。

  • desc :一个字符串,提供插件的一般描述。 这可以是你喜欢的任何东西。

  • license :插件许可证类型。 该值可以是一个 PLUGIN_LICENSE_PROPRIETARY PLUGIN_LICENSE_GPL PLUGIN_LICENSE_BSD

  • init :一次性初始化函数,或者 NULL 如果没有这样的函数。 服务器在加载插件时执行此功能 INSTALL PLUGIN ,对于 mysql.plugin 表中 列出 的插件, 或者 在服务器启动时发生。 该函数接受一个参数,该参数指向用于标识插件的内部结构。 它为成功返回零,为失败返回非零。

  • deinit :一次性取消初始化函数,或者 NULL 如果没有这样的函数。 服务器在卸载插件时执行此功能 UNINSTALL PLUGIN ,对于 mysql.plugin 服务器关闭时 ,对于 表中 列出 的插件, 或者为插件 执行该操作 该函数接受一个参数,该参数指向用于标识插件的内部结构。它为成功返回零,为失败返回非零。

  • version :插件版本号。 安装插件后,可以从 INFORMATION_SCHEMA.PLUGINS 表中 检索此值 该值包括主要和次要数字。 如果将值写为十六进制常量,则格式为 ,其中 分别为主要和次要数字。 例如, 代表版本3.2。 0xMMNN MM NN 0x0302

  • status_vars :指向与插件关联的状态变量的结构的指针,或者 NULL 如果没有这样的变量。 安装插件后,这些变量将显示在 SHOW STATUS 语句 的输出中

    status_vars 如果不是 NULL 成员 指向 st_mysql_show_var 描述状态变量 结构 数组 请参见 第29.2.4.2.2节“服务器插件状态和系统变量”

  • system_vars :指向与插件关联的系统变量的结构的指针,或者 NULL 如果没有这样的变量。 这些选项和系统变量可用于帮助初始化插件中的变量。 安装插件后,这些变量将显示在 SHOW VARIABLES 语句 的输出中

    system_vars 如果不是 NULL 成员 指向 st_mysql_sys_var 描述系统变量 结构 数组 请参见 第29.2.4.2.2节“服务器插件状态和系统变量”

  • __reserved1 :未来的占位符。 它应该设置为 NULL

  • flags :插件标志。 各个位对应于不同的标志。 该值应设置为适用标志的OR。 这些标志可用:

    #define PLUGIN_OPT_NO_INSTALL 1UL / *不能动态加载* /
    #define PLUGIN_OPT_NO_UNINSTALL 2UL / *不能动态卸载* /
    #define PLUGIN_OPT_ALLOW_EARLY 4UL / *允许--early-plugin-load * /
    

    启用时,标志具有以下含义:

    • PLUGIN_OPT_NO_INSTALL :无法在运行时使用该 INSTALL PLUGIN 语句 加载插件 这是适当的,必须在与服务器启动时加载的插件 --plugin-load --plugin-load-add --early-plugin-load 选择。

    • PLUGIN_OPT_NO_UNINSTALL :使用该 UNINSTALL PLUGIN 语句 无法在运行时卸载插件

    • PLUGIN_OPT_ALLOW_EARLY :可以使用该 --early-plugin-load 选项 在服务器启动序列的早期加载插件 此标志对于是否可以在服务器启动时使用 --plugin-load or --plugin-load-add 选项 加载插件 ,或者在运行时使用 INSTALL PLUGIN 语句 加载此标志

      此标志已添加到MySQL 8.0.17中。 使用8.0.17之前的MySQL发行版编译的所有插件都没有设置此标志。 将这些加载到8.0.17之前的服务器时这没关系,但尝试使用 --early-plugin-load 8.0.17之前的MySQL发行版加载的插件二进制文件加载到8.0.17或更高版本的服务器中将会失败。 必须根据MySQL 8.0.17或更高版本重新编译插件。

只有在加载和卸载插件时 ,服务器 才会 调用 常规插件描述符中 init deinit 函数。 它们与插件的使用无关,例如当SQL语句导致调用插件时发生。

例如,包含单个全文解析器插件的库的描述符信息 simple_parser 如下所示:

mysql_declare_plugin(ftexample)
{
  MYSQL_FTPARSER_PLUGIN,/ * type * /
  &simple_parser_descriptor,/ *描述符* /
  “simple_parser”,/ * name * /
  “Oracle Corporation”,/ * author * /
  “简单的全文解析器”,/ *描述* /
  PLUGIN_LICENSE_GPL,/ *插件许可证* /
  simple_parser_plugin_init,/ * init函数(加载时)* /
  simple_parser_plugin_deinit,/ * deinit函数(卸载时)* /
  0x0001,/ *版本* /
  simple_status,/ *状态变量* /
  simple_system_variables,/ *系统变量* /
  空值,
  0
}
mysql_declare_plugin_end;

对于全文解析器插件,类型必须是 MYSQL_FTPARSER_PLUGIN 这是 WITH PARSER 在创建 FULLTEXT 索引 将插件标识为合法用于 子句的 值。 (此子句没有其他插件类型合法。)

plugin.h 像这样 定义 mysql_declare_plugin() mysql_declare_plugin_end 宏:

#ifndef MYSQL_DYNAMIC_PLUGIN
#define __MYSQL_DECLARE_PLUGIN(NAME,VERSION,PSIZE,DECLS)\
MYSQL_PLUGIN_EXPORT int VERSION = MYSQL_PLUGIN_INTERFACE_VERSION; \
MYSQL_PLUGIN_EXPORT int PSIZE = sizeof(struct st_mysql_plugin); \
MYSQL_PLUGIN_EXPORT struct st_mysql_plugin DECLS [] = {
#其他
#define __MYSQL_DECLARE_PLUGIN(NAME,VERSION,PSIZE,DECLS)\
MYSQL_PLUGIN_EXPORT int _mysql_plugin_interface_version_ = MYSQL_PLUGIN_INTERFACE_VERSION; \
MYSQL_PLUGIN_EXPORT int _mysql_sizeof_struct_st_plugin_ = sizeof(struct st_mysql_plugin); \
MYSQL_PLUGIN_EXPORT struct st_mysql_plugin _mysql_plugin_declarations _ [] = {
#万一

#define mysql_declare_plugin(NAME)\
__MYSQL_DECLARE_PLUGIN(NAME,\
                 builtin_ ## NAME ## _plugin_interface_version,\
                 builtin_ ## NAME ## _sizeof_struct_st_plugin,\
                 builtin_ ## NAME ## _plugin)

#define mysql_declare_plugin_end,{0,0,0,0,0,0,0,0,0,0,0,0,0}}
注意

_mysql_plugin_interface_version_ 只有在 定义 符号时, 这些声明才定义 符号 MYSQL_DYNAMIC_PLUGIN 这意味着 -DMYSQL_DYNAMIC_PLUGIN 必须将其作为编译命令的一部分提供,以将插件构建为共享库。

当宏如上所示使用时,它们会扩展为以下代码,该代码定义了两个必需的符号( _mysql_plugin_interface_version_ _mysql_plugin_declarations_ ):

int _mysql_plugin_interface_version_ = MYSQL_PLUGIN_INTERFACE_VERSION;
int _mysql_sizeof_struct_st_plugin_ = sizeof(struct st_mysql_plugin);
struct st_mysql_plugin _mysql_plugin_declarations _ [] = {
{
  MYSQL_FTPARSER_PLUGIN,/ * type * /
  &simple_parser_descriptor,/ *描述符* /
  “simple_parser”,/ * name * /
  “Oracle Corporation”,/ * author * /
  “简单的全文解析器”,/ *描述* /
  PLUGIN_LICENSE_GPL,/ *插件许可证* /
  simple_parser_plugin_init,/ * init函数(加载时)* /
  simple_parser_plugin_deinit,/ * deinit函数(卸载时)* /
  0x0001,/ *版本* /
  simple_status,/ *状态变量* /
  simple_system_variables,/ *系统变量* /
  空值,
  0
}
  {0,0,0,0,0,0,0,0,0,0,0,0}}
};

前面的示例在通用描述符中声明了一个插件,但是可以声明多个插件。 列出声明一个与前一后 mysql_declare_plugin() mysql_declare_plugin_end ,以逗号分隔。

MySQL服务器插件必须编译为C ++代码。 您不应使用的一个C ++特性是初始化全局结构的非常量变量。 结构的成员(例如 st_mysql_plugin 结构)应仅使用常量变量进行初始化。 simple_parser 前面显示 描述符在C ++插件中是允许的,因为它满足了这个要求:

mysql_declare_plugin(ftexample)
{
  MYSQL_FTPARSER_PLUGIN,/ * type * /
  &simple_parser_descriptor,/ *描述符* /
  “simple_parser”,/ * name * /
  “Oracle Corporation”,/ * author * /
  “简单的全文解析器”,/ *描述* /
  PLUGIN_LICENSE_GPL,/ *插件许可证* /
  simple_parser_plugin_init,/ * init函数(加载时)* /
  simple_parser_plugin_deinit,/ * deinit函数(卸载时)* /
  0x0001,/ *版本* /
  simple_status,/ *状态变量* /
  simple_system_variables,/ *系统变量* /
  空值,
  0
}
mysql_declare_plugin_end;

这是编写通用描述符的另一种有效方法。 它使用常量变量来指示插件名称,作者和描述:

const char * simple_parser_name =“simple_parser”;
const char * simple_parser_author =“Oracle Corporation”;
const char * simple_parser_description =“简单的全文解析器”;

mysql_declare_plugin(ftexample)
{
  MYSQL_FTPARSER_PLUGIN,/ * type * /
  &simple_parser_descriptor,/ *描述符* /
  simple_parser_name,/ * name * /
  simple_parser_author,/ * author * /
  simple_parser_description,/ * description * /
  PLUGIN_LICENSE_GPL,/ *插件许可证* /
  simple_parser_plugin_init,/ * init函数(加载时)* /
  simple_parser_plugin_deinit,/ * deinit函数(卸载时)* /
  0x0001,/ *版本* /
  simple_status,/ *状态变量* /
  simple_system_variables,/ *系统变量* /
  空值,
  0
}
mysql_declare_plugin_end;

但是,以下一般描述符无效。 它使用结构成员来指示插件名称,作者和描述,但结构在C ++中不被视为常量初始化器:

typedef结构
{
  const char * name;
  const char * author;
  const char * description;
} plugin_info;

plugin_info parser_info = {
  “simple_parser”
  “甲骨文公司”,
  “简单的全文解析器”
};

mysql_declare_plugin(ftexample)
{
  MYSQL_FTPARSER_PLUGIN,/ * type * /
  &simple_parser_descriptor,/ *描述符* /
  parser_info.name,/ * name * /
  parser_info.author,/ * author * /
  parser_info.description,/ * description * /
  PLUGIN_LICENSE_GPL,/ *插件许可证* /
  simple_parser_plugin_init,/ * init函数(加载时)* /
  simple_parser_plugin_deinit,/ * deinit函数(卸载时)* /
  0x0001,/ *版本* /
  simple_status,/ *状态变量* /
  simple_system_variables,/ *系统变量* /
  空值,
  0
}
mysql_declare_plugin_end;
29.2.4.2.2服务器插件状态和系统变量

服务器插件界面使插件暴露使用状态和系统变量 status_vars system_vars 一般的插件描述符的成员。

status_vars 通用插件描述符 成员(如果不是0)指向 st_mysql_show_var 结构 数组 ,每个结构描述一个状态变量,后跟一个所有成员都设置为0的 st_mysql_show_var 结构 结构具有以下定义:

struct st_mysql_show_var {
  const char * name;
  char *值;
  枚举enum_mysql_show_type类型;
};

下表显示了允许的状态变量 type 值以及相应的变量应该是什么。

表29.1服务器插件状态变量类型

变量类型 含义
SHOW_BOOL 指向布尔变量的指针
SHOW_INT 指向整数变量的指针
SHOW_LONG 指向长整数变量的指针
SHOW_LONGLONG 指向longlong整型变量的指针
SHOW_CHAR 一个字符串
SHOW_CHAR_PTR 指向字符串的指针
SHOW_ARRAY 指向另一个 st_mysql_show_var 数组的 指针
SHOW_FUNC 指向函数的指针
SHOW_DOUBLE 指向双倍的指针

对于 SHOW_FUNC 类型,调用该函数并填充其 out 参数,然后 参数提供有关要显示的变量的信息。 该功能有这个签名:

#define SHOW_VAR_FUNC_BUFF_SIZE 1024

typedef int(* mysql_show_var_func)(void * thd,
                                    struct st_mysql_show_var * out,
                                    char * buf);

system_vars 成员(如果不是0)指向一个 st_mysql_sys_var 结构 数组 ,每个结构描述一个系统变量(也可以从命令行或配置文件中设置),后跟一个所有成员都设置为0的 st_mysql_sys_var 结构。定义如下:

struct st_mysql_sys_var {
 int标志;
 const char * name,* comment;
 int(* check)(THD *,struct st_mysql_sys_var *,void *,st_mysql_value *);
 void(* update)(THD *,struct st_mysql_sys_var *,void *,const void *);
};

根据标志,附加字段根据需要附加。

为方便起见,定义了许多宏,这使得在插件中创建新的系统变量变得更加简单。

在整个宏中,可以使用以下字段:

  • name :系统变量的不带引号的标识符。

  • varname :静态变量的标识符。 如果没有,则与该 name 字段 相同

  • opt :系统变量的附加使用标志。 下表显示了允许的标志。

    表29.2服务器插件系统变量标志

    旗帜价值 描述
    PLUGIN_VAR_READONLY 系统变量是只读的
    PLUGIN_VAR_NOSYSVAR 系统变量在运行时不是用户可见的
    PLUGIN_VAR_NOCMDOPT 系统变量不能从命令行配置
    PLUGIN_VAR_NOCMDARG 命令行不需要参数(通常用于布尔变量)
    PLUGIN_VAR_RQCMDARG 命令行需要一个参数(这是默认值)
    PLUGIN_VAR_OPCMDARG 参数在命令行中是可选的
    PLUGIN_VAR_MEMALLOC 用于字符串变量; 表示要分配内存以存储字符串

  • comment :要在服务器帮助消息中显示的描述性注释。 NULL 如果要隐藏此变量。

  • check :检查功能, NULL 默认情况下。

  • update :更新功能, NULL 默认情况下。

  • default :变量默认值。

  • minimum :变量最小值。

  • maximum :变量最大值。

  • blocksize :可变块大小。 设置该值时,它将四舍五入为最接近的倍数 blocksize

可以通过直接使用静态变量或使用 SYSVAR() 访问器宏 来访问系统变量 SYSVAR() 宏提供了完整。 通常只有当代码不能直接访问底层变量时才应该使用它。

例如:

static int my_foo;
static MYSQL_SYSVAR_INT(foo_var,my_foo,
                        PLUGIN_VAR_RQCMDARG,“foo评论”,
                        NULL,NULL,0,0,INT_MAX,0);
 ...
   SYSVAR(foo_var)=值;
   value = SYSVAR(foo_var);
   my_foo = value;
   value = my_foo;

只能通过 THDVAR() 访问者宏 访问会话变量 例如:

static MYSQL_THDVAR_BOOL(some_flag,
                         PLUGIN_VAR_NOCMDARG,“旗帜评论”,
                         NULL,NULL,FALSE);
 ...
   if(THDVAR(thd,some_flag))
   {
     做一点事();
     THDVAR(thd,some_flag)= FALSE;
   }

所有全局和会话系统变量必须 在使用前 发布到 mysqld 这是通过构造 NULL 变量 终止数组并在插件公共接口中链接到变量来完成的。 例如:

static struct st_mysql_sys_var * my_plugin_vars [] = {
  MYSQL_SYSVAR(foo_var)
  MYSQL_SYSVAR(some_flag)
  空值
};
mysql_declare_plugin(fooplug)
{
  MYSQL _..._插件,
  &plugin_data,
  “fooplug”
  “foo作者”,
  “这确实很棒!”,
  PLUGIN_LICENSE_GPL,
  foo_init,
  foo_fini,
  0×0001,
  空值,
  my_plugin_vars,
  空值,
  0
}
mysql_declare_plugin_end;

以下便捷宏使您可以声明不同类型的系统变量:

  • 类型的布尔系统变量 bool ,它是一个1字节的布尔值。 (0 = false ,1 = true

    MYSQL_THDVAR_BOOL(名称,选择,评论,检查,更新,默认)
    MYSQL_SYSVAR_BOOL(名称,varname,opt,comment,check,update,default)
    
  • 字符串系统变量类型 char* ,它是指向以null结尾的字符串的指针。

    MYSQL_THDVAR_STR(名称,选择,评论,检查,更新,默认)
    MYSQL_SYSVAR_STR(name,varname,opt,comment,check,update,default)
    
  • 整数系统变量,其中有几个变种。

    • 一个 int 系统变量,其通常是4字节的符号字。

      MYSQL_THDVAR_INT(name,opt,comment,check,update,default,min,max,blk)
      MYSQL_SYSVAR_INT(name,varname,opt,comment,check,update,default,
                     最小,最大,块大小)
      
    • 一个 unsigned int 系统变量,其通常是4字节的无符号字。

      MYSQL_THDVAR_UINT(name,opt,comment,check,update,default,min,max,blk)
      MYSQL_SYSVAR_UINT(name,varname,opt,comment,check,update,default,
                      最小,最大,块大小)
      
    • long 系统变量,它典型地是4或8字节符号字。

      MYSQL_THDVAR_LONG(名称,选择,评论,检查,更新,默认,最小,最大,blk)
      MYSQL_SYSVAR_LONG(name,varname,opt,comment,check,update,default,
                      最小,最大,块大小)
      
    • 一个 unsigned long 系统变量,这典型地是4或8个字节的无符号字。

      MYSQL_THDVAR_ULONG(name,opt,comment,check,update,default,min,max,blk)
      MYSQL_SYSVAR_ULONG(name,varname,opt,comment,check,update,default,
                       最小,最大,块大小)
      
    • long long 系统变量,其通常为8字节符号字。

      MYSQL_THDVAR_LONGLONG(姓名,选择,评论,检查,更新,
                          default,minimum,maximum,blocksize)
      MYSQL_SYSVAR_LONGLONG(名称,varname,opt,comment,check,update,
                          default,minimum,maximum,blocksize)
      
    • 一个 unsigned long long 系统变量,其通常为8字节无符号字。

      MYSQL_THDVAR_ULONGLONG(姓名,选择,评论,检查,更新,
                           default,minimum,maximum,blocksize)
      MYSQL_SYSVAR_ULONGLONG(名称,varname,opt,comment,check,update,
                           default,minimum,maximum,blocksize)
      
    • double 系统变量,其通常为8字节符号字。

      MYSQL_THDVAR_DOUBLE(姓名,选择,评论,检查,更新,
                           default,minimum,maximum,blocksize)
      MYSQL_SYSVAR_DOUBLE(name,varname,opt,comment,check,update,
                           default,minimum,maximum,blocksize)
      
    • 一个 unsigned long 系统变量,这典型地是4或8个字节的无符号字。 可能值的范围是 typelib 从0开始 的元素数量的序数

      MYSQL_THDVAR_ENUM(名称,选择,评论,检查,更新,默认,类型库)
      MYSQL_SYSVAR_ENUM(名称,varname,opt,comment,check,update,
                      默认,typelib)
      
    • 一个 unsigned long long 系统变量,其通常为8字节无符号字。 每个位代表一个元素 typelib

      MYSQL_THDVAR_SET(名称,选择,评论,检查,更新,默认,类型库)
      MYSQL_SYSVAR_SET(名称,varname,opt,comment,check,update,
                     默认,typelib)
      

在内部,所有可变和插件系统变量都存储在 HASH 结构中。

通过编译 DYNAMIC_ARRAY 与命令行选项相关的所有变量,对它们进行排序,然后迭代它们以显示每个选项 来处理服务器命令行帮助文本的 显示。

当一个命令行选项已被处理,则接着从除去 argv handle_option() 函数( my_getopt.c ); 实际上,它被消耗了。

服务器在插件安装过程中,在插件成功加载之后但在调用插件初始化函数之前立即处理命令行选项

在运行时加载的插件不会受益于任何配置选项,并且必须具有可用的默认值。 安装完成后,它们将在 mysqld 初始化时 加载, 并且可以在命令行或内部设置配置选项 my.cnf

插件应该将 thd 参数视为只读。

29.2.4.2.3客户端插件描述符

每个客户端插件必须具有一个描述符,该描述符向客户端插件API提供信息。 描述符结构以所有客户端插件共有的固定成员集开头,后跟任何特定于插件类型的成员。

文件中 st_mysql_client_plugin 结构 client_plugin.h 定义了 包含公共成员 通用 描述符:

struct st_mysql_client_plugin
{
  int类型;
  unsigned int interface_version;
  const char * name;
  const char * author;
  const char * desc;
  unsigned int version [3];
  const char * license;
  void * mysql_api;
  int(* init)(char *,size_t,int,va_list);
  int(* deinit)();
  int(* options)(const char * option,const void *);
};

公共 st_mysql_client_plugin 描述符结构成员如下使用。 char * 成员应指定为以null结尾的字符串。

  • type :插件类型。 这必须是插件类型值之一 client_plugin.h ,例如 MYSQL_CLIENT_AUTHENTICATION_PLUGIN

  • interface_version :插件界面版本。 例如,这是 MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 一个身份验证插件。

  • name :一个提供插件名称的字符串。 当您 mysql_options() 使用该 MYSQL_DEFAULT_AUTH 选项 调用 或指定 --default-auth MySQL客户端程序 选项 时,这是您引用该插件的名称

  • author :一个命名插件作者的字符串。 这可以是你喜欢的任何东西。

  • desc :一个字符串,提供插件的一般描述。 这可以是你喜欢的任何东西。

  • version :插件版本为三个整数的数组,表示主要版本,次要版本和次要版本。 例如, {1,2,3} 表示版本1.2.3。

  • license :一个指定许可证类型的字符串。

  • mysql_api :供内部使用。 NULL 在插件描述符中 指定它

  • init :一次性初始化函数,或者 NULL 如果没有这样的函数。 客户端库在加载插件时执行此功能。 该函数返回零表示成功,非零表示失败。

    init 如果发生错误, 函数使用其前两个参数返回错误消息。 第一个参数是指向 char 缓冲区 的指针 ,第二个参数表示缓冲区长度。 init 函数 返回的任何消息都 必须以空值终止,因此最大消息长度是缓冲区长度减去1。 下一个参数传递给 mysql_load_plugin() 第一个表示有多少个参数(如果没有,则为0),后跟任何剩余的参数。

  • deinit :一次性取消初始化函数,或者 NULL 如果没有这样的函数。 客户端库在卸载插件时执行此功能。 该函数不带参数。 它为成功返回零,为失败返回非零。

  • options :用于处理传递给插件的选项的函数,或者 NULL 如果没有这样的函数。 该函数接受两个表示选项名称的参数和一个指向其值的指针。 该函数返回零表示成功,非零表示失败。

对于给定的客户端插件类型,公共描述符成员可以跟随实现该类型的插件所必需的附加成员。 例如, st_mysql_client_plugin_AUTHENTICATION 身份验证插件 结构在客户端库调用以执行身份验证的最后具有一个功能。

要声明插件,请使用 mysql_declare_client_plugin() mysql_end_client_plugin 宏:

mysql_declare_client_plugin(plugin_type
   ... members common to all client plugins...
   ... type-specific extra members...
mysql_end_client_plugin;

不要 明确 指定 type interface_version 成员。 mysql_declare_client_plugin() 宏使用 plugin_type 参数来自动生成它们的值。 例如,声明一个这样的身份验证客户端插件:

mysql_declare_client_plugin(认证)
  “my_auth_plugin”
  “作者姓名”,
  “我的客户端身份验证插件”,
  {1,0,0},
  “GPL”
  空值,
  my_auth_init,
  my_auth_deinit,
  my_auth_options,
  my_auth_main
mysql_end_client_plugin;

此声明使用 AUTHENTICATION 参数将 type interface_version 成员 设置 MYSQL_CLIENT_AUTHENTICATION_PLUGIN MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION

根据插件类型,描述符可能具有跟随公共成员的其他成员。 例如,对于身份验证插件,有一个函数( my_auth_main() 在刚才显示的描述符中)处理与服务器的通信。 请参见 第29.2.4.9节“编写身份验证插件”

通常,支持使用身份验证插件的客户端程序会通过调用 mysql_options() 来设置 MYSQL_DEFAULT_AUTH MYSQL_PLUGIN_DIR 选项 来加载插件

char * plugin_dir =“ path_to_plugin_dir”;
char * default_auth =“ plugin_name”;

/ * ...进程命令行选项... * /

mysql_options(&mysql,MYSQL_PLUGIN_DIR,plugin_dir);
mysql_options(&mysql,MYSQL_DEFAULT_AUTH,default_auth);

通常,程序还会接受 --plugin-dir --default-auth 允许用户覆盖默认值的选项。

如果客户端程序需要较低级别的插件管理,则客户端库包含带 st_mysql_client_plugin 参数的函数。 请参见 第28.7.17节“C API客户端插件函数”

29.2.4.3编译和安装插件库

编写插件后,必须编译并安装它。 编译共享对象的过程因系统而异。 如果使用构建库 CMake ,它应该能够为您的系统生成正确的编译命令。 如果库已命名 somepluglib ,则应该使用名称类似的共享库文件 somepluglib.so .so 文件名后缀可能与您的系统不同。)

要使用 CMake ,您需要设置配置文件以启用插件的编译和安装。 使用 plugin MySQL源代码分发目录 下的插件示例 作为指导。

创建 CMakeLists.txt ,应该看起来像这样:

MYSQL_ADD_PLUGIN(somepluglib somepluglib.c
  MODULE_ONLY MODULE_OUTPUT_NAME“somepluglib”)

CMake 生成时 Makefile ,它应该注意将 -DMYSQL_DYNAMIC_PLUGIN 标志 传递给编译命令 ,并向链接器传递 -lmysqlservices 标志,该标志是通过插件服务接口提供的服务链接任何函数所需的。 请参见 第29.3节“用于插件的MySQL服务”

运行 CMake ,然后运行 make

shell> cmake .
shell>make

如果需要为 CMake 指定配置选项 ,请参见 第2.9.4节“MySQL源配置选项” 以获取列表。 例如,您可能希望指定 CMAKE_INSTALL_PREFIX 指示应安装插件的MySQL基本目录。 您可以通过以下方式查看此选项使用的值 SHOW VARIABLES

MySQL的> SHOW VARIABLES LIKE 'basedir';
+ --------------- + ------------------ +
| Variable_name | 价值|
+ --------------- + ------------------ +
| 基地| / usr / local / mysql |
+ --------------- + ------------------ +

您应该安装库的插件目录的位置由 plugin_dir 系统变量 给出 例如:

MySQL的> SHOW VARIABLES LIKE 'plugin_dir';
+ --------------- + --------------------------------- -  +
| Variable_name | 价值|
+ --------------- + --------------------------------- -  +
| plugin_dir | / usr / local / mysql / lib / mysql / plugin |
+ --------------- + --------------------------------- -  +

要安装插件库,请使用 make

外壳> make install

验证 make install 是否 在正确的目录中安装了插件库。 安装后,请确保库权限允许服务器执行它。

29.2.4.4编写全文分析器插件

MySQL支持服务器端全文解析器插件 MyISAM InnoDB 有关全文解析器插件的介绍性信息,请参阅 全文解析器插件

全文解析器插件可用于替换或修改内置的全文解析器。 本节介绍如何编写名为的全文解析器插件 simple_parser 此插件基于比MySQL内置全文解析器使用的规则更简单的规则执行解析:单词是空白字符的非空运行。

说明使用 plugin/fulltext MySQL源代码分发目录中 的源代码 ,因此将位置更改为该目录。 以下过程描述了如何创建插件库:

  1. 要编写全文解析器插件,请在插件源文件中包含以下头文件。 根据插件的功能和要求,可能还需要其他MySQL或通用头文件。

    #include <mysql / plugin.h>
    

    plugin.h 定义 MYSQL_FTPARSER_PLUGIN 服务器插件类型和声明插件所需的数据结构。

  2. 设置插件库文件的库描述符。

    此描述符包含服务器插件的常规插件描述符。 对于全文解析器插件,类型必须是 MYSQL_FTPARSER_PLUGIN 这是 WITH PARSER 在创建 FULLTEXT 索引 将插件标识为合法用于 子句的 值。 (此子句没有其他插件类型合法。)

    例如,包含单个全文解析器插件的库的库描述符 simple_parser 如下所示:

    mysql_declare_plugin(ftexample)
    {
      MYSQL_FTPARSER_PLUGIN,/ * type * /
      &simple_parser_descriptor,/ *描述符* /
      “simple_parser”,/ * name * /
      “Oracle Corporation”,/ * author * /
      “简单的全文解析器”,/ *描述* /
      PLUGIN_LICENSE_GPL,/ *插件许可证* /
      simple_parser_plugin_init,/ * init函数(加载时)* /
      simple_parser_plugin_deinit,/ * deinit函数(卸载时)* /
      0x0001,/ *版本* /
      simple_status,/ *状态变量* /
      simple_system_variables,/ *系统变量* /
      空值,
      0
    }
    mysql_declare_plugin_end;
    

    所述 name 构件( simple_parser )指示要用于如在语句中的插件的引用名称 INSTALL PLUGIN UNINSTALL PLUGIN 这也是由 SHOW PLUGINS 显示的名称 INFORMATION_SCHEMA.PLUGINS

    有关更多信息,请参见 第29.2.4.2.1节“服务器插件库和插件描述符”

  3. 设置特定于类型的插件描述符。

    库描述符中的每个通用插件描述符都指向特定于类型的描述符。 对于全文解析器插件,特定于类型的描述符是 文件中 st_mysql_ftparser 结构 的实例 plugin.h

    struct st_mysql_ftparser
    {
      int interface_version;
      int(* parse)(MYSQL_FTPARSER_PARAM * param);
      int(* init)(MYSQL_FTPARSER_PARAM * param);
      int(* deinit)(MYSQL_FTPARSER_PARAM * param);
    };
    

    如结构定义所示,描述符具有接口版本号并包含指向三个函数的指针。

    接口版本号使用符号指定,符号格式为: 对于全文解析器插件,符号是 在源代码中,您将找到定义的全文解析器插件的实际接口版本号 当前接口版本号是 MYSQL_xxx_INTERFACE_VERSION MYSQL_FTPARSER_INTERFACE_VERSION include/mysql/plugin_ftparser.h 0x0101

    init deinit 成员应指向的函数,或者如果不需要该函数被设置为0。 parse 构件必须指向执行解析的功能。

    simple_parser 声明中,该描述符由表示 &simple_parser_descriptor 描述符指定全文插件接口的版本号(由给定 MYSQL_FTPARSER_INTERFACE_VERSION ),以及插件的解析,初始化和取消初始化函数:

    static struct st_mysql_ftparser simple_parser_descriptor =
    {
      MYSQL_FTPARSER_INTERFACE_VERSION,/ *接口版本* /
      simple_parser_parse,/ *解析函数* /
      simple_parser_init,/ *解析器初始化函数* /
      simple_parser_deinit / *解析器deinit函数* /
    };
    

    全文解析器插件用于两种不同的上下文,索引和搜索。 在这两种情况下,服务器在处理导致调用插件的每个SQL语句的开头和结尾调用初始化和取消初始化函数。 但是,在语句处理期间,服务器以特定于上下文的方式调用主解析函数:

    • 对于索引,服务器为要索引的每个列值调用解析器。

    • 对于搜索,服务器调用解析器来解析搜索字符串。 也可以为语句处理的行调用解析器。 在自然语言模式下,服务器无需调用解析器。 对于布尔模式短语搜索或带有查询扩展的自然语言搜索,解析器用于解析不在索引中的信息的列值。 此外,如果对没有 FULLTEXT 索引 的列进行布尔模式搜索 ,则将调用内置解析器。 (插件与特定索引相关联。如果没有索引,则不使用插件。)

    在普通的插件描述符中的插件声明有 init deinit 指向初始化和还原功能的部件,因此不会对特定类型的插件描述它所指向。 但是,这些函数对具有不同的用途,并且由于不同的原因而被调用:

    • 对于通用插件描述符中的插件声明,在加载和卸载插件时调用初始化和取消初始化函数。

    • 对于特定于类型的插件描述符,将根据使用该插件的SQL语句调用初始化和取消初始化函数。

    插件描述符中指定的每个接口函数应该为成功返回零,或者为非失败返回非零,并且每个接口函数都接收一个指向 MYSQL_FTPARSER_PARAM 包含解析上下文 结构 的参数 结构有这个定义:

    typedef struct st_mysql_ftparser_param
    {
      int(* mysql_parse)(struct st_mysql_ftparser_param *,
                         char * doc,int doc_len);
      int(* mysql_add_word)(struct st_mysql_ftparser_param *,
                            char * word,int word_len,
                            MYSQL_FTPARSER_BOOLEAN_INFO * boolean_info);
      void * ftparser_state;
      void * mysql_ftparam;
      struct charset_info_st * cs;
      char * doc;
      int length;
      int标志;
      enum enum_ftparser_mode模式;
    MYSQL_FTPARSER_PARAM;
    

    结构成员使用如下:

    • mysql_parse :指向调用服务器内置解析器的回调函数的指针。 当插件充当内置解析器的前端时,请使用此回调。 也就是说,当调用插件解析函数时,它应该处理输入以提取文本并将文本传递给 mysql_parse 回调。

      此回调函数的第一个参数应该是 param 值本身:

      param-> mysql_parse(param,...);
      

      前端插件可以提取文本并将其全部传递给内置解析器,或者它可以一次提取并将文本传递给内置解析器。 但是,在这种情况下,内置解析器会对文本片段进行处理,就像它们之间存在隐式字断开一样。

    • mysql_add_word :指向回调函数的指针,该函数将单词添加到全文索引或搜索项列表中。 解析器插件替换内置解析器时使用此回调。 也就是说,当调用插件解析函数时,它应该将输入解析为单词并调用 mysql_add_word 每个单词 回调。

      此回调函数的第一个参数应该是 param 值本身:

      param-> mysql_add_word(param,...);
      
    • ftparser_state :这是一个通用指针。 插件可以将其设置为指向内部用于其自身目的的信息。

    • mysql_ftparam :这是由服务器设置的。 它作为第一个参数传递给 mysql_parse mysql_add_word 回调。

    • cs :指向文本字符集信息的指针,如果没有可用信息,则为0。

    • doc :指向要解析的文本的指针。

    • length :要解析的文本的长度,以字节为单位。

    • flags :解析器标志。 如果没有特殊标志,则为零。 唯一的非零标志是 MYSQL_FTFLAGS_NEED_COPY ,这意味着 mysql_add_word() 必须保存该单词的副本(也就是说,它不能使用指向该单词的指针,因为该单词位于将被覆盖的缓冲区中。)

      在调用解析器插件,解析器插件本身或 mysql_parse() 函数 之前,MySQL可以设置或重置此标志

    • mode :解析模式。 该值将是以下常量之一:

      • MYSQL_FTPARSER_SIMPLE_MODE :解析快速简单模式,用于索引和自然语言查询。 解析器应该只将那些应该被索引的单词传递给服务器。 如果解析器使用长度限制或禁用词列表来确定要忽略哪些单词,则不应将此类单词传递给服务器。

      • MYSQL_FTPARSER_WITH_STOPWORDS :在禁用词模式下解析。 这用于布尔搜索中的短语匹配。 解析器应该将所有单词传递给服务器,甚至是超出任何正常长度限制的停用词或单词。

      • MYSQL_FTPARSER_FULL_BOOLEAN_INFO :解析布尔模式。 这用于解析布尔查询字符串。 解析器不仅应识别单词,还应识别布尔模式运算符,并使用 mysql_add_word 回调 将它们作为标记传递给服务器 为了告诉服务器传递了什么类型的令牌,插件需要填充 MYSQL_FTPARSER_BOOLEAN_INFO 结构并传递指针。

    注意

    对于 MyISAM ,禁用词列表和 ft_min_word_len ft_max_word_len 在tokenizer内部进行检查。 因为 InnoDB 在标记生成器之外检查 停用词列表和等效字长变量设置( innodb_ft_min_token_size innodb_ft_max_token_size )。 因此, InnoDB 插件解析器不需要检查停用词列表 innodb_ft_min_token_size ,或 innodb_ft_max_token_size 相反,建议返回所有单词 InnoDB 但是,如果要在插件解析器中检查停用词,请使用 MYSQL_FTPARSER_SIMPLE_MODE ,用于全文搜索索引和自然语言搜索。 对于 MYSQL_FTPARSER_WITH_STOPWORDS MYSQL_FTPARSER_FULL_BOOLEAN_INFO 模式,建议 InnoDB 在短语搜索的情况下 将所有单词返回到 包括停用词。

    如果在布尔模式下调用解析器,则 param->mode 值为 MYSQL_FTPARSER_FULL_BOOLEAN_INFO MYSQL_FTPARSER_BOOLEAN_INFO 解析器用来传送令牌信息到服务器结构如下:

    typedef struct st_mysql_ftparser_boolean_info
    {
      enum enum_ft_token_type类型;
      int yesno;
      int weight_adjust;
      char wasign;
      char trunc;
      int position;
      / *这些是解析器状态,必须删除。* /
      char prev;
      char * quot;
    MYSQL_FTPARSER_BOOLEAN_INFO;
    

    解析器应该按如下方式填充结构成员:

    • type :令牌类型。 下表显示了允许的类型。

      表29.3全文分析器标记类型

      令牌值 含义
      FT_TOKEN_EOF 数据结束
      FT_TOKEN_WORD 一个常规词
      FT_TOKEN_LEFT_PAREN 组或子表达式的开头
      FT_TOKEN_RIGHT_PAREN 组或子表达式的结束
      FT_TOKEN_STOPWORD 一个停用词

    • yesno :是否必须出现匹配的单词。 0表示该单词是可选的,但如果存在,则会增加匹配相关性。 大于0的值表示该单词必须存在。 小于0的值表示该单词不得出现。

    • weight_adjust :一个加权因子,用于确定单词计数的匹配程度。 它可用于增加或减少单词在相关性计算中的重要性。 值为零表示没有重量调整。 大于或小于零的值分别表示更高或更低的重量。 在这些例子 第12.9.2,“布尔全文搜索” ,即使用 < > 运营商说明如何加权作品。

    • wasign :加权因子的符号。 负值的作用类似于 ~ 布尔搜索运算符,这会导致单词对相关性的贡献为负。

    • trunc :是否应该进行匹配,就好像 * 已经给出 了布尔模式 截断运算符一样。

    • position :文档中单词的起始位置,以字节为单位。 通过使用 InnoDB 全文搜索。 对于以布尔模式调用的现有插件,必须为位置成员添加支持。

    插件不应该使用 prev ,并 quot 在成员 MYSQL_FTPARSER_BOOLEAN_INFO 结构。

    注意

    插件解析器框架不支持:

    • @distance 布尔运算符。

    • 前导加号( + )或减号( - )布尔运算符后跟空格,然后是单词( '+ apple' '- apple' )。 前导加号或减号必须与单词直接相邻,例如: '+apple' '-apple'

    有关布尔全文搜索运算符的信息,请参见 第12.9.2节“布尔全文搜索”

  4. 设置插件接口函数。

    库描述符中的通用插件描述符命名服务器在加载和卸载插件时应调用的初始化和取消初始化函数。 因为 simple_parser ,这些函数什么都不做,只返回零表示它们成功:

    static int simple_parser_plugin_init(void * arg __attribute __((unused)))
    {
      返回(0);
    }
    
    static int simple_parser_plugin_deinit(void * arg __attribute __((unused)))
    {
      返回(0);
    }
    

    因为这些函数实际上没有做任何事情,所以可以省略它们并在插件声明中为每个函数指定0。

    特定于类型的插件描述符,用于 simple_parser 命名服务器在使用插件时调用的初始化,取消初始化和解析函数。 因为 simple_parser ,初始化和取消初始化函数什么都不做:

    static int simple_parser_init(MYSQL_FTPARSER_PARAM * param
                                  __attribute __((未使用的)))
    {
      返回(0);
    }
    
    static int simple_parser_deinit(MYSQL_FTPARSER_PARAM * param
                                    __attribute __((未使用的)))
    {
      返回(0);
    }
    

    在这里,因为这些函数什么也不做,你可以省略它们并在插件描述符中为每个函数指定0。

    主解析函数 simple_parser_parse() 可以替代内置的全文解析器,因此需要将文本拆分为单词并将每个单词传递给服务器。 解析函数的第一个参数是指向包含解析上下文的结构的指针。 此结构具有 doc 指向要解析的文本的 length 成员 ,以及 指示文本长度的成员。 插件完成的简单解析将空白字符的非空运行视为单词,因此它标识了这样的单词:

    static int simple_parser_parse(MYSQL_FTPARSER_PARAM * param)
    {
      char * end,* start,* docend = param-> doc + param-> length;
    
      for(end = start = param-> doc ;; end ++)
      {
        if(结束== docend)
        {
          if(结束>开始)
            add_word(param,start,end  -  start);
          打破;
        }
        否则if(isspace(* end))
        {
          if(结束>开始)
            add_word(param,start,end  -  start);
          start = end + 1;
        }
      }
      返回(0);
    }
    

    当解析器找到每个单词时,它会调用一个函数 add_word() 将单词传递给服务器。 add_word() 只是一个辅助函数; 它不是插件界面的一部分。 解析器将解析上下文指针传递给 add_word() ,以及指向该单词和长度值的指针:

    static void add_word(MYSQL_FTPARSER_PARAM * param,char * word,size_t len)
    {
      MYSQL_FTPARSER_BOOLEAN_INFO bool_info =
        {FT_TOKEN_WORD,0,0,0,0,0,'',0};
    
      param-> mysql_add_word(param,word,len和bool_info);
    }
    

    对于布尔模式解析, add_word() 填充 bool_info 结构 的成员,如 结构讨论中所述 st_mysql_ftparser_boolean_info

  5. 设置状态变量。 对于 simple_parser 插件,以下状态变量数组设置一个状态变量,其值为静态文本,另一个状态变量具有存储在长整数变量中的值:

    long number_of_calls = 0;
    
    struct st_mysql_show_var simple_status [] =
    {
      {“simple_parser_static”,(char *)“只是一个静态文本”,SHOW_CHAR},
      {“simple_parser_called”,(char *)&number_of_calls,SHOW_LONG},
      {0,0,0}
    };
    

    通过使用以插件名称开头的状态变量名称,您可以轻松地显示插件的变量 SHOW STATUS

    MySQL的> SHOW STATUS LIKE 'simple_parser%';
    + ---------------------- + -------------------- +
    | Variable_name | 价值|
    + ---------------------- + -------------------- +
    | simple_parser_static | 只是一个静态文本|
    | simple_parser_called | 0 |
    + ---------------------- + -------------------- +
    
  6. 要编译和安装插件库文件,请使用 第29.2.4.3节“编译和安装插件库”中的说明 要使库文件可供使用,请将其安装在plugin目录( plugin_dir 系统变量 指定的目录 )中。 对于 simple_parser 插件,它是从源代码构建MySQL时编译和安装的。 它也包含在二进制分发中。 构建过程生成一个名称为的共享对象库 mypluglib.so .so 后缀可能因您的平台而异)。

  7. 要使用该插件,请将其注册到服务器。 例如,要在运行时注册插件,请使用此语句( .so 根据需要 调整 平台 后缀):

    安装PLUGIN simple_parser SONAME'mypluglib.so';
    

    有关插件加载的其他信息,请参见 第5.6.1节“安装和卸载插件”

  8. 要验证插件安装,请检查 INFORMATION_SCHEMA.PLUGINS 表或使用该 SHOW PLUGINS 语句。 请参见 第5.6.2节“获取服务器插件信息”

  9. 测试插件以验证它是否正常工作。

    创建一个包含字符串列的表,并将解析器插件与 FULLTEXT 列上的索引 相关联

    
    mysql> CREATE TABLE t (c VARCHAR(255),
        - >   FULLTEXT (c) WITH PARSER simple_parser
        - >) ENGINE=MyISAM;
    查询OK,0行受影响(0.01秒)
    

    在表格中插入一些文字并尝试一些搜索。 这些应该验证解析器插件将所有非空白字符视为单词字符:

    mysql> INSERT INTO t VALUES
        - >   ('utf8mb4_0900_as_cs is a case-sensitive collation'),
        - >   ('I\'d like a case of oranges'),
        - >   ('this is sensitive information'),
        - >   ('another row'),
        - >  ('yet another row');
    查询OK,5行受影响(0.02秒)
    记录:5个重复:0个警告:0
    
    MySQL的> SELECT c FROM t;
    + ------------------------------------------------- -  +
    | c |
    + ------------------------------------------------- -  +
    | utf8mb4_0900_as_cs是区分大小写的排序规则|
    | 我想要一箱橘子|
    | 这是敏感信息|
    | 另一行|
    | 又一行|
    + ------------------------------------------------- -  +
    5行(0.00秒)
    
    MySQL的> SELECT MATCH(c) AGAINST('case') FROM t;
    + -------------------------- +
    | 比赛(c)反对('案例')|
    + -------------------------- +
    | 0 |
    | 1.2968142032623 |
    | 0 |
    | 0 |
    | 0 |
    + -------------------------- +
    5行(0.00秒)
    
    MySQL的> SELECT MATCH(c) AGAINST('sensitive') FROM t;
    + ------------------------------- +
    | 比赛(c)反对('敏感')|
    + ------------------------------- +
    | 0 |
    | 0 |
    | 1.3253291845322 |
    | 0 |
    | 0 |
    + ------------------------------- +
    5行(0.01秒)
    
    MySQL的> SELECT MATCH(c) AGAINST('case-sensitive') FROM t;
    + ------------------------------------ +
    | 匹配(c)反对('区分大小写')|
    + ------------------------------------ +
    | 1.3109166622162 |
    | 0 |
    | 0 |
    | 0 |
    | 0 |
    + ------------------------------------ +
    5行(0.01秒)
    
    MySQL的> SELECT MATCH(c) AGAINST('I\'d') FROM t;
    + -------------------------- +
    | 比赛(c)反对('我'')|
    + -------------------------- +
    | 0 |
    | 1.2968142032623 |
    | 0 |
    | 0 |
    | 0 |
    + -------------------------- +
    5行(0.01秒)
    

    case insensitive 都不 匹配 不区分大小写 它们对内置解析器的方式。

29.2.4.5编写守护程序插件

守护程序插件是一种简单类型的插件,用于应由服务器运行但不与之通信的代码。 本节介绍如何使用 plugin/daemon_example MySQL源代码分发目录中 的示例插件编写守护程序服务器插件 该目录包含 daemon_example.cc 名为守护进程插件 源文件,该守护进程插件以固定 daemon_example 间隔将心跳字符串写入 mysql-heartbeat.log 数据目录中 指定的文件

要编写守护程序插件,请在插件源文件中包含以下头文件。 根据插件的功能和要求,可能还需要其他MySQL或通用头文件。

#include <mysql / plugin.h>

plugin.h 定义 MYSQL_DAEMON_PLUGIN 服务器插件类型和声明插件所需的数据结构。

daemon_example.cc 文件设置库描述符如下。 库描述符包括单个通用服务器插件描述符。

mysql_declare_plugin(daemon_example)
{
  MYSQL_DAEMON_PLUGIN,
  &daemon_example_plugin,
  “daemon_example”
  “Brian Aker”,
  “守护进程示例,在mysql-heartbeat.log中创建一个心跳节拍文件”,
  PLUGIN_LICENSE_GPL,
  daemon_example_plugin_init,/ *插件初始化* /
  daemon_example_plugin_deinit,/ *插件Deinit * /
  0x0100 / * 1.0 * /,
  NULL,/ *状态变量* /
  NULL,/ *系统变量* /
  NULL,/ * config选项* /
  0,/ *标志* /
}
mysql_declare_plugin_end;

所述 name 构件( daemon_example )指示要用于如在语句中的插件的引用名称 INSTALL PLUGIN UNINSTALL PLUGIN 这也是由 SHOW PLUGINS 显示的名称 INFORMATION_SCHEMA.PLUGINS

插件描述符的第二个成员 daemon_example_plugin 指向特定于类型的守护程序插件描述符。 此结构仅包含特定于类型的API版本号:

struct st_mysql_daemon daemon_example_plugin =
{MYSQL_DAEMON_INTERFACE_VERSION};

特定于类型的结构没有接口功能。 服务器和插件之间没有通信,除了服务器从通用插件描述符调用初始化和取消初始化函数来启动和停止插件:

  • daemon_example_plugin_init() 打开心跳文件并生成一个定期唤醒的线程,并将下一条消息写入该文件。

  • daemon_example_plugin_deinit() 关闭文件并执行其他清理。

要编译和安装插件库文件,请使用 第29.2.4.3节“编译和安装插件库”中的说明 要使库文件可供使用,请将其安装在plugin目录( plugin_dir 系统变量 指定的目录 )中。 对于 daemon_example 插件,它是从源代码构建MySQL时编译和安装的。 它也包含在二进制分发中。 构建过程生成一个名称为的共享对象库 libdaemon_example.so .so 后缀可能因您的平台而异)。

要使用该插件,请将其注册到服务器。 例如,要在运行时注册插件,请使用此语句( .so 根据需要 调整 平台 后缀):

INSTALL PLUGIN daemon_example SONAME'ibdaemon_example.so';

有关插件加载的其他信息,请参见 第5.6.1节“安装和卸载插件”

要验证插件安装,请检查 INFORMATION_SCHEMA.PLUGINS 表或使用该 SHOW PLUGINS 语句。 请参见 第5.6.2节“获取服务器插件信息”

在加载插件时,它会定期将心跳字符串写入 mysql-heartbeat.log 数据目录中 指定的文件 此文件无限制地增长,因此在您满意自己插件正常运行后,请将其卸载:

UNINSTALL PLUGIN daemon_example;

29.2.4.6编写INFORMATION_SCHEMA插件

本节介绍如何编写服务器端 INFORMATION_SCHEMA 表插件。 例如,实现此类插件的代码,请参阅 sql/sql_show.cc MySQL源代码分发 文件。 您还可以查看 InnoDB 源代码 中的示例插件 请参阅 源树中 handler/i_s.cc handler/ha_innodb.cc 文件 InnoDB (在 storage/innobase 目录中)。

要编写 INFORMATION_SCHEMA 表插件,请在插件源文件中包含以下头文件。 根据插件的功能和要求,可能还需要其他MySQL或通用头文件。

#include <sql_class.h>
#include <table.h>

这些头文件位于 sql MySQL源代码分发目录中。 它们包含C ++结构,因此 INFORMATION_SCHEMA 插件 的源文件 必须编译为C ++代码。

这里开发的示例插件的源文件名为 simple_i_s_table.cc 它创建一个 INFORMATION_SCHEMA 名为 的简单 SIMPLE_I_S_TABLE ,其中有两列名为 NAME VALUE 实现该表的插件库的一般描述符如下所示:

mysql_declare_plugin(simple_i_s_library)
{
  MYSQL_INFORMATION_SCHEMA_PLUGIN,
  &simple_table_info,/ *特定于类型的描述符* /
  “SIMPLE_I_S_TABLE”,/ *表名* /
  “作者姓名”,/ *作者* /
  “简单INFORMATION_SCHEMA表”,/ *说明* /
  PLUGIN_LICENSE_GPL,/ *许可证类型* /
  simple_table_init,/ * init函数* /
  空值,
  0x0100,/ *版本= 1.0 * /
  NULL,/ *没有状态变量* /
  NULL,/ *没有系统变量* /
  NULL,/ *没有保留信息* /
  0 / *没有标志* /
}
mysql_declare_plugin_end;

所述 name 构件( SIMPLE_I_S_TABLE )指示要用于如在语句中的插件的引用名称 INSTALL PLUGIN UNINSTALL PLUGIN 这也是由 SHOW PLUGINS 显示的名称 INFORMATION_SCHEMA.PLUGINS

simple_table_info 通用描述符 成员指向特定于类型的描述符,该描述符仅包含特定于类型的API版本号:

static struct st_mysql_information_schema simple_table_info =
{MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION};

通用描述符指向初始化和取消初始化函数:

  • 初始化函数提供有关表结构和填充表的函数的信息。

  • 取消初始化函数执行任何所需的清理。 如果不需要清理,则此描述符成员可以 NULL (如所示示例中所示)。

初始化函数应该返回0表示成功,如果发生错误则返回1。 该函数接收一个通用指针,它应该解释为指向表结构的指针:

static int table_init(void * ptr)
{
  ST_SCHEMA_TABLE * schema_table =(ST_SCHEMA_TABLE *)ptr;

  schema_table-> fields_info = simple_table_fields;
  schema_table-> fill_table = simple_fill_table;
  返回0;
}

该函数应该设置表结构的这两个成员:

  • fields_info ST_FIELD_INFO 包含有关每列的信息 结构 数组

  • fill_table :填充表的函数。

指向的数组 fields_info 应该包含每个列的一个元素 INFORMATION_SCHEMA 加上一个终止元素。 simple_table_fields 示例插件 的以下 数组表示它 SIMPLE_I_S_TABLE 有两列。 NAME 是字符串值,长度为10, VALUE 是整数值,显示宽度为20.最后一个结构标记数组的结尾。

static ST_FIELD_INFO simple_table_fields [] =
{
  {“NAME”,10,MYSQL_TYPE_STRING,0,0 0,0},
  {“VALUE”,6,MYSQL_TYPE_LONG,0,MY_I_S_UNSIGNED,0,0},
  {0,0,MYSQL_TYPE_NULL,0,0,0,0}
};

有关列的信息结构的更多信息,请参阅的定义 ST_FIELD_INFO table.h 头文件中。 允许的 类型值是C API中使用的值; 请参见 第28.7.5节“C API数据结构” MYSQL_TYPE_xxx

fill_table 成员应设置为填充表的函数,并返回0表示成功,如果发生错误则返回1。 对于示例插件,该 simple_fill_table() 函数如下所示:

static int simple_fill_table(THD * thd,TABLE_LIST * tables,Item * cond)
{
  TABLE * table = tables-> table;

  table-> field [0]  - > store(“Name 1”,6,system_charset_info);
  表 - >字段[1]  - >存储(1);
  if(schema_table_store_record(thd,table))
    返回1;
  table-> field [0]  - > store(“Name 2”,6,system_charset_info);
  表 - >字段[1]  - >存储器(2);
  if(schema_table_store_record(thd,table))
    返回1;
  返回0;
}

对于 INFORMATION_SCHEMA 表的 每一行 ,此函数初始化每列,然后调用 schema_table_store_record() 以安装该行。 store() 参数取决于的值的类型的方法来被存储。 对于第0列( NAME ,字符串), store() 获取指向字符串的指针,其长度以及有关字符串字符集的信息:

store(const char * to,uint length,CHARSET_INFO * cs);

对于第1列( VALUE ,整数), store() 取值和一个标志,指示它是否为无符号:

store(longlong nr,bool unsigned_value);

有关如何填充 INFORMATION_SCHEMA 表的 其他示例 ,请搜索 schema_table_store_record() in的 实例 sql_show.cc

要编译和安装插件库文件,请使用 第29.2.4.3节“编译和安装插件库”中的说明 要使库文件可供使用,请将其安装在plugin目录( plugin_dir 系统变量 指定的目录 )中。

要测试插件,请安装它:

MySQL的> INSTALL PLUGIN SIMPLE_I_S_TABLE SONAME 'simple_i_s_table.so';

验证表是否存在:

mysql> SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
    - >WHERE TABLE_NAME = 'SIMPLE_I_S_TABLE';
+ ------------------ +
| TABLE_NAME |
+ ------------------ +
| SIMPLE_I_S_TABLE |
+ ------------------ +

尝试从中进行选择:

MySQL的> SELECT * FROM INFORMATION_SCHEMA.SIMPLE_I_S_TABLE;
+ -------- + ------- +
| NAME | 价值|
+ -------- + ------- +
| 名称1 | 1 |
| 名称2 | 2 |
+ -------- + ------- +

卸载它:

MySQL的> UNINSTALL PLUGIN SIMPLE_I_S_TABLE;

29.2.4.7编写半同步复制插件

本节介绍如何使用 plugin/semisync MySQL源代码分发目录中 的示例插件编写服务器端半同步复制插件 该目录包含名为 rpl_semi_sync_master 和的 主插件和从属插件的源文件 rpl_semi_sync_slave 这里的信息仅包括如何设置插件框架。 有关插件如何实现复制功能的详细信息,请参阅源代码。

要编写半同步复制插件,请在插件源文件中包含以下头文件。 根据插件的功能和要求,可能还需要其他MySQL或通用头文件。

#include <mysql / plugin.h>

plugin.h 定义 MYSQL_REPLICATION_PLUGIN 服务器插件类型和声明插件所需的数据结构。

对于主端, semisync_master_plugin.cc 包含一个名为的插件的通用描述符 rpl_semi_sync_master

mysql_declare_plugin(semi_sync_master)
{
  MYSQL_REPLICATION_PLUGIN,
  &semi_sync_master_plugin,
  “rpl_semi_sync_master”
  “何振兴”,
  “半同步复制主机”,
  PLUGIN_LICENSE_GPL,
  semi_sync_master_plugin_init,/ * Plugin Init * /
  semi_sync_master_plugin_deinit,/ *插件Deinit * /
  0x0100 / * 1.0 * /,
  semi_sync_master_status_vars,/ *状态变量* /
  semi_sync_master_system_vars,/ *系统变量* /
  NULL,/ * config选项* /
  0,/ *标志* /
}
mysql_declare_plugin_end;

对于slave端, semisync_slave_plugin.cc 包含一个名为的插件的通用描述符 rpl_semi_sync_slave

mysql_declare_plugin(semi_sync_slave)
{
  MYSQL_REPLICATION_PLUGIN,
  &semi_sync_slave_plugin,
  “rpl_semi_sync_slave”
  “何振兴”,
  “半同步复制从属”,
  PLUGIN_LICENSE_GPL,
  semi_sync_slave_plugin_init,/ * Plugin Init * /
  semi_sync_slave_plugin_deinit,/ *插件Deinit * /
  0x0100 / * 1.0 * /,
  semi_sync_slave_status_vars,/ *状态变量* /
  semi_sync_slave_system_vars,/ *系统变量* /
  NULL,/ * config选项* /
  0,/ *标志* /
}
mysql_declare_plugin_end;

对于主插件和从插件,通用描述符具有指向特定于类型的描述符,初始化和取消初始化函数以及插件实现的状态和系统变量的指针。 有关变量设置的信息,请参见 第29.2.4.2.2节“服务器插件状态和系统变量” 以下注释讨论了类型特定的描述符以及主插件的初始化和取消初始化函数,但类似地应用于从插件。

semi_sync_master_plugin 主通用描述符 成员指向特定于类型的描述符,该描述符仅包含特定于类型的API版本号:

struct Mysql_replication semi_sync_master_plugin = {
  MYSQL_REPLICATION_INTERFACE_VERSION
};

初始化和取消初始化函数声明如下所示:

static int semi_sync_master_plugin_init(void * p);
static int semi_sync_master_plugin_deinit(void * p);

初始化函数使用指针向服务器注册事务和二进制日志 观察者 成功初始化后,服务器负责在适当的时间调用观察者。 (有关观察者的详细信息,请参阅源文件。)通过取消注册观察者来清除取消初始化函数。 每个函数返回0表示成功,如果发生错误则返回1。

要编译和安装插件库文件,请使用 第29.2.4.3节“编译和安装插件库”中的说明 要使库文件可供使用,请将其安装在plugin目录( plugin_dir 系统变量 指定的目录 )中。 对于 rpl_semi_sync_master rpl_semi_sync_slave 插件,从源代码构建MySQL时会编译和安装它们。 它们也包含在二进制分发中。 构建过程生成名称为 semisync_master.so 和的 共享对象库 semisync_slave.so .so 后缀可能因您的平台而异)。

29.2.4.8编写审计插件

本节介绍如何使用 plugin/audit_null MySQL源代码分发目录中 的示例插件编写服务器端审计插件 目录中的源文件 audit_null.c audit_null_variables.h 文件实现了一个名为的审计插件 NULL_AUDIT

注意

使用审计插件API的插件的其他示例是查询重写插件(请参见 第5.6.4节“重写器查询重写插件” )和版本标记插件(请参见 第5.6.6节“版本标记” )。

在服务器内,可插拔审计接口在 MySQL源代码分发目录 中的 sql_audit.h sql_audit.cc 文件中 实现 sql 此外,当发生可审计事件时,服务器中的多个位置会调用审计接口,以便在必要时通知已注册的审计插件。 要查看此类调用发生的位置,请在服务器源文件中搜索具有表单名称的函数调用 服务器操作发生审核通知,例如: mysql_audit_xxx()

  • 客户端连接和断开事件

  • 将消息写入常规查询日志(如果启用了日志)

  • 将消息写入错误日志

  • 将查询结果发送给客户端

要编写审计插件,请在插件源文件中包含以下头文件。 根据插件的功能和要求,可能还需要其他MySQL或通用头文件。

#include <mysql / plugin_audit.h>

plugin_audit.h 包含 plugin.h ,因此您无需明确包含后一个文件。 plugin.h 定义 MYSQL_AUDIT_PLUGIN 服务器插件类型和声明插件所需的数据结构。 plugin_audit.h 定义特定于审计插件的数据结构。

审计插件一般描述符

与任何MySQL服务器插件一样,审计插件具有通用插件描述符(请参见 第29.2.4.2.1节“服务器插件库和插件描述符” )和类型特定的插件描述符。 audit_null.c ,一般描述符 audit_null 看起来像这样:

mysql_declare_plugin(audit_null)
{
  MYSQL_AUDIT_PLUGIN,/ * type * /
  &audit_null_descriptor,/ *描述符* /
  “NULL_AUDIT”,/ * name * /
  “甲骨文公司”,/ *作者* /
  “Simple NULL Audit”,/ * description * /
  PLUGIN_LICENSE_GPL,
  audit_null_plugin_init,/ * init函数(加载时)* /
  audit_null_plugin_deinit,/ * deinit函数(卸载时)* /
  0x0003,/ *版本* /
  simple_status,/ *状态变量* /
  system_variables,/ *系统变量* /
  空值,
  0,
}
mysql_declare_plugin_end;

第一个成员 MYSQL_AUDIT_PLUGIN 将此插件标识为审计插件。

audit_null_descriptor 指向特定于类型的插件描述符,稍后描述。

所述 name 构件( NULL_AUDIT )指示要用于如在语句中的插件的引用名称 INSTALL PLUGIN UNINSTALL PLUGIN 这也是由 INFORMATION_SCHEMA.PLUGINS 显示的名称 SHOW PLUGINS

audit_null_plugin_init 被加载插件时初始化函数执行插件初始化。 audit_null_plugin_deinit 函数在卸载插件时执行清理。

一般的插件描述符还指 simple_status system_variables ,暴露多个状态和系统变量的结构。 启用插件后,可以使用 SHOW 语句( SHOW STATUS SHOW VARIABLES )或相应的性能架构表 检查这些变量

simple_status 结构使用表单名称声明了几个状态变量 为其收到的每个通知 递增 状态变量。 其他状态变量更具体, 仅为特定事件的通知增加它们。 Audit_null_xxx NULL_AUDIT Audit_null_called NULL_AUDIT

system_variables 是一个系统变量元素的数组,每个元素都是使用 定义的 这些系统变量具有表单的名称 这些变量可用于在运行时与插件进行通信。 MYSQL_THDVAR_xxx null_audit_xxx

审计插件类型特定描述符

audit_null_descriptor 通用插件描述符中 值指向特定于类型的插件描述符。 对于审计插件,此描述符具有以下结构(在中定义 plugin_audit.h ):

struct st_mysql_audit
{
  int interface_version;
  void(* release_thd)(MYSQL_THD);
  int(* event_notify)(MYSQL_THD,mysql_event_class_t,const void *);
  unsigned long class_mask [MYSQL_AUDIT_CLASS_MASK_SIZE];
};

审计插件的特定于类型的描述符具有以下成员:

  • interface_version :按照惯例,特定于类型的插件描述符以给定插件类型的接口版本开头。 服务器检查 interface_version 何时加载插件以查看插件是否与其兼容。 对于审计插件, interface_version 成员 的值 MYSQL_AUDIT_INTERFACE_VERSION (在中定义 plugin_audit.h )。

  • release_thd :服务器调用的函数,通知插件它正在与其线程上下文分离。 这应该 NULL 是没有这样的功能。

  • event_notify :服务器调用以通知插件已发生可审计事件的函数。 这个功能不应该是 NULL ; 这没有意义,因为不会发生审计。

  • class_mask :一系列 MYSQL_AUDIT_CLASS_MASK_SIZE 元素。 每个元素指定给定事件类的位掩码,以指示插件想要通知的子类。 (这是插件 订阅 感兴趣的事件的方式。)元素应为0以忽略相应事件类的事件。

服务器 一起 使用 event_notify release_thd 功能。 它们在特定线程的上下文中调用,并且线程可以执行产生多个事件通知的活动。 服务器第一次调用 event_notify 线程时,它会创建插件与线程的绑定。 存在此绑定时,无法卸载该插件。 当没有更多的线程事件发生时,服务器通过调用来通知插件 release_thd 功能,然后破坏绑定。 例如,当客户端发出语句时,处理该语句的线程可能会通知审计插件有关该语句生成的结果集以及正在记录的语句。 发生这些通知后,服务器会在将线程置于休眠状态之前释放插件,直到客户端发出另一个语句。

这种设计使插件能够在第一次调用 event_notify 函数时 分配给定线程所需的资源,并在 函数中释放它们 release_thd

event_notify函数:
  如果需要内存来服务线程
    分配内存
  ...其余的通知处理......

release_thd函数:
  如果分配了内存
    释放记忆
  ...其余的发布处理......

这比在通知功能中重复分配和释放内存更有效。

对于 NULL_AUDIT 审计插件,特定于类型的插件描述符如下所示:

static struct st_mysql_audit audit_null_descriptor =
{
  MYSQL_AUDIT_INTERFACE_VERSION,/ *接口版本* /
  NULL,/ * release_thd函数* /
  audit_null_notify,/ * notify function * /
  {(unsigned long)MYSQL_AUDIT_GENERAL_ALL,
    (unsigned long)MYSQL_AUDIT_CONNECTION_ALL,
    (unsigned long)MYSQL_AUDIT_PARSE_ALL,
    (unsigned long)MYSQL_AUDIT_AUTHORIZATION_ALL,
    (unsigned long)MYSQL_AUDIT_TABLE_ACCESS_ALL,
    (unsigned long)MYSQL_AUDIT_GLOBAL_VARIABLE_ALL,
    (unsigned long)MYSQL_AUDIT_SERVER_STARTUP_ALL,
    (unsigned long)MYSQL_AUDIT_SERVER_SHUTDOWN_ALL,
    (unsigned long)MYSQL_AUDIT_COMMAND_ALL,
    (unsigned long)MYSQL_AUDIT_QUERY_ALL,
    (unsigned long)MYSQL_AUDIT_STORED_PROGRAM_ALL}
};

服务器调用 audit_null_notify() 将审计事件信息传递给插件。 没有 release_thd 功能。

class_mask 成员是一个数组,指示插件订阅的事件类。 如图所示,数组内容订阅了所有可用事件类的所有子类。 要忽略给定事件类的所有通知,请指定相应的 class_mask 元素 为0。

的数目 class_mask 的元素对应于事件类,其中的每一个列在数量 mysql_event_class_t 枚举所定义 plugin_audit.h

typedef enum
{
  MYSQL_AUDIT_GENERAL_CLASS = 0,
  MYSQL_AUDIT_CONNECTION_CLASS = 1,
  MYSQL_AUDIT_PARSE_CLASS = 2,
  MYSQL_AUDIT_AUTHORIZATION_CLASS = 3,
  MYSQL_AUDIT_TABLE_ACCESS_CLASS = 4,
  MYSQL_AUDIT_GLOBAL_VARIABLE_CLASS = 5,
  MYSQL_AUDIT_SERVER_STARTUP_CLASS = 6,
  MYSQL_AUDIT_SERVER_SHUTDOWN_CLASS = 7,
  MYSQL_AUDIT_COMMAND_CLASS = 8,
  MYSQL_AUDIT_QUERY_CLASS = 9,
  MYSQL_AUDIT_STORED_PROGRAM_CLASS = 10,
  / *此项必须在列表的最后。* /
  MYSQL_AUDIT_CLASS_MASK_SIZE
} mysql_event_class_t;

对于任何给定的事件类, plugin_audit.h 定义各个事件子类的位掩码符号,以及作为 xxx_ALL 所有子类 位掩码的并集的 符号。 例如,对于 MYSQL_AUDIT_CONNECTION_CLASS (涵盖连接和断开事件的类), plugin_audit.h 定义以下符号:

typedef enum
{
  认证阶段完成后发生/ **。* /
  MYSQL_AUDIT_CONNECTION_CONNECT = 1 << 0,
  连接终止后发生/ **。* /
  MYSQL_AUDIT_CONNECTION_DISCONNECT = 1 << 1,
  / **在COM_CHANGE_USER RPC完成后发生。* /
  MYSQL_AUDIT_CONNECTION_CHANGE_USER = 1 << 2,
  / **在认证之前发生。* /
  MYSQL_AUDIT_CONNECTION_PRE_AUTHENTICATE = 1 << 3
} mysql_event_connection_subclass_t;

#define MYSQL_AUDIT_CONNECTION_ALL(MYSQL_AUDIT_CONNECTION_CONNECT | \
                                    MYSQL_AUDIT_CONNECTION_DISCONNECT | \
                                    MYSQL_AUDIT_CONNECTION_CHANGE_USER | \
                                    MYSQL_AUDIT_CONNECTION_PRE_AUTHENTICATE)

要订阅连接事件类的所有子类(如 NULL_AUDIT 插件所做的那样),插件 MYSQL_AUDIT_CONNECTION_ALL 在相应的 class_mask 元素中 指定 class_mask[1] 在本例中)。 为了只订阅一些子类,插件将 class_mask 元素 设置 为感兴趣的子类的并集。 例如,要仅订阅connect和change-user子类,插件将设置 class_mask[1] 为此值:

MYSQL_AUDIT_CONNECTION_CONNECT | MYSQL_AUDIT_CONNECTION_CHANGE_USER
审计插件通知功能

审计插件的大部分工作都发生在通知函数( event_notify 特定于类型的插件描述符 成员)中。 服务器为每个可审计事件调用此函数。 审计插件通知函数有这个原型:

int(* event_notify)(MYSQL_THD,mysql_event_class_t,const void *);

event_notify 函数原型 的第二个和第三个参数 表示事件类和事件结构的通用指针。 (不同类中的事件具有不同的结构。通知函数可以使用事件类值来确定应用哪个事件结构。)该函数处理事件并返回指示服务器是否应继续处理事件或终止事件的状态。

对于 NULL_AUDIT ,通知功能是 audit_null_notify() 此函数递增全局事件计数器(插件公开为 Audit_null_called 状态值的值),然后检查事件类以确定如何处理事件结构:

static int audit_null_notify(MYSQL_THD thd __attribute __((unused)),
                             mysql_event_class_t event_class,
                             const void * event)
{
  ...

  number_of_calls ++;

  if(event_class == MYSQL_AUDIT_GENERAL_CLASS)
  {
    const struct mysql_event_general * event_general =
                                    (const struct mysql_event_general *)事件;
    ...
  }
  else if(event_class == MYSQL_AUDIT_CONNECTION_CLASS)
  {
    const struct mysql_event_connection * event_connection =
                                (const struct mysql_event_connection *)事件;
    ...

  }
  else if(event_class == MYSQL_AUDIT_PARSE_CLASS)
  {
    const struct mysql_event_parse * event_parse =
                                      (const struct mysql_event_parse *)事件;
    ...
  }
  ...
}

通知函数 event 根据值 解释 参数 event_class 所述 event 参数是一个通用的指针事件记录,其结构的每个事件类不同。 (该 plugin_audit.h 文件包含定义每个事件类内容的结构。)对于每个类, audit_null_notify() 将事件转换为适当的特定于类的结构,然后检查其子类以确定要递增的子类计数器。 例如,处理连接事件类中的事件的代码如下所示:

else if(event_class == MYSQL_AUDIT_CONNECTION_CLASS)
{
  const struct mysql_event_connection * event_connection =
                              (const struct mysql_event_connection *)事件;

  switch(event_connection-> event_subclass)
  {
  case MYSQL_AUDIT_CONNECTION_CONNECT:
    number_of_calls_connection_connect ++;
    打破;
  case MYSQL_AUDIT_CONNECTION_DISCONNECT:
    number_of_calls_connection_disconnect ++;
    打破;
  case MYSQL_AUDIT_CONNECTION_CHANGE_USER:
    number_of_calls_connection_change_user ++;
    打破;
  case MYSQL_AUDIT_CONNECTION_PRE_AUTHENTICATE:
    number_of_calls_connection_pre_authenticate ++;
      打破;
  默认:
    打破;
  }
}
注意

MYSQL_AUDIT_GENERAL_CLASS 自MySQL 5.7.9起, 一般事件class( )已被弃用,将在未来的MySQL版本中删除。 为了减少插件开销,最好只订阅更感兴趣的特定事件类。

对于某些事件类, NULL_AUDIT 除了递增计数器之外 插件还执行其他处理。 在任何情况下,当通知功能完成处理事件时,它应返回一个状态,指示服务器是应该继续处理事件还是终止事件。

审计插件错误处理

审核插件通知功能可以通过两种方式报告当前事件的状态值:

  • 使用通知函数返回值。 在这种情况下,如果服务器应继续处理事件,则函数返回零;如果服务器应终止事件,则函数返回非零值。

  • my_message() 在从通知功能返回之前 调用该 函数来设置错误状态。 在这种情况下,忽略通知函数返回值,服务器终止事件处理并显示错误。 my_message() 参数指示哪些错误报告,它的消息。 例如:

    my_message(ER_AUDIT_API_ABORT,“这是我的错误信息。”,MYF(0));
    

    有些事件无法中止。 不考虑非零返回值, my_message() 错误调用必须遵循 is_error() 检查。 例如:

    if(!thd-> get_stmt_da() - > is_error())
    {
      my_message(ER_AUDIT_API_ABORT,“这是我的错误信息。”,MYF(0));
    }
    

某些事件无法终止:

  • MYSQL_AUDIT_CONNECTION_DISCONNECT :服务器无法阻止客户端断开连接。

  • MYSQL_AUDIT_COMMAND_END :此事件提供已完成执行的命令的状态,因此无法终止它。

如果审计插件返回非终止事件的非零状态,则服务器将忽略该状态并继续处理该事件。 如果审计插件使用该 my_message() 函数终止 不可 终结事件, 则也是如此

审计插件使用情况

要编译和安装插件库文件,请使用 第29.2.4.3节“编译和安装插件库”中的说明 要使库文件可供使用,请将其安装在plugin目录( plugin_dir 系统变量 指定的目录 )中。 对于 NULL_AUDIT 插件,它是从源代码构建MySQL时编译和安装的。 它也包含在二进制分发中。 构建过程生成一个名称为的共享对象库 adt_null.so .so 后缀可能因您的平台而异)。

要在运行时注册插件,请使用此语句( .so 根据需要 调整 平台 后缀):

INSTALL PLUGIN NULL_AUDIT SONAME'adt_null.so';

有关插件加载的其他信息,请参见 第5.6.1节“安装和卸载插件”

要验证插件安装,请检查 INFORMATION_SCHEMA.PLUGINS 表或使用该 SHOW PLUGINS 语句。 请参见 第5.6.2节“获取服务器插件信息”

安装审计插件时,它会公开状态变量,指示已调用插件的事件:

MySQL的> SHOW STATUS LIKE 'Audit_null%';
+ ---------------------------------------- + -------- +
| Variable_name | 价值|
+ ---------------------------------------- + -------- +
| Audit_null_authorization_column | 0 |
| Audit_null_authorization_db | 0 |
| Audit_null_authorization_procedure | 0 |
| Audit_null_authorization_proxy | 0 |
| Audit_null_authorization_table | 0 |
| Audit_null_authorization_user | 0 |
| Audit_null_called | 185547 |
| Audit_null_command_end | 20999 |
| Audit_null_command_start | 21001 |
| Audit_null_connection_change_user | 0 |
| Audit_null_connection_connect | 5823 |
| Audit_null_connection_disconnect | 5818 |
| Audit_null_connection_pre_authenticate | 5823 |
| Audit_null_general_error | 1 |
| Audit_null_general_log | 26559 |
| Audit_null_general_result | 19922 |
| Audit_null_general_status | 21000 |
| Audit_null_global_variable_get | 0 |
| Audit_null_global_variable_set | 0 |
| Audit_null_parse_postparse | 14648 |
| Audit_null_parse_preparse | 14648 |
| Audit_null_query_nested_start | 6 |
| Audit_null_query_nested_status_end | 6 |
| Audit_null_query_start | 14648 |
| Audit_null_query_status_end | 14647 |
| Audit_null_server_shutdown | 0 |
| Audit_null_server_startup | 1 |
| Audit_null_table_access_delete | 104 |
| Audit_null_table_access_insert | 2839 |
| Audit_null_table_access_read | 97842 |
| Audit_null_table_access_update | 278 |
+ ---------------------------------------- + -------- +

Audit_null_called 计算所有事件,其他变量计算特定事件子类的实例。 例如,前面的 SHOW STATUS 语句会导致服务器将结果发送到客户端,并在启用该日志时将消息写入常规查询日志。 因此,客户端发出的声明重复导致 Audit_null_called Audit_null_general_result 以及 Audit_null_general_log 将每次递增。 无论是否启用该日志,都会发生通知。

状态变量值在所有会话中聚合。 单个会话没有计数器。

NULL_AUDIT 公开了几个系统变量,可以在运行时与插件进行通信:

MySQL的> SHOW VARIABLES LIKE 'null_audit%';
+ ------------------------------------ + ------- +
| Variable_name | 价值|
+ ------------------------------------ + ------- +
| null_audit_abort_message | |
| null_audit_abort_value | 1 |
| null_audit_event_order_check | |
| null_audit_event_order_check_exact | 1 |
| null_audit_event_order_started | 0 |
| null_audit_event_record | |
| null_audit_event_record_def | |
+ ------------------------------------ + ------- +

要检查审计API调用的顺序,请将 null_audit_event_order_check 变量设置为预期的事件顺序。 例如:

SET null_audit_event_order_check =
      'MYSQL_AUDIT_CONNECTION_PRE_AUTHENTICATE ;;;'
      'MYSQL_AUDIT_GENERAL_LOG ;;;'
      'MYSQL_AUDIT_CONNECTION_CONNECT ;;';

该语句利用将相邻字符串连接成单个字符串的SQL语法。

值的格式为:

' event_name; event_data; command'['; event_name; event_data; command'] ......

匹配事件顺序后,该 null_audit_event_order_check 值将替换为值 EVENT-ORDER-OK

指定命令值 ABORT_RET 可以中止对指定事件的审计API调用。 以下示例 INSERT MYSQL_AUDIT_QUERY_STATUS_END 事件发生 中止 语句执行

SET null_audit_event_order_check =
   'MYSQL_AUDIT_COMMAND_START; command_id的= “3” ;;'
   'MYSQL_AUDIT_GENERAL_LOG ;;;'
   'MYSQL_AUDIT_QUERY_START ;;;'
   'MYSQL_AUDIT_QUERY_STATUS_END ;; ABORT_RET';

审计插件与前面的序列匹配后,它将中止事件处理并向客户端发送错误消息:

ERROR 3164(HY000):由审计API('MYSQL_AUDIT_QUERY_STATUS_END'; 1)中止。

从审计API通知例程返回非零值是中止事件执行的标准方法。 也可以通过将 null_audit_abort_value 变量 设置 为通知例程应返回的值 来指定自定义错误代码

SET null_audit_abort_value = 123;

中止序列会产生带有自定义错误代码的标准消息。 假设您设置审计日志系统变量,如下所示:

SET null_audit_abort_value = 123;
SET null_audit_event_order_check =
    'MYSQL_AUDIT_COMMAND_START; command_id的= “3” ;;'
    'MYSQL_AUDIT_GENERAL_LOG ;;;'
    'MYSQL_AUDIT_QUERY_START ;; ABORT_RET';

然后 SELECT 1 在此错误中 执行 结果:

ERROR 3164(HY000):由审计API('MYSQL_AUDIT_QUERY_START'; 123)中止。

事件也可以通过设置 null_audit_abort_message 变量 指定的自定义消息中止 :假设您设置审计日志系统变量,如下所示:

SET null_audit_abort_message ='自定义错误文本。';
SET null_audit_event_order_check =
    'MYSQL_AUDIT_COMMAND_START; command_id的= “3” ;;'
    'MYSQL_AUDIT_GENERAL_LOG ;;;'
    'MYSQL_AUDIT_QUERY_START ;; ABORT_RET';

然后中止序列会导致以下错误:

ERROR 3164(HY000):自定义错误文本。

出于测试创建的目的,可以记录通过插件的事件。 通过在 null_audit_event_record_def 变量中 指定开始和结束事件来开始录制

SET null_audit_event_record_def =
    'MYSQL_AUDIT_COMMAND_START; MYSQL_AUDIT_COMMAND_END';

语句执行导致存储 null_audit_event_record 变量 中发生的事件

要在测试后禁用插件,请使用此语句卸载它:

UNINSTALL PLUGIN NULL_AUDIT;

29.2.4.9编写认证插件

MySQL支持可插入身份验证,其中调用插件来验证客户端连接。 身份验证插件允许使用除 mysql.user 系统表中 存储的密码的内置方法之外的身份验证方法 例如,可以编写插件来访问外部认证方法。 此外,认证插件可以支持代理用户能力,使得连接用户是另一个用户的代理,并且出于访问控制的目的,将其视为具有不同用户的特权。 有关更多信息,请参见 第6.2.17节“可插入身份验证” 第6.2.18节“代理用户”

可以为服务器端或客户端编写认证插件。 服务器端插件使用与其他服务器插件类型相同的插件API,例如全文解析器或审计插件(尽管具有不同的特定于类型的描述符)。 客户端插件使用客户端插件API。

几个头文件包含与身份验证插件相关的信息:

  • plugin.h :定义 MYSQL_AUTHENTICATION_PLUGIN 服务器插件类型。

  • client_plugin.h :定义客户端插件的API。 这包括客户端插件描述符和客户端插件C API调用的函数原型(请参见 第28.7.17节“C API客户端插件函数” )。

  • plugin_auth.h :定义特定于身份验证插件的服务器插件API的一部分。 这包括服务器端身份验证插件的类型特定描述符和 MYSQL_SERVER_AUTH_INFO 结构。

  • plugin_auth_common.h :包含客户端和服务器身份验证插件的常见元素。 这包括返回值定义和 MYSQL_PLUGIN_VIO 结构。

要编写身份验证插件,请在插件源文件中包含以下头文件。 根据插件的功能和要求,可能还需要其他MySQL或通用头文件。

  • 对于实现服务器身份验证插件的源文件,请包含以下文件:

    #include <mysql / plugin_auth.h>
    
  • 对于实现客户端身份验证插件或客户端和服务器插件的源文件,请包含以下文件:

    #include <mysql / plugin_auth.h>
    #include <mysql / client_plugin.h>
    #include <mysql.h>
    

plugin_auth.h 包括 plugin.h plugin_auth_common.h ,因此您不需要明确包含后面的文件。

本节介绍如何编写一对协同工作的简单服务器和客户端身份验证插件。

警告

这些插件接受任何非空密码,密码以明文形式发送。 这是不安全的,因此插件 不应该在生产环境中使用。

这里开发的服务器端和客户端插件都被命名 auth_simple 第29.2.4.2节“插件数据结构” 中所述,插件库文件必须与客户端插件具有相同的基本名称,因此源文件名是 auth_simple.c 并生成一个名为的库 auth_simple.so (假设您的系统使用 .so 后缀作为后缀)库文件)。

在MySQL源代码发行版中,身份验证插件源位于 plugin/auth 目录中,可以作为编写其他身份验证插件的指南进行检查。 另外,要了解如何实现内置身份验证插件,请参阅 sql/sql_acl.cc 内置于MySQL服务器 sql-common/client.c 的插件以及内置于 libmysqlclient 客户端库的 插件 (对于内置客户端插件,请注意其中 auth_plugin_t 使用的结构与通常的客户端插件声明宏使用的结构不同。特别是,前两个成员是显式提供的,而不是声明宏。)

29.2.4.9.1编写服务器端认证插件

使用通常的通用描述符格式声明服务器端插件,该格式用于所有服务器插件类型(请参见 第29.2.4.2.1节“服务器插件库和插件描述符” )。 对于 auth_simple 插件,描述符如下所示:

mysql_declare_plugin(auth_simple)
{
  MYSQL_AUTHENTICATION_PLUGIN,
  &auth_simple_handler,/ *特定于类型的描述符* /
  “auth_simple”,/ *插件名称* /
  “作者姓名”,/ *作者* /
  “Any-password authentication plugin”,/ * description * /
  PLUGIN_LICENSE_GPL,/ *许可证类型* /
  NULL,/ *没有init函数* /
  NULL,/ *没有deinit函数* /
  0x0100,/ *版本= 1.0 * /
  NULL,/ *没有状态变量* /
  NULL,/ *没有系统变量* /
  NULL,/ *没有保留信息* /
  0 / *没有标志* /
}
mysql_declare_plugin_end;

所述 name 构件( auth_simple )指示要用于如在语句中的插件的引用名称 INSTALL PLUGIN UNINSTALL PLUGIN 这也是由 SHOW PLUGINS 显示的名称 INFORMATION_SCHEMA.PLUGINS

auth_simple_handler 通用描述符 成员指向特定于类型的描述符。 对于身份验证插件,特定于类型的描述符是 st_mysql_auth 结构 的实例 (在中定义 plugin_auth.h ):

struct st_mysql_auth
{
  int interface_version;
  const char * client_auth_plugin;
  int(* authenticate_user)(MYSQL_PLUGIN_VIO * vio,MYSQL_SERVER_AUTH_INFO * info);
  int(* generate_authentication_string)(char * outbuf,
      unsigned int * outbuflen,const char * inbuf,unsigned int inbuflen);
  int(* validate_authentication_string)(char * const inbuf,unsigned int buflen);
  int(* set_salt)(const char * password,unsigned int password_len,
                  unsigned char * salt,unsigned char * salt_len);
  const unsigned long authentication_flags;
};

st_mysql_auth 结构具有以下成员:

  • interface_version :始终是特定于类型的API版本号 MYSQL_AUTHENTICATION_INTERFACE_VERSION

  • client_auth_plugin :客户端插件名称

  • authenticate_user :指向与客户端通信的主插件函数的指针

  • generate_authentication_string :指向插件函数的指针,该函数从身份验证字符串生成密码摘要

  • validate_authentication_string :指向验证密码摘要的插件函数的指针

  • set_salt :指向将加扰密码转换为二进制形式的插件函数的指针

  • authentication_flags :一个标志词

client_auth_plugin 如果需要特定的插件, 成员应指明客户端插件的名称。 的值 NULL 的装置 的任何插件。 在后一种情况下,客户端使用的任何插件都可以。 如果服务器插件不关心客户端插件或它发送的用户名或密码,这很有用。 例如,如果服务器插件仅对本地客户端进行身份验证并使用操作系统的某些属性而不是客户端插件发送的信息,则可能会出现这种情况。

对于 auth_simple ,特定于类型的描述符如下所示:

static struct st_mysql_auth auth_simple_handler =
{
  MYSQL_AUTHENTICATION_INTERFACE_VERSION,
  “auth_simple”,/ *需要客户端插件名称* /
  auth_simple_server / *服务器端插件主要功能* /
  generate_auth_string_hash,/ *从密码字符串生成摘要* /
  validate_auth_string_hash,/ *验证密码摘要* /
  set_salt,/ *生成密码盐值* /
  AUTH_FLAG_PRIVILEGED_USER_FOR_PASSWORD_CHANGE
};

main函数 auth_simple_server() 采用两个表示I / O结构和 MYSQL_SERVER_AUTH_INFO 结构的参数。 找到的结构定义 plugin_auth.h 如下所示:

typedef struct st_mysql_server_auth_info
{
  char * user_name;
  unsigned int user_name_length;
  const char * auth_string;
  unsigned long auth_string_length;
  char authenticated_as [MYSQL_USERNAME_LENGTH + 1];
  char external_user [512];
  int password_used;
  const char * host_or_ip;
  unsigned int host_or_ip_length;
MYSQL_SERVER_AUTH_INFO;

字符串成员的字符集是UTF-8。 如果存在 _length 与字符串关联 成员,则表示字符串长度(以字节为单位)。 字符串也以空值终止。

当服务器调用身份验证插件时,它应该 MYSQL_SERVER_AUTH_INFO 如下 解释 结构成员。 其中一些用于在客户端会话中设置SQL函数或系统变量的值,如图所示。

  • user_name :客户端发送的用户名。 该值成为 USER() 函数值。

  • user_name_length user_name 以字节 单位 的长度

  • auth_string 系统表 authentication_string 中行 的值, mysql.user 用于匹配的帐户名称(即,与客户端用户名和主机名匹配的行,以及服务器用于确定如何对客户端进行身份验证的行)。

    假设您使用以下语句创建帐户:

    创建用户'my_user'@'localhost'
      用my_plugin AS'my_auth_string'识别;
    

    my_user 从本地主机连接时,服务器调用 my_plugin ,并传递 'my_auth_string' 到它作为 auth_string 值。

  • auth_string_length auth_string 以字节 单位 的长度

  • authenticated_as :服务器将此设置为用户名(值 user_name )。 插件可以更改它以指示客户端应具有不同用户的权限。 例如,如果插件支持代理用户,则初始值是连接(代理)用户的名称,插件可以将此成员更改为代理用户名。 然后,服务器将代理用户视为具有代理用户的权限(假设满足代理用户支持的其他条件;请参见 第29.2.4.9.4节“在身份验证插件中实现代理用户支持” )。 该值表示为大多数 MYSQL_USER_NAME_LENGTH 字节长 的字符串 加上终止空值。 该值成为 CURRENT_USER() 函数值。

  • external_user :服务器将此设置为空字符串(以null结尾)。 其值成为 external_user 系统变量值。 如果插件希望该系统变量具有不同的值,则应相应地设置此成员(例如,设置连接用户名)。 该值表示为最多511个字节长的字符串,加上终止空值。

  • password_used :此成员在身份验证失败时应用。 该插件可以设置或忽略它。 该值用于构造失败错误消息 Authentication fails. Password used: %s password_used 确定如何 %s 处理,如下表所示。

    password_used %s 处理
    0 没有
    1
    2 将没有 %s
  • host_or_ip :客户端主机的名称(如果可以解析),或者IP地址。

  • host_or_ip_length host_or_ip 以字节 单位 的长度

auth_simple 主要功能, auth_simple_server() ,读取来自客户端的密码(一个空终止字符串)和成功,如果密码不为空(第一个字节不是null):

static int auth_simple_server(MYSQL_PLUGIN_VIO * vio,
                               MYSQL_SERVER_AUTH_INFO * info)
{
  unsigned char * pkt;
  int pkt_len;

  / *将密码读取为以null结尾的字符串,在出错时失败* /
  if((pkt_len = vio-> read_packet(vio,&pkt))<0)
    返回CR_ERROR;

  / *空密码失败* /
  if(!pkt_len || * pkt =='\ 0')
  {
    info-> password_used = PASSWORD_USED_NO;
    返回CR_ERROR;
  }

  / *接受任何非空密码* /
  info-> password_used = PASSWORD_USED_YES;

  返回CR_OK;
}

主函数应返回下表中显示的错误代码之一。

错误代码 含义
CR_OK 成功
CR_OK_HANDSHAKE_COMPLETE 不要将状态包发送回客户端
CR_ERROR 错误
CR_AUTH_USER_CREDENTIALS 验证失败
CR_AUTH_HANDSHAKE 验证握手失败
CR_AUTH_PLUGIN_ERROR 内部插件错误

有关握手如何工作的示例,请参阅 plugin/auth/dialog.c 源文件。

服务器计算Performance Schema host_cache 中的插件错误

auth_simple_server() 是非常基本的,它不使用身份验证信息结构,除了设置指示是否收到密码的成员。

支持代理用户的插件必须向代理服务器返回代理用户的名称(客户端用户应获得其权限的MySQL用户)。 为此,插件必须将 info->authenticated_as 成员设置为代理用户名。 有关代理的信息,请参见 第6.2.18节“代理用户” 第29.2.4.9.4节“在身份验证插件中实现代理用户支持”

generate_authentication_string 插件描述符 成员获取密码并从中生成密码哈希(摘要):

  • 前两个参数是指向输出缓冲区的指针及其最大长度(以字节为单位)。 该函数应将密码哈希写入输出缓冲区,并将长度重置为实际哈希长度。

  • 后两个参数表示密码输入缓冲区及其长度(以字节为单位)。

  • 该函数返回0表示成功,如果发生错误则返回1。

对于 auth_simple 插件,该 generate_auth_string_hash() 函数实现了该 generate_authentication_string 成员。 它只是复制密码,除非它太长而无法放入输出缓冲区。

int generate_auth_string_hash(char * outbuf,unsigned int * buflen,
                              const char * inbuf,unsigned int inbuflen)
{
  / *
    如果服务器指定的缓冲区无法复制到输出缓冲区,则失败
  * /
  if(* buflen <inbuflen)
    返回1; / *错误* /
  strncpy(outbuf,inbuf,inbuflen);
  * buflen = strlen(inbuf);
  返回0; / *成功* /
}

validate_authentication_string 插件描述符 成员验证密码哈希:

  • 参数是指向密码哈希的指针及其长度(以字节为单位)。

  • 该函数返回0表示成功,如果无法验证密码哈希,则返回1。

对于 auth_simple 插件,该 validate_auth_string_hash() 函数实现了该 validate_authentication_string 成员。 它无条件地返回成功:

int validate_auth_string_hash(char * const inbuf __attribute __((unused)),
                              unsigned int buflen __attribute __((unused)))
{
  返回0; / *成功* /
}

set_salt 插件描述符 成员仅由 mysql_native_password 插件使用(请参见 第6.4.1.1节“本机可插入认证” )。 对于其他身份验证插件,您可以使用这个简单的实现:

int set_salt(const char * password __attribute __((unused)),
             unsigned int password_len __attribute __((unused)),
             unsigned char * salt __attribute __((unused)),
             unsigned char * salt_len)
{
  * salt_len = 0;
  返回0; / *成功* /
}

authentication_flags 插件描述符 成员包含影响插件操作的标志。 允许的标志是:

  • AUTH_FLAG_PRIVILEGED_USER_FOR_PASSWORD_CHANGE :凭据更改是特权操作。 如果设置了此标志,则服务器要求用户具有 数据库 的全局 CREATE USER 特权或 UPDATE 特权 mysql

  • AUTH_FLAG_USES_INTERNAL_STORAGE :无论插件使用内部存储(在 authentication_string mysql.user 行)。 如果未设置此标志,则尝试设置密码失败,服务器将发出警告。

29.2.4.9.2编写客户端认证插件

使用 mysql_declare_client_plugin() mysql_end_client_plugin 声明客户端插件描述符 (请参见 第29.2.4.2.3节“客户端插件描述符” )。 对于 auth_simple 插件,描述符如下所示:

mysql_declare_client_plugin(认证)
  “auth_simple”,/ *插件名称* /
  “作者姓名”,/ *作者* /
  “Any-password authentication plugin”,/ * description * /
  {1,0,0},/ *版本= 1.0.0 * /
  “GPL”,/ *许可证类型* /
  NULL,/ *供内部使用* /
  NULL,/ *没有init函数* /
  NULL,/ *没有deinit函数* /
  NULL,/ *没有选项处理函数* /
  auth_simple_client / *主要功能* /
mysql_end_client_plugin;

插件名称到选项处理函数的描述符成员对于所有客户端插件类型都是通用的。 (有关说明,请参见 第29.2.4.2.3节“客户端插件描述符” 。)在公共成员之后,描述符还有一个特定于身份验证插件的附加成员。 这是 主要 功能,它处理与服务器的通信。 该函数接受两个表示I / O结构和连接处理程序的参数。 对于我们简单的any-password插件,main函数除了向服务器写入用户提供的密码外什么都不做:

static int auth_simple_client(MYSQL_PLUGIN_VIO * vio,MYSQL * mysql)
{
  int res;

  / *将密码作为以空字符结尾的字符串发送为明文* /
  res = vio-> write_packet(vio,(const unsigned char *)mysql-> passwd,
                         strlen(mysql-> passwd)+ 1);

  返回资源?CR_ERROR:CR_OK;
}

主函数应返回下表中显示的错误代码之一。

错误代码 含义
CR_OK 成功
CR_OK_HANDSHAKE_COMPLETE 成功,客户完成
CR_ERROR 错误

CR_OK_HANDSHAKE_COMPLETE 表示客户端已成功完成其部分并已读取最后一个数据包。 CR_OK_HANDSHAKE_COMPLETE 如果预先不知道认证协议中的往返次数,则 客户端插件可以返回 ,并且插件必须读取另一个包以确定认证是否结束。

29.2.4.9.3使用身份验证插件

要编译和安装插件库文件,请使用 第29.2.4.3节“编译和安装插件库”中的说明 要使库文件可供使用,请将其安装在plugin目录( plugin_dir 系统变量 指定的目录 )中。

向服务器注册服务器端插件。 例如,要在服务器启动时加载插件,请使用 --plugin-load=auth_simple.so 选项( .so 根据需要 调整 平台 后缀)。

创建服务器将使用该 auth_simple 插件进行身份验证的用户:

mysql> CREATE USER 'x'@'localhost'
    - >IDENTIFIED WITH auth_simple;

使用客户端程序以用户身份连接到服务器 x 服务器端 auth_simple 插件与客户端程序通信它应该使用客户端 auth_simple 插件,后者将密码发送到服务器。 服务器插件应拒绝发送空密码的连接并接受发送非空密码的连接。 每种方式调用客户端程序来验证这一点:

外壳> mysql --user=x --skip-password
ERROR 1045(28000):用户'x'@'localhost'拒绝访问(使用密码:否)

shell> mysql --user=x --password
输入密码:abc
MySQL的>

因为服务器插件接受任何非空密码,所以应该认为它不安全。 在测试插件以验证它是否正常工作后,重新启动服务器而不使用该 --plugin-load 选项,以免不加思索地使服务器运行时加载了不安全的身份验证插件。 此外,删除用户 DROP USER 'x'@'localhost'

有关加载和使用身份验证插件的其他信息,请参见 第5.6.1节“安装和卸载插件” 第6.2.17节“可插入身份验证”

如果您正在编写支持使用身份验证插件的客户端程序,通常这样的程序会通过调用 mysql_options() 来设置 MYSQL_DEFAULT_AUTH MYSQL_PLUGIN_DIR 选项 来加载插件

char * plugin_dir =“ path_to_plugin_dir”;
char * default_auth =“ plugin_name”;

/ * ...进程命令行选项... * /

mysql_options(&mysql,MYSQL_PLUGIN_DIR,plugin_dir);
mysql_options(&mysql,MYSQL_DEFAULT_AUTH,default_auth);

通常,程序还会接受 --plugin-dir --default-auth 允许用户覆盖默认值的选项。

如果客户端程序需要较低级别的插件管理,则客户端库包含带 st_mysql_client_plugin 参数的函数。 请参见 第28.7.17节“C API客户端插件函数”

29.2.4.9.4在认证插件中实现代理用户支持

可插拔身份验证使其成为可能的功能之一是代理用户(请参见 第6.2.18节“代理用户” )。 要使服务器端身份验证插件参与代理用户支持,必须满足以下条件:

  • 当连接客户端应被视为代理用户时,插件必须 authenticated_as MYSQL_SERVER_AUTH_INFO 结构 成员中 返回不同的名称 ,以指示代理用户名。 它还可以选择设置 external_user 成员,以设置 external_user 系统变量 的值

  • 必须将代理用户帐户设置为由插件进行身份验证。 使用 CREATE USER or GRANT 语句将帐户与插件相关联。

  • 代理用户帐户必须具有 PROXY 代理帐户 权限。 使用该 GRANT 语句授予此权限。

换句话说,插件所需的代理用户支持的唯一方面是它设置 authenticated_as 为代理用户名。 其余的是可选的(设置 external_user )或由DBA使用SQL语句完成。

身份验证插件如何确定代理用户连接时要返回的代理用户? 这取决于插件。 通常,插件根据服务器传递给它的身份验证字符串将客户端映射到代理用户。 此字符串来自 语句 AS IDENTIFIED WITH 子句 部分,该 子句 CREATE USER 指定使用插件进行身份验证。

插件开发人员确定身份验证字符串的语法规则,并根据这些规则实现插件。 假设插件采用以逗号分隔的对列表,将外部用户映射到MySQL用户。 例如:

创建用户''''%。example.com'
  使用my_plugin AS'识别'extuser1 = mysqlusera,extuser2 = mysqluserb'
创建用户''''%。example.org'
  使用my_plugin确认AS'extuser1 = mysqluserc,extuser2 = mysqluserd'

当服务器调用插件来验证客户端时,它会将相应的验证字符串传递给插件。 该插件负责:

  1. 将字符串解析为其组件以确定要使用的映射

  2. 将客户端用户名与映射进行比较

  3. 返回正确的MySQL用户名

例如,如果 extuser2 example.com 主机 连接 ,则服务器传递 'extuser1=mysqlusera, extuser2=mysqluserb' 给插件,插件应该复制 mysqluserb authenticated_as 终止空字节。 如果 extuser2 example.org 主机 连接 ,则服务器通过 'extuser1=mysqluserc, extuser2=mysqluserd' ,插件应该复制 mysqluserd

如果映射中没有匹配项,则操作取决于插件。 如果需要匹配,插件可能会返回错误。 或者插件可能只返回客户端名称; 在这种情况下,它不应该更改 authenticated_as ,并且服务器不会将客户端视为代理。

以下示例演示如何使用名为的插件处理代理用户 auth_simple_proxy auth_simple 前面描述 插件一样, auth_simple_proxy 接受任何非空密码作为有效密码(因此不应在生产环境中使用)。 此外,它还检查了 auth_string 身份验证字符串成员,并使用这些非常简单的规则来解释它:

  • 如果字符串为空,则插件返回给定的用户名,不会发生代理。 也就是说,插件 authenticated_as 保持不变 的值

  • 如果字符串是非空的,则插件将其视为代理用户的名称并将其复制到 authenticated_as 以便进行代理。

对于测试,根据前面的规则设置一个未代理的帐户,并设置一个帐户。 这意味着一个帐户没有 AS 子句,一个包含一个 AS 命名代理用户 子句:

创建用户'plugin_user1'@'localhost'
  使用auth_simple_proxy标识;
创建用户'plugin_user2'@'localhost'
  使用auth_simple_proxy识别AS'proxyied_user';

此外,由代理用户创建一个帐户,并授予 plugin_user2 PROXY 特权吧:

创建用户'proxied_user'@'localhost'
  由'proxied_user_pass'识别;
授予代理权
  在'proxied_user'@'localhost'
  TO'plugin_user2'@'localhost';

在服务器调用身份验证插件之前,它会设置 authenticated_as 为客户端用户名。 要指示用户是代理,插件应设置 authenticated_as 为代理用户名。 对于 auth_simple_proxy ,这意味着它必须检查该 auth_string 值,如果该值为非空,则将其复制到该 authenticated_as 成员以将其作为代理用户的名称返回。 此外,当代理发生时,插件将 external_user 成员设置为客户端用户名; 这成为 external_user 系统变量 的值

static int auth_simple_proxy_server(MYSQL_PLUGIN_VIO * vio,
                                     MYSQL_SERVER_AUTH_INFO * info)
{
  unsigned char * pkt;
  int pkt_len;

  / *将密码读取为以null结尾的字符串,在出错时失败* /
  if((pkt_len = vio-> read_packet(vio,&pkt))<0)
    返回CR_ERROR;

  / *空密码失败* /
  if(!pkt_len || * pkt =='\ 0')
  {
    info-> password_used = PASSWORD_USED_NO;
    返回CR_ERROR;
  }

  / *接受任何非空密码* /
  info-> password_used = PASSWORD_USED_YES;

  / *如果认证字符串是非空的,则用作代理用户名* /
  / *并使用客户端名称作为external_user值* /
  if(info-> auth_string_length> 0)
  {
    strcpy(info-> authenticated_as,info-> auth_string);
    strcpy(info-> external_user,info-> user_name);
  }

  返回CR_OK;
}

成功连接后,该 USER() 函数应指示连接的客户端用户和主机名,并 CURRENT_USER() 应指明在会话期间应用其权限的帐户。 如果没有代理发生,则后一个值应该是连接用户帐户;如果代理确实发生,则后者应该是代理帐户。

编译并安装插件,然后测试它。 首先,连接为 plugin_user1

shell> mysql --user=plugin_user1 --password
输入密码:x

在这种情况下,应该没有代理:

MySQL的> SELECT USER(), CURRENT_USER(), @@proxy_user, @@external_user\G
*************************** 1。排******************** *******
         USER():plugin_user1 @ localhost
 CURRENT_USER():plugin_user1 @ localhost
   @@ proxy_user:NULL
@@ external_user:NULL

然后连接为 plugin_user2

shell> mysql --user=plugin_user2 --password
输入密码:x

在这种情况下, plugin_user2 应该代理到 proxied_user

MySQL的> SELECT USER(), CURRENT_USER(), @@proxy_user, @@external_user\G
*************************** 1。排******************** *******
         USER():plugin_user2 @ localhost
 CURRENT_USER():proxied_user @ localhost
   @@ proxy_user:'plugin_user2'@'localhost'
@@ external_user:'plugin_user2'@'localhost'

29.2.4.10编写密码验证插件

本节介绍如何编写服务器端密码验证插件。 这些说明基于 plugin/password_validation MySQL源代码分发目录中 的源代码 validate_password.cc 目录中 源文件实现了名为的插件 validate_password

注意

在MySQL 8.0.4中, validate_password 插件重新实现为 validate_password 组件。 插件形式 validate_password 仍然可用但现已弃用,将在未来的MySQL版本中删除。 使用该插件的MySQL安装应该转换为使用该组件。 请参见 第6.4.3.3节“转换到密码验证组件”

要编写密码验证插件,请在插件源文件中包含以下头文件。 根据插件的功能和要求,可能还需要其他MySQL或通用头文件。

#include <mysql / plugin_validate_password.h>

plugin_validate_password.h 包含 plugin.h ,因此您无需明确包含后一个文件。 plugin.h 定义 MYSQL_VALIDATE_PASSWORD_PLUGIN 服务器插件类型和声明插件所需的数据结构。 plugin_validate_password.h 定义特定于密码验证插件的数据结构。

与任何MySQL服务器插件一样,密码验证插件具有通用插件描述符(请参见 第29.2.4.2.1节“服务器插件库和插件描述符” )。 validate_password.cc ,一般描述符 validate_password 看起来像这样:

mysql_declare_plugin(validate_password)
{
  MYSQL_VALIDATE_PASSWORD_PLUGIN,/ * type * /
  &validate_password_descriptor,/ *描述符* /
  “validate_password”,/ * name * /
  “Oracle Corporation”,/ * author * /
  “检查密码强度”,/ *说明* /
  PLUGIN_LICENSE_GPL,
  validate_password_init,/ * init函数(加载时)* /
  validate_password_deinit,/ * deinit函数(卸载时)* /
  0x0100,/ *版本* /
  空值,
  validate_password_system_variables,/ *系统变量* /
  空值,
  0,
}
mysql_declare_plugin_end;

所述 name 构件( validate_password )指示要用于如在语句中的插件的引用名称 INSTALL PLUGIN UNINSTALL PLUGIN 这也是由 INFORMATION_SCHEMA.PLUGINS 显示的名称 SHOW PLUGINS

通用描述符还指的是 validate_password_system_variables 一个向 SHOW VARIABLES 语句 公开几个系统变量的结构

static struct st_mysql_sys_var * validate_password_system_variables [] = {
  MYSQL_SYSVAR(长度),
  MYSQL_SYSVAR(NUMBER_SHOWN列)
  MYSQL_SYSVAR(mixed_case_count)
  MYSQL_SYSVAR(special_char_count)
  MYSQL_SYSVAR(政策),
  MYSQL_SYSVAR(dictionary_file)
  空值
};

validate_password_init 初始化函数读取字典文件如果指定一个,并且 validate_password_deinit 函数释放与文件关联的数据结构。

validate_password_descriptor 通用描述符中 值指向特定于类型的描述符。 对于密码验证插件,此描述符具有以下结构:

struct st_mysql_validate_password
{
  int interface_version;
  / *
    对于满足密码的密码,此函数返回TRUE
    策略(由插件变量选择)和其他所有的FALSE
    密码
  * /
  int(* validate_password)(mysql_string_handle密码);
  / *
    此函数返回密码强度(0-100)
    根据政策
  * /
  int(* get_password_strength)(mysql_string_handle密码);
};

特定于类型的描述符具有以下成员:

  • interface_version :按照惯例,特定于类型的插件描述符以给定插件类型的接口版本开头。 服务器检查 interface_version 何时加载插件以查看插件是否与其兼容。 对于密码验证插件, interface_version 成员 的值 MYSQL_VALIDATE_PASSWORD_INTERFACE_VERSION (在中定义 plugin_validate_password.h )。

  • validate_password :服务器调用的函数,用于测试密码是否满足当前密码策略。 如果密码正常则返回1,否则返回0。 参数是密码,作为 mysql_string_handle 传递 此数据类型由 mysql_string 服务器服务实现。 有关详细信息,请参阅 string_service.h string_service.cc 源文件 sql 目录。

  • get_password_strength :服务器调用以评估密码强度的函数。 它返回一个从0(弱)到100(强)的值。 参数是密码,作为 mysql_string_handle 传递

对于 validate_password 插件,特定于类型的描述符如下所示:

static struct st_mysql_validate_password validate_password_descriptor =
{
  MYSQL_VALIDATE_PASSWORD_INTERFACE_VERSION,
  validate_password,/ *验证函数* /
  get_password_strength / *验证强度函数* /
};

要编译和安装插件库文件,请使用 第29.2.4.3节“编译和安装插件库”中的说明 要使库文件可供使用,请将其安装在plugin目录( plugin_dir 系统变量 指定的目录 )中。 对于 validate_password 插件,它是从源代码构建MySQL时编译和安装的。 它也包含在二进制分发中。 构建过程生成一个名称为的共享对象库 validate_password.so .so 后缀可能因您的平台而异)。

要在运行时注册插件,请使用此语句( .so 根据需要 调整 平台 后缀):

INSTALL PLUGIN validate_password SONAME'validate_password.so';

有关插件加载的其他信息,请参见 第5.6.1节“安装和卸载插件”

要验证插件安装,请检查 INFORMATION_SCHEMA.PLUGINS 表或使用该 SHOW PLUGINS 语句。 请参见 第5.6.2节“获取服务器插件信息”

虽然 validate_password 已安装的插件,它暴露系统变量指示密码校验参数:

MySQL的> SHOW VARIABLES LIKE 'validate_password%';
+ -------------------------------------- + -------- +
| Variable_name | 价值|
+ -------------------------------------- + -------- +
| validate_password_dictionary_file | |
| validate_password_length | 8 |
| validate_password_mixed_case_count | 1 |
| validate_password_number_count | 1 |
| validate_password_policy | MEDIUM |
| validate_password_special_char_count | 1 |
+ -------------------------------------- + -------- +

有关这些变量的说明,请参见 第6.4.3.2节“密码验证选项和变量”

要在测试后禁用插件,请使用此语句卸载它:

UNINSTALL PLUGIN validate_password;

29.2.4.11编写协议跟踪插件

MySQL支持使用协议跟踪插件:客户端插件,用于实现客户端与使用客户端/服务器协议发生的服务器之间的通信跟踪。

29.2.4.11.1使用测试协议跟踪插件

MySQL包含一个测试协议跟踪插件,用于说明此类插件可用的信息,以及编写其他协议跟踪插件的指南。 要了解测试插件的工作原理,请使用MySQL源代码分发; 二进制发行版是在禁用测试插件的情况下构建的。

通过在 启用 CMake 选项的情况 下配置MySQL来启用测试协议跟踪插件 这会导致构建测试跟踪插件并加载MySQL客户端程序,但默认情况下该插件无效。 使用以下环境变量控制插件: WITH_TEST_TRACE_PLUGIN

  • MYSQL_TEST_TRACE_DEBUG :将此变量设置为0以外的值,以使测试插件生成诊断输出 stderr

  • MYSQL_TEST_TRACE_CRASH :将此变量设置为0以外的值,以使测试插件在检测到无效跟踪事件时中止客户端程序。

警告

来自测试协议跟踪插件的诊断输出可以公开密码和其他敏感信息。

鉴于从启用了测试插件的源代码构建的MySQL安装,您可以看到 mysql 客户端和MySQL服务器 之间的通信跟踪, 如下所示:

shell> export MYSQL_TEST_TRACE_DEBUG=1
shqll>mysql
test_trace:初始化测试跟踪插件
test_trace:在阶段CONNECTING中开始跟踪
test_trace:stage:CONNECTING,event:CONNECTING
test_trace:stage:CONNECTING,event:CONNECTED
test_trace:stage:WAIT_FOR_INIT_PACKET,event:READ_PACKET
test_trace:stage:WAIT_FOR_INIT_PACKET,event:PACKET_RECEIVED
test_trace:收到的数据包:87个字节
  0A 35 2E 37 2E 33 2D 6D 31 33 2D 64 65 62 75 67 .5.7.3-m13-debug
  2D 6C 6F 67 00 04 00 00 00 2B 7C 4F 55 3F 79 67 -log ..... + | OU?yg
test_trace:004:stage:WAIT_FOR_INIT_PACKET,event:INIT_PACKET_RECEIVED
test_trace:004:stage:AUTHENTICATE,event:AUTH_PLUGIN
test_trace:004:使用身份验证插件:mysql_native_password
test_trace:004:stage:AUTHENTICATE,event:SEND_AUTH_RESPONSE
test_trace:004:发送数据包:188字节
  85 A6 7F 00 00 00 00 01 21 00 00 00 00 00 00 00。?......!.......
  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
...
MySQL的> quit
test_trace:008:stage:READY_FOR_COMMAND,event:SEND_COMMAND
test_trace:008:退出
test_trace:008:stage:READY_FOR_COMMAND,event:PACKET_SENT
test_trace:008:发送的数据包:0个字节
test_trace:008:stage:READY_FOR_COMMAND,event:DISCONNECTED
test_trace:008:连接已关闭
test_trace:008:跟踪连接已结束
再见
test_trace:测试跟踪插件已取消初始化

要禁用跟踪输出,请执行以下操作:

外壳> MYSQL_TEST_TRACE_DEBUG=
29.2.4.11.2使用自己的协议跟踪插件
注意

要使用自己的协议跟踪插件,必须在 禁用 CMake 选项的情况下 配置MySQL, 因为一次只能加载一个协议跟踪插件,并且尝试加载第二个插件时会发生错误。 如果您已经在启用了测试协议跟踪插件的情况下构建了MySQL以查看它是如何工作的,那么必须在没有它的情况下重建MySQL,然后才能使用自己的插件。 WITH_TEST_TRACE_PLUGIN

本节讨论如何编写名为的基本协议跟踪插件 simple_trace 此插件提供了一个框架,显示如何设置客户端插件描述符并创建跟踪相关的回调函数。 simple_trace 这些函数中,这些函数是基本的,除了说明所需的参数之外别无他法。 要详细了解跟踪插件如何使用跟踪事件信息,请检查测试协议跟踪插件的源文件( test_trace_plugin.cc libmysql MySQL源代码分发 目录中)。 不过,请注意 st_mysql_client_plugin_TRACE 这里使用的结构与通常的客户端插件声明宏使用的结构不同。 特别是,前两个成员是明确定义的,而不是由声明宏隐式定义的。

几个头文件包含与协议跟踪插件相关的信息:

  • client_plugin.h :定义客户端插件的API。 这包括客户端插件描述符和客户端插件C API调用的函数原型(请参见 第28.7.17节“C API客户端插件函数” )。

  • plugin_trace.h :包含类型的客户端插件的声明 MYSQL_CLIENT_TRACE_PLUGIN 它还包含允许的协议阶段,阶段之间的转换以及每个阶段允许的事件类型的描述。

要编写协议跟踪插件,请在插件源文件中包含以下头文件。 根据插件的功能和要求,可能还需要其他MySQL或通用头文件。

#include <mysql / plugin_trace.h>
#include <mysql.h>

plugin_trace.h 包含 client_plugin.h ,因此您无需明确包含后一个文件。

使用 mysql_declare_client_plugin() mysql_end_client_plugin 声明客户端插件描述符 (请参见 第29.2.4.2.3节“客户端插件描述符” )。 对于 simple_trace 插件,描述符如下所示:

mysql_declare_client_plugin(TRACE)
  “simple_trace”,/ *插件名称* /
  “作者姓名”,/ *作者* /
  “简单协议跟踪插件”,/ * description * /
  {1,0,0},/ *版本= 1.0.0 * /
  “GPL”,/ *许可证类型* /
  NULL,/ *供内部使用* /
  plugin_init,/ *初始化函数* /
  plugin_deinit,/ *取消初始化函数* /
  plugin_options,/ *选项处理函数* /
  trace_start,/ * start-trace function * /
  trace_stop,/ *停止跟踪功能* /
  trace_event / *事件处理函数* /
mysql_end_client_plugin;

插件名称到选项处理函数的描述符成员对于所有客户端插件类型都是通用的。 公共成员之后的成员实现跟踪事件处理。

插件不需要处理的函数成员可以 NULL 在描述符中 声明 ,在这种情况下,您不需要编写任何相应的函数。 为了便于说明和显示参数语法,以下讨论实现了描述符中列出的所有函数,即使其中一些函数不执行任何操作,

所有客户端插件通用的初始化,取消初始化和选项功能声明如下。 有关参数和返回值的说明,请参见 第29.2.4.2.3节“客户端插件描述符”

static int
plugin_init(char * errbuf,size_t errbuf_len,int argc,va_list args)
{
  返回0;
}

static int
plugin_deinit()
{
  返回0;
}

static int
plugin_options(const char *选项,const void * value)
{
  返回0;
}

客户端插件描述符的特定于跟踪的成员是回调函数。 以下描述提供了有关如何使用它们的更多详细信息。 每个都有一个第一个参数,它是一个指向插件实例的指针,以防你的实现需要访问它。

trace_start() :在每个跟踪连接的开始处调用此函数(每个连接在加载插件后启动)。 它通过连接处理程序和跟踪开始的协议阶段。 trace_start() 分配 trace_event() 函数 所需的内存 (如果有),并返回指向它的指针。 如果不需要内存,则返回此函数 NULL

静态无效*
trace_start(struct st_mysql_client_plugin_TRACE * self,
            MYSQL * conn,
            enum protocol_stage阶段)
{
  struct st_trace_data * plugin_data = malloc(sizeof(struct st_trace_data));

  fprintf(stderr,“初始化跟踪:阶段%d \ n”,阶段);
  if(plugin_data)
  {
    memset(plugin_data,0,sizeof(struct st_trace_data));
    fprintf(stderr,“Trace initialized \ n”);
    return plugin_data;
  }
  fprintf(stderr,“无法初始化跟踪\ n”);
  出口(1);
}

trace_stop() :跟踪连接结束时调用此函数。 这通常发生在连接关闭时,但可能更早发生。 例如, trace_event() 可以随时返回非零值,并导致跟踪连接终止。 trace_stop() 然后,即使连接尚未结束,也会调用它。

trace_stop() 传递连接处理程序和指向由 trace_start() NULL 如果没有) 分配的内存的指针 如果指针为非 NULL trace_stop() 则应释放内存。 此函数不返回任何值。

静态无效
trace_stop(struct st_mysql_client_plugin_TRACE * self,
           MYSQL * conn,
           void * plugin_data)
{
  fprintf(stderr,“终止跟踪\ n”);
  if(plugin_data)
    自由(plugin_data);
}

trace_event() :为每个事件发生调用此函数。 它传递一个指向由 trace_start() NULL 如果没有) 分配的内存的指针 ,连接处理程序,当前协议阶段和事件代码以及事件数据。 此函数返回0以继续跟踪,如果跟踪应停止则返回非零值。

static int
trace_event(struct st_mysql_client_plugin_TRACE * self,
            void * plugin_data,
            MYSQL * conn,
            enum protocol_stage阶段,
            枚举trace_event事件,
            struct st_trace_event_args args)
{
  fprintf(stderr,“跟踪事件收到:阶段%d,事件%d \ n”,阶段,事件);
  if(event == TRACE_EVENT_DISCONNECTED)
    fprintf(stderr,“Connection closed \ n”);
  返回0;
}

跟踪框架在连接结束时关闭连接的跟踪,因此 trace_event() 只有在您想要提前终止跟踪连接时才应返回非零值。 假设您只想跟踪某个MySQL帐户的连接。 身份验证后,您可以检查连接的用户名,如果不是您感兴趣的用户,则停止跟踪。

对于每次调用 trace_event() st_trace_event_args 结构都包含事件数据。 它有这个定义:

struct st_trace_event_args
{
  const char * plugin_name;
  int cmd;
  const unsigned char * hdr;
  size_t hdr_len;
  const unsigned char * pkt;
  size_t pkt_len;
};

对于不同的事件类型,该 st_trace_event_args 结构包含以下描述的信息。 所有长度都以字节为单位。 未使用的成员设置为 0 / NULL

AUTH_PLUGIN 事件:

plugin_name插件的名称

SEND_COMMAND 事件:

cmd命令代码
hdr指向命令包头的指针
hdr_len标题的长度
pkt指向命令参数的指针
pkt_len参数的长度

其他 活动: SEND_xxx xxx_RECEIVED

pkt指向发送或接收的数据的指针
pkt_len数据的长度

PACKET_SENT 事件:

pkt_len发送的字节数

要编译和安装插件库文件,请使用 第29.2.4.3节“编译和安装插件库”中的说明 要使库文件可供使用,请将其安装在plugin目录( plugin_dir 系统变量 指定的目录 )中。

在插件库文件编译并安装在插件目录中之后,您可以通过将 LIBMYSQL_PLUGINS 环境变量设置为插件名称来 轻松测试它 ,这会影响使用该变量的任何客户端程序。 mysql 就是这样一个程序:

shell> export LIBMYSQL_PLUGINS=simple_trace
shqll>mysql
初始化跟踪:阶段0
跟踪已初始化
收到跟踪事件:第0阶段,事件1
收到追踪事件:第0阶段,事件2
...
欢迎使用MySQL监视器。命令以;结尾; 或\ g。
收到跟踪事件
收到跟踪事件
...
MySQL的> SELECT 1;
收到跟踪事件:第4阶段,事件12
收到跟踪事件:第4阶段,事件16
...
收到跟踪事件:第8阶段,事件14
收到跟踪事件:第8阶段,事件15
+ --- +
| 1 |
+ --- +
| 1 |
+ --- +
1排(0.00秒)

MySQL的> quit
收到跟踪事件:第4阶段,事件12
收到跟踪事件:第4阶段,事件16
收到跟踪事件:第4阶段,事件3
连接已关闭
终止痕迹
再见

要停止加载跟踪插件,请执行以下操作:

外壳> LIBMYSQL_PLUGINS=

也可以编写直接加载插件的客户端程序。 您可以通过调用 mysql_options() 设置 MYSQL_PLUGIN_DIR 选项 告诉客户端插件目录所在的位置

char * plugin_dir =“ path_to_plugin_dir”;

/ * ...进程命令行选项... * /

mysql_options(&mysql,MYSQL_PLUGIN_DIR,plugin_dir);

通常,程序还会接受一个 --plugin-dir 允许用户覆盖默认值 选项。

如果客户端程序需要较低级别的插件管理,则客户端库包含带 st_mysql_client_plugin 参数的函数。 请参见 第28.7.17节“C API客户端插件函数”

29.2.4.12编写密钥环插件

MySQL服务器支持密钥环服务,该服务使内部服务器组件和插件能够安全地存储敏感信息,以便以后检索。 本节介绍如何编写可由服务功能用于执行密钥管理操作的服务器端密钥环插件。 有关一般密钥环信息,请参见 第6.4.4节“MySQL密钥环”

这里的说明基于 plugin/keyring MySQL源代码分发目录中 的源代码 该目录中的源文件实现了一个名为插件的插件 keyring_file 该插件 使用服务器主机本地的文件进行数据存储。

要编写密钥环插件,请在插件源文件中包含以下头文件。 根据插件的功能和要求,可能还需要其他MySQL或通用头文件。

#include <mysql / plugin_keyring.h>

plugin_keyring.h 包含 plugin.h ,因此您无需明确包含后一个文件。 plugin.h 定义 MYSQL_KEYRING_PLUGIN 服务器插件类型和声明插件所需的数据结构。 plugin_keyring.h 定义特定于密钥环插件的数据结构。

与任何MySQL服务器插件一样,密钥环插件具有通用插件描述符(请参见 第29.2.4.2.1节“服务器插件库和插件描述符” )。 keyring.cc ,一般描述符 keyring_file 看起来像这样:

mysql_declare_plugin(keyring_file)
{
  MYSQL_KEYRING_PLUGIN,/ *类型* /
  &keyring_descriptor,/ *描述符* /
  “keyring_file”,/ * name * /
  “Oracle Corporation”,/ * author * /
  “向/从平面文件存储/获取身份验证数据”,/ * description * /
  PLUGIN_LICENSE_GPL,
  keyring_init,/ * init函数(加载时)* /
  keyring_deinit,/ * deinit函数(卸载时)* /
  0x0100,/ *版本* /
  NULL,/ *状态变量* /
  keyring_system_variables,/ *系统变量* /
  空值,
  0,
}
mysql_declare_plugin_end;

所述 name 构件( keyring_file )表示插件名称。 这是由 INFORMATION_SCHEMA.PLUGINS 显示的名称 SHOW PLUGINS

通用描述符还指的是 keyring_system_variables 一个将系统变量暴露给 SHOW VARIABLES 语句的结构:

static struct st_mysql_sys_var * keyring_system_variables [] = {
  MYSQL_SYSVAR(数据),
  空值
};

keyring_init 初始化函数创建数据文件,如果它不存在,然后读取它并初始化密钥库。 keyring_deinit 函数释放与文件关联的数据结构。

keyring_descriptor 通用描述符中 值指向特定于类型的描述符。 对于密钥环插件,此描述符具有以下结构:

struct st_mysql_keyring
{
  int interface_version;
  bool(* mysql_key_store)(const char * key_id,const char * key_type,
                          const char * user_id,const void * key,size_t key_len);
  bool(* mysql_key_fetch)(const char * key_id,char ** key_type,
                          const char * user_id,void ** key,size_t * key_len);
  bool(* mysql_key_remove)(const char * key_id,const char * user_id);
  bool(* mysql_key_generate)(const char * key_id,const char * key_type,
                             const char * user_id,size_t key_len);
};

特定于类型的描述符具有以下成员:

  • interface_version :按照惯例,特定于类型的插件描述符以给定插件类型的接口版本开头。 服务器检查 interface_version 何时加载插件以查看插件是否与其兼容。 对于密钥环插件, interface_version 成员 的值 MYSQL_KEYRING_INTERFACE_VERSION (在中定义 plugin_keyring.h )。

  • mysql_key_store :在密钥环中对密钥进行模糊处理和存储的函数。

  • mysql_key_fetch :一个从密钥环反混淆和检索密钥的函数。

  • mysql_key_remove :从密钥环中删除密钥的函数。

  • mysql_key_generate :生成新随机密钥并将其存储在密钥环中的函数。

对于 keyring_file 插件,特定于类型的描述符如下所示:

static struct st_mysql_keyring keyring_descriptor =
{
  MYSQL_KEYRING_INTERFACE_VERSION,
  mysql_key_store,
  mysql_key_fetch,
  mysql_key_remove,
  mysql_key_generate
};

密钥环插件实现 功能类似于 密钥环服务API公开 功能。 例如, 插件功能类似于 密钥环服务功能。 有关密钥环服务功能的参数及其使用方法的信息,请参见 第29.3.2节“密钥环服务” mysql_key_xxx my_key_xxx mysql_key_store my_key_store

要编译和安装插件库文件,请使用 第29.2.4.3节“编译和安装插件库”中的说明 要使库文件可供使用,请将其安装在plugin目录( plugin_dir 系统变量 指定的目录 )中。 对于 keyring_file 插件,它是从源代码构建MySQL时编译和安装的。 它也包含在二进制分发中。 构建过程生成一个名称为的共享对象库 keyring_file.so .so 后缀可能因您的平台而异)。

密钥环插件通常在服务器启动过程中尽早加载,以便它们可用于可能依赖于它们的内置插件和存储引擎。 对于 keyring_file ,在服务器 my.cnf 文件中 使用这些行 .so 根据需要 调整 平台 后缀):

的[mysqld]
早期插件负荷= keyring_file.so

有关插件加载的其他信息,请参见 第5.6.1节“安装和卸载插件”

要验证插件安装,请检查 INFORMATION_SCHEMA.PLUGINS 表或使用该 SHOW PLUGINS 语句(请参见 第5.6.2节“获取服务器插件信息” )。 例如:

MySQL的> SELECT PLUGIN_NAME, PLUGIN_STATUS
       FROM INFORMATION_SCHEMA.PLUGINS
       WHERE PLUGIN_NAME LIKE 'keyring%';
+ -------------- + --------------- +
| PLUGIN_NAME | PLUGIN_STATUS |
+ -------------- + --------------- +
| keyring_file | ACTIVE |
+ -------------- + --------------- +

keyring_file 被安装插件,它公开了一个系统变量,表示它使用安全的信息存储器中的数据文件的位置:

MySQL的> SHOW VARIABLES LIKE 'keyring_file%';
+ ------------------- + ----------------------------- ----- +
| Variable_name | 价值|
+ ------------------- + ----------------------------- ----- +
| keyring_file_data | / usr / local / mysql / keyring / keyring |
+ ------------------- + ----------------------------- ----- +

有关 keyring_file_data 变量 的说明 ,请参见 第5.1.8节“服务器系统变量”

要在测试后禁用插件,请重新启动服务器,而不要使用 --early-plugin-load 命名该插件 选项。

29.3用于插件的MySQL服务

MySQL服务器插件可以访问服务器 插件服务。 插件服务接口公开了插件可以调用的服务器功能。 它补充了插件API并具有以下特征:

  • 服务使插件能够使用普通函数调用访问服务器内的代码。 服务也可用于用户定义的函数(UDF)。

  • 服务是可移植的,可在多个平台上运行。

  • 该接口包括版本控制机制,以便可以在加载时针对插件版本检查服务器支持的服务版本。 版本控制可防止服务器提供的服务版本与插件预期或要求的服务版本之间的不兼容性。

  • 有关用于测试插件服务的插件的信息,请参阅MySQL服务器Doxygen文档的插件测试插件服务部分,可从 https://dev.mysql.com/doc/index-other.html获取

插件服务接口与插件API的不同之处如下:

  • 插件API允许服务器使用插件。 调用主动权在于服务器调用插件。 这使插件能够扩展服务器功能或注册以接收有关服务器处理的通知。

  • 插件服务接口使插件能够调用服务器内的代码。 调用的主动权在于调用服务函数的插件。 这使得许多插件可以使用已在服务器中实现的功能; 他们不需要自己单独实施。

要确定存在哪些服务以及它们提供的功能,请查看 include/mysql MySQL源代码分发 目录。 相关文件是:

  • plugin.h 包括 services.h ,这是 标题,包括所有可用的服务特定标头文件。

  • 特定于服务的标头具有表单的名称 service_xxx.h

每个特定于服务的标头应包含提供给定服务的完整使用文档的注释,包括可用的服务功能,调用顺序和返回值。

对于希望修改服务器以添加新服务的开发人员,请参阅 MySQL Internals:MySQL Services for Plugins

可用服务包括以下内容:

  • get_sysvar_source :一种服务,使插件能够检索系统变量设置的来源。

  • locking_service :实现具有三个属性的锁的服务:锁定命名空间,锁定名称和锁定模式。 此锁定接口有两个级别:1)作为C语言接口,可从服务器插件或用户定义的函数调用为插件服务; 2)在SQL级别,作为一组用户定义的函数映射到服务例程的调用。 有关更多信息,请参见 第29.3.1节“锁定服务”

  • my_plugin_log_service :一种服务,使插件能够报告错误并指定错误消息。 服务器将消息写入其错误日志。

  • status_variable_registration 用于注册状态变量的服务。

  • my_thd_scheduler :用于插件的服务,用于选择线程调度程序。

  • mysql_keyring :密钥环存储服务。 有关更多信息,请参见 第29.3.2节“密钥环服务”

  • mysql_password_policy :用于密码验证和强度检查的服务。

  • plugin_registry_service :MySQL Server包括一个基于组件的基础架构,用于提高服务器的可扩展性; 请参见 第5.5节“MySQL服务器组件” 但是,MySQL插件使用的是一个早于组件接口的接口。 plugin_registry_service 使插件来访问组件注册和服务。

  • security_context :一种服务,使插件能够检查或操作线程安全上下文。 此服务提供setter和getter例程来访问服务器 Security_context 类的属性,其中包括操作系统用户和主机,经过身份验证的用户和主机以及客户端IP地址等属性。

  • thd_alloc :内存分配服务。

  • thd_wait :插件服务,用于报告何时进入睡眠或停止状态。

本节的其余部分描述了插件如何使用可用作服务的服务器功能。 另请参阅 使用该 服务 守护程序 示例插件 的源代码 my_snprintf 在MySQL源代码发行版中,该插件位于 plugin/daemon_example 目录中。

要在插件中使用服务或服务,插件源文件必须包含 plugin.h 头文件才能访问与服务相关的信息:

#include <mysql / plugin.h>

这并不代表任何额外的设置成本。 插件必须包含该文件,因为它包含每个插件所需的定义和结构。

要访问服务,插件会像其他任何函数一样调用服务函数。

要报告服务器将向其写入错误日志的错误,请首先选择错误级别。 mysql/service_my_plugin_log.h 定义这些级别:

枚举plugin_log_level
{
  MY_ERROR_LEVEL,
  MY_WARNING_LEVEL,
  MY_INFORMATION_LEVEL
};

然后调用 my_plugin_log_message()

int my_plugin_log_message(MYSQL_PLUGIN *插件,枚举plugin_log_level级别,
                          const char * format,...);

例如:

my_plugin_log_message(plugin_ptr,MY_ERROR_LEVEL,“无法初始化插件”);

有些服务 插件,可以提供 通过 插件,因此只有当提供服务的插件加载可用。 任何使用此类服务​​的MySQL组件都应检查该服务是否可用。

构建插件时,请使用 -lmysqlservices 链接时 标志链接 libmysqlservices 库中。 例如,对于 CMake ,将其放在顶级 CMakeLists.txt 文件中:

FIND_LIBRARY(MYSQLSERVICES_LIB mysqlservices
 路径“$ {MYSQL_SRCDIR} / libservices”NO_DEFAULT_PATH)

把它放在 CMakeLists.txt 包含插件源的目录 中的 文件中:

#plugin需要mysql服务库来进行错误记录
TARGET_LINK_LIBRARIES(your_plugin_library_name$ {MYSQLSERVICES_LIB})

29.3.1锁定服务

MySQL发行版提供了一个锁定接口,可在两个级别上使用:

  • 作为C语言接口,可以从服务器插件或用户定义的函数调用为插件服务

  • 在SQL级别,作为一组用户定义的函数映射到服务例程的调用

有关插件服务的一般信息,请参见 第29.3节“插件的MySQL服务” 有关用户定义函数的一般信息,请参见 第29.4.2节“添加新的用户定义函数”

锁定界面具有以下特征:

  • 锁具有三个属性:锁定命名空间,锁定名称和锁定模式:

    • 锁由名称空间和锁名称的组合标识。 命名空间允许不同的应用程序使用相同的锁名称,而不会在单独的命名空间中创建锁定而发生冲突。 例如,如果应用程序A和B使用的名称空间 ns1 ns2 ,分别,每个应用可以使用锁的名称 lock1 lock2 不与其它应用干扰。

    • 锁定模式是读取或写入。 读锁是共享的:如果会话对给定的锁标识符具有读锁定,则其他会话可以获取对同一标识符的读锁定。 写锁是独占的:如果会话对给定的锁标识符具有写锁定,则其他会话无法获取对同一标识符的读或写锁。

  • 名称空间和锁定名称必须为非 NULL 空, 空,并且最大长度为64个字符。 指定为 NULL 空字符串 的命名空间或锁定名称 或长度超过64个字符的字符串会导致 ER_LOCKING_SERVICE_WRONG_NAME 错误。

  • 锁定接口将命名空间和锁定名称视为二进制字符串,因此比较区分大小写。

  • 锁定接口提供获取锁定和释放锁定的功能。 调用这些函数不需要特殊权限。 权限检查是调用应用程序的责任。

  • 如果没有立即可用,可以等待锁。 锁定获取调用采用整数超时值,该值指示在放弃之前等待获取锁的秒数。 如果在未成功锁定获取的情况下达到超时, ER_LOCKING_SERVICE_TIMEOUT 则会发生错误。 如果超时为0,则没有等待,如果无法立即获取锁,则调用会产生错误。

  • 锁定接口检测不同会话中的锁定获取调用之间的死锁。 在这种情况下,锁定服务选择一个调用者并终止其锁定获取请求并显示 ER_LOCKING_SERVICE_DEADLOCK 错误。 此错误不会导致事务回滚。 要在遇到死锁的情况下选择会话,锁定服务会优先选择在持有写锁定的会话上保持读锁定的会话。

  • 会话可以通过单个锁定获取调用获取多个锁。 对于给定的调用,锁获取是原子的:如果获取了所有锁,则调用成功。 如果获取任何锁失败,则调用不会获取锁定并失败,通常会 出现错误 ER_LOCKING_SERVICE_TIMEOUT ER_LOCKING_SERVICE_DEADLOCK 错误。

  • 会话可以为相同的锁标识符(名称空间和锁名称组合)获取多个锁。 这些锁实例可以是读锁,写锁或两者的混合。

  • 通过调用释放锁定函数显式释放会话中获​​取的锁定,或者在会话终止(正常或异常)时隐式释放。 事务提交或回滚时不会释放锁。

  • 在会话中,释放时给定命名空间的所有锁定一起释放。

锁定服务提供的接口 GET_LOCK() 与SQL函数 提供的接口不同 (参见 第12.14节“锁定函数” )。 例如, GET_LOCK() 不实现名称空间并仅提供排它锁,而不提供不同的读写锁。

29.3.1.1锁定服务C接口

本节介绍如何使用锁定服务C语言接口。 要使用UDF接口,请参见 第29.3.1.2节“锁定服务UDF接口” 有关锁定服务接口的一般特性,请参见 第29.3.1节“锁定服务” 有关插件服务的一般信息,请参见 第29.3节“插件的MySQL服务”

使用锁定服务的源文件应包含此头文件:

#include <mysql / service_locking.h>

要获取一个或多个锁,请调用此函数:

int mysql_acquire_locking_service_locks(MYSQL_THD opaque_thd,
                                        const char * lock_namespace,
                                        const char ** lock_names,
                                        size_t lock_num,
                                        enum enum_locking_service_lock_type lock_type,
                                        unsigned long lock_timeout);

这些论点具有以下含义:

  • opaque_thd :一个线程句柄。 如果指定为 NULL ,则使用当前线程的句柄。

  • lock_namespace :以空字符结尾的字符串,表示锁定命名空间。

  • lock_names :一组以null结尾的字符串,提供要获取的锁的名称。

  • lock_num lock_names 数组中 的名称数

  • lock_type :锁定模式,可以是 LOCKING_SERVICE_READ LOCKING_SERVICE_WRITE 获取读锁定或写分别锁,。

  • lock_timeout :在放弃之前等待获取锁的整数秒数。

要释放为给定命名空间获取的锁,请调用此函数:

int mysql_release_locking_service_locks(MYSQL_THD opaque_thd,
                                        const char * lock_namespace);

这些论点具有以下含义:

  • opaque_thd :一个线程句柄。 如果指定为 NULL ,则使用当前线程的句柄。

  • lock_namespace :以空字符结尾的字符串,表示锁定命名空间。

可以使用性能模式在SQL级别监视锁定服务获取或等待的锁定。 有关详细信息,请参见 第29.3.1.2.3节“锁定服务监视”

29.3.1.2锁定服务UDF接口

本节介绍如何使用锁定服务用户定义函数(UDF)接口。 要使用C语言接口,请参见 第29.3.1.1节“锁定服务C接口” 有关锁定服务接口的一般特性,请参见 第29.3.1节“锁定服务” 有关用户定义函数的一般信息,请参见 第29.4.2节“添加新的用户定义函数”

29.3.1.2.1安装或卸载UDF锁定接口

无需安装 第29.3.1.1节“锁定服务C接口”中 描述的锁定服务例程 因为它们内置于服务器中。 映射到服务例程调用的用户定义函数(UDF)也是如此:必须在使用前安装UDF。 本节介绍如何执行此操作。 有关UDF安装的一般信息,请参见 第5.7.1节“安装和卸载用户定义的函数”

锁定服务UDF在位于由 plugin_dir 系统变量 命名的目录中的插件库文件中实现 文件基名是 locking_service 文件名后缀因平台 .so 而异 (例如, 对于Unix和类Unix系统, .dll 对于Windows)。

要安装锁定服务UDF,请使用该 CREATE FUNCTION 语句( .so 根据需要 调整 平台 后缀):

CREATE FUNCTION service_get_read_locks RETURNS INT
  SONAME'locking_service.so';
CREATE FUNCTION service_get_write_locks RETURNS INT
  SONAME'locking_service.so';
CREATE FUNCTION service_release_locks RETURNS INT
  SONAME'locking_service.so';

如果UDF在主复制服务器上使用,请将它们安装在所有从属服务器上,以避免复制问题。

安装后,UDF将保持安装状态,直到卸载为止。 要删除它们,请使用以下 DROP FUNCTION 语句:

DROP FUNCTION service_get_read_locks;
DROP FUNCTION service_get_write_locks;
DROP FUNCTION service_release_locks;
29.3.1.2.2使用UDF锁定接口

在使用锁定服务UDF之前,请按照 第29.3.1.2.1节“安装或卸载UDF锁定接口”中 提供的说明进行 安装

要获取一个或多个读锁定,请调用此函数:

MySQL的> SELECT service_get_read_locks('mynamespace', 'rlock1', 'rlock2', 10);
+ ------------------------------------------------- -------------- +
| service_get_read_locks('mynamespace','rlock1','rlock2',10)|
+ ------------------------------------------------- -------------- +
| 1 |
+ ------------------------------------------------- -------------- +

第一个参数是lock名称空间。 最后一个参数是一个整数超时,指示在放弃之前等待获取锁的秒数。 中间的参数是锁名称。

对于刚刚所示的示例中,函数获取与锁标识符锁 (mynamespace, rlock1) (mynamespace, rlock2)

要获取写锁而不是读锁,请调用此函数:

MySQL的> SELECT service_get_write_locks('mynamespace', 'wlock1', 'wlock2', 10);
+ ------------------------------------------------- --------------- +
| service_get_write_locks('mynamespace','wlock1','wlock2',10)|
+ ------------------------------------------------- --------------- +
| 1 |
+ ------------------------------------------------- --------------- +

在这种情况下,锁标识符是 (mynamespace, wlock1) (mynamespace, wlock2)

要释放命名空间的所有锁,请使用以下函数:

MySQL的> SELECT service_release_locks('mynamespace');
+ -------------------------------------- +
| service_release_locks('mynamespace')|
+ -------------------------------------- +
| 1 |
+ -------------------------------------- +

每个锁定函数返回非零表示成功。 如果函数失败,则会发生错误。 例如,出现以下错误,因为锁名称不能为空:

MySQL的> SELECT service_get_read_locks('mynamespace', '', 10);
错误3131(42000):锁定服务锁定名称''不正确。

会话可以获取相同锁定标识符的多个锁。 只要不同的会话没有标识符的写锁定,该会话就可以获取任意数量的读或写锁。 对标识符的每个锁定请求获取新锁。 以下语句获取具有相同标识符的三个写锁,然后获取相同标识符的三个读锁:

SELECT service_get_write_locks('ns','lock1','lock1','lock1',0);
SELECT service_get_read_locks('ns','lock1','lock1','lock1',0);

如果此时检查Performance Schema metadata_locks 表,您会发现该会话包含六个具有相同 (ns, lock1) 标识符的 不同锁 (有关详细信息,请参见 第29.3.1.2.3节“锁定服务监视” 。)

因为会话至少保持一个写锁定 (ns, lock1) ,所以没有其他会话可以获取锁定,无论是读取还是写入。 如果会话仅保留标识符的读锁定,则其他会话可以为其获取读锁定,但不能获取写锁定。

单个锁定获取调用的锁定是以原子方式获取的,但原子性不适用于调用。 因此,对于如下的陈述,其中 service_get_write_locks() 每行调用一次结果集,每个单独的调用都保持原子性,但不是整个语句的原子性:

SELECT service_get_write_locks('ns','lock1','lock2',0)FROM t1 WHERE ...;
警告

因为锁定服务为给定锁定标识符的每个成功请求返回单独的锁,所以单个语句可能获取大量锁。 例如:

INSERT INTO ... SELECT service_get_write_locks('ns',t1.col_name,0)FROM t1;

这些类型的陈述可能会产生某些不利影响。 例如,如果语句部分失败并回滚,则仍然存在获取到失败点的锁。 如果意图是在插入的行和获取的锁之间存在对应关系,则不会满足该意图。 此外,如果按特定顺序授予锁定很重要,请注意结果集顺序可能会有所不同,具体取决于优化程序选择的执行计划。 由于这些原因,最好将应用程序限制为每个语句的单个锁定获取调用。

29.3.1.2.3锁定服务监控

锁定服务是使用MySQL Server元数据锁框架实现的,因此您可以通过检查Performance Schema metadata_locks 来监视获取或等待的锁定服务锁

首先,启用元数据锁定工具:

mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES'
    - >WHERE NAME = 'wait/lock/metadata/sql/mdl';

然后获取一些锁并检查 metadata_locks 的内容

MySQL的> SELECT service_get_write_locks('mynamespace', 'lock1', 0);
+ ------------------------------------------------- --- +
| service_get_write_locks('mynamespace','lock1',0)|
+ ------------------------------------------------- --- +
| 1 |
+ ------------------------------------------------- --- +
MySQL的> SELECT service_get_read_locks('mynamespace', 'lock2', 0);
+ ------------------------------------------------- -  +
| service_get_read_locks('mynamespace','lock2',0)|
+ ------------------------------------------------- -  +
| 1 |
+ ------------------------------------------------- -  +
mysql> SELECT OBJECT_TYPE, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS
    - > FROM performance_schema.metadata_locks
    - >WHERE OBJECT_TYPE = 'LOCKING SERVICE'\G
*************************** 1。排******************** *******
  OBJECT_TYPE:锁定服务
OBJECT_SCHEMA:mynamespace
  OBJECT_NAME:lock1
    LOCK_TYPE:EXCLUSIVE
  LOCK_STATUS:已授权
*************************** 2.排******************** *******
  OBJECT_TYPE:锁定服务
OBJECT_SCHEMA:mynamespace
  OBJECT_NAME:lock2
    LOCK_TYPE:共享
  LOCK_STATUS:已授权

锁定服务锁的 OBJECT_TYPE 值为 LOCKING SERVICE 这是不同于,例如,与所获取的锁 GET_LOCK() 功能,其具有 OBJECT_TYPE USER LEVEL LOCK

锁命名空间,名称和模式出现在 OBJECT_SCHEMA OBJECT_NAME LOCK_TYPE 列。 读写锁的 LOCK_TYPE 值为 SHARED EXCLUSIVE 分别

对于 正在等待的 锁, LOCK_STATUS GRANTED 用于获取的锁 PENDING 您将看到 PENDING 一个会话是否持有写锁定,另一个会话是否正在尝试获取具有相同标识符的锁定。

29.3.1.2.4锁定服务UDF接口参考

锁定服务的SQL接口实现本节中描述的用户定义的函数。 有关用法示例,请参见 第29.3.1.2.2节“使用UDF锁定接口”

这些功能具有以下特点:

  • 返回值非零表示成功。 否则,会发生错误。

  • 名称空间和锁定名称必须为非 NULL 空, 空,并且最大长度为64个字符。

  • 超时值必须是整数,表示在放弃错误之前等待获取锁的秒数。 如果超时为0,则没有等待,如果无法立即获取锁,则该函数会产生错误。

这些锁定服务UDF可用:

  • service_get_read_locks(namespace, lock_name[, lock_name] ..., timeout)

    使用给定的锁名称在给定的命名空间中获取一个或多个读(共享)锁,如果在给定的超时值内未获取锁,则超时错误。

  • service_get_write_locks(namespace, lock_name[, lock_name] ..., timeout)

    使用给定的锁名称在给定的命名空间中获取一个或多个写(独占)锁,如果在给定的超时值内未获取锁,则超时错误。

  • service_release_locks(namespace)

    对于给定的命名空间,使用 service_get_read_locks() 释放在当前会话中获取的所有锁 service_get_write_locks()

    命名空间中没有锁定不是错误。

29.3.2密钥环服务

MySQL服务器支持密钥环服务,该服务使内部服务器组件和插件能够安全地存储敏感信息,以便以后检索。 本节介绍如何使用密钥环服务功能来存储,检索和删除MySQL密钥环密钥库中的密钥。 密钥环服务功能的SQL接口也可用作一组用户定义的函数(UDF); 请参见 第6.4.4.8节“通用密钥环密钥管理功能” 有关一般密钥环信息,请参见 第6.4.4节“MySQL密钥环”

密钥环服务使用启用的任何底层密钥环插件(如果有)。 如果未启用密钥环插件,则密钥环服务调用将失败。

记录 在密钥库中包含的数据(密钥本身)和一个唯一的标识符,通过该键被访问。 标识符有两部分:

  • key_id :密钥ID或名称。 MySQL Server保留以 key_id 值开头的值 mysql_

  • user_id :会话有效用户ID。 如果没有用户上下文,则此值可以是 NULL 该值实际上不必是 用户 ; 意义取决于应用程序。

    实现密钥环UDF接口的函数将值 CURRENT_USER() 作为 user_id 值传递给密钥环服务函数。

密钥环服务功能具有以下共同特征:

  • 每个函数返回0表示成功,1表示失败。

  • key_id user_id 参数形成指示要使用哪个键在钥匙圈一个独特的组合。

  • key_type 参数提供有关密钥的其他信息,例如其加密方法或预期用途。

  • 密钥环服务功能将密钥ID,用户名,类型和值视为二进制字符串,因此比较区分大小写。 例如,ID MyKey mykey 引用不同的键。

这些密钥环服务功能可用:

  • my_key_fetch()

    从密钥环中反混淆和检索密钥及其类型。 该函数为用于存储返回的键和键类型的缓冲区分配内存。 当不再需要内存时,调用者应该对内存进行归零或混淆,然后释放它。

    句法:

    bool my_key_fetch(const char * key_id,const char ** key_type,
                      const char * user_id,void ** key,size_t * key_len)
    

    参数:

    • key_id user_id :以空值终止的字符串,作为一对形成唯一标识符,指示要获取的键。

    • key_type :缓冲区指针的地址。 该函数在其中存储一个指向以null结尾的字符串的指针,该字符串提供有关密钥的附加信息(在添加密钥时存储)。

    • key :缓冲区指针的地址。 该函数在其中存储指向包含所获取的密钥数据的缓冲区的指针。

    • key_len :函数的地址,函数将 *key 缓冲区 的大小(以字节为单位)存储到该变量中

    返回值:

    成功返回0,失败返回1。

  • my_key_generate()

    生成给定类型和长度的新随机密钥,并将其存储在密钥环中。 密钥具有长度 key_len 并且与由 key_id 形成的标识符相关联 user_id 类型和长度值必须与底层密钥环插件支持的值一致。 请参见 第6.4.4.7节“支持的密钥环密钥类型”

    句法:

    bool my_key_generate(const char * key_id,const char * key_type,
                         const char * user_id,size_t key_len)
    

    参数:

    • key_id user_id :以空值终止的字符串,它们成对形成要生成的密钥的唯一标识符。

    • key_type :以null结尾的字符串,提供有关密钥的其他信息。

    • key_len :要生成的密钥的大小(以字节为单位)。

    返回值:

    成功返回0,失败返回1。

  • my_key_remove()

    从钥匙圈中取出钥匙。

    句法:

    bool my_key_remove(const char * key_id,const char * user_id)
    

    参数:

    • key_id user_id :以空值终止的字符串,作为一对形成要删除的键的唯一标识符。

    返回值:

    成功返回0,失败返回1。

  • my_key_store()

    在密钥环中对密钥进行模糊处理和存储。

    句法:

    bool my_key_store(const char * key_id,const char * key_type,
                      const char * user_id,void * key,size_t key_len)
    

    参数:

    • key_id user_id :以空值终止的字符串,作为一对形成要存储的密钥的唯一标识符。

    • key_type :以null结尾的字符串,提供有关密钥的其他信息。

    • key :包含要存储的密钥数据的缓冲区。

    • key_len key 缓冲区 的大小(以字节为单位)

    返回值:

    成功返回0,失败返回1。

29.4向MySQL添加新功能

有三种方法可以向MySQL添加新功能:

创建编译函数的每种方法都有优点和缺点:

  • 如果编写用户定义的函数,则必须安装除服务器本身之外的目标文件。 如果将函数编译到服务器中,则无需执行此操作。

  • 本机功能要求您修改源分发。 UDF没有。 您可以将UDF添加到二进制MySQL发行版中。 无需访问MySQL源代码。

  • 如果升级MySQL发行版,则可以继续使用以前安装的UDF,除非升级到UDF接口更改的较新版本。 对于本机功能,每次升级时都必须重复修改。

无论您使用哪种方法添加新函数,都可以在SQL语句中调用它们,就像本机函数(如 ABS() 或)一样 SOUNDEX()

有关 描述服务器如何解释对不同类型函数的引用的规则 请参见 第9.2.4节“函数名称解析和解析”

以下部分描述了UDF接口的功能,提供了编写UDF的说明,讨论了MySQL为防止UDF滥用而采取的安全预防措施,并描述了如何添加本机MySQL函数。

例如,说明如何编写UDF的源代码,请查看 sql/udf_example.cc MySQL源代码分发中提供 文件。

注意

MySQL源代码包含使用Doxygen编写的内部文档。 本文档对于从开发人员的角度理解MySQL的工作原理非常有用。 生成的Doxygen内容可从 https://dev.mysql.com/doc/index-other.html获得 也可以使用 第2.9.7节“生成MySQL Doxygen文档内容”中 的说明从MySQL源代码分发本地生成此内容

29.4.1用户自定义功能接口的功能

用户定义函数的MySQL接口提供以下特性和功能:

  • 函数可以返回字符串,整数或实数值,并且可以接受相同类型的参数。

  • 您可以定义一次对单个行进行操作的简单函数,或者对按行组进行操作的聚合函数。

  • 向函数提供信息,使其能够检查传递给它们的参数的数量,类型和名称。

  • 在将它们传递给函数之前,您可以告诉MySQL强制给定类型的参数。

  • 您可以指示函数返回 NULL 或发生错误。

29.4.2添加新的用户定义函数

要使UDF机制起作用,必须使用C或C ++编写函数,并且操作系统必须支持动态加载。 MySQL源代码分发包含一个 sql/udf_example.cc 定义五个UDF函数 的文件 请参阅此文件以了解UDF调用约定的工作原理。 include/mysql_com.h 头文件定义UDF相关的符号和数据结构,虽然你不必直接包含这个头文件; 它包括在内 mysql.h

UDF包含成为正在运行的服务器的一部分的代码,因此在编写UDF时,您将受到适用于编写服务器代码的任何和所有约束的约束。 例如,如果尝试使用 libstdc++ 库中的 函数,则可能会出现问题 这些约束可能会在服务器的未来版本中发生变化,因此服务器升级可能需要对最初为旧服务器编写的UDF进行修订。 有关这些约束的信息,请参见 第2.9.4节“MySQL源配置选项” 第2.9.5节“处理编译MySQL的问题”

为了能够使用UDF,您必须 动态 链接 mysqld 如果你想使用一个需要从访问符号的UDF 的mysqld (例如, metaphone 在功能 sql/udf_example.cc 用途 default_charset_info ),必须链接与程序 -rdynamic (见 man dlopen )。

对于要在SQL语句中使用的每个函数,应定义相应的C(或C ++)函数。 在以下讨论中,名称 xxx 用于示例函数名称。 要区分SQL和C / C ++用法, XXX() (大写)表示SQL函数调用, xxx() (小写)表示C / C ++函数调用。

注意

使用C ++时,您可以将C函数封装在:

extern“C”{...}

这可确保您的C ++函数名称在已完成的UDF中保持可读性。

以下列表描述了为实现名为的函数实现接口而编写的C / C ++函数 XXX() 主要功能 xxx() 是必需的。 此外,由于 第29.4.2.6节“UDF安全预防措施”中 讨论的原因,UDF至少需要此处描述的其他功能之一

  • xxx()

    主要功能。 这是计算函数结果的地方。 此处显示SQL函数数据类型与C / C ++函数的返回类型之间的对应关系。

    SQL类型 C / C ++类型
    STRING char *
    INTEGER long long
    REAL double

    也可以声明一个 DECIMAL 函数,但是当前该值是作为字符串返回的,因此您应该将UDF编写为 STRING 函数。 ROW 功能未实现。

  • xxx_init()

    初始化函数 xxx() 如果存在,它可用于以下目的:

    • 检查参数的数量 XXX()

    • 验证参数是否为必需类型,或者在调用main函数时告诉MySQL强制所需类型的参数。

    • 分配主函数所需的任何内存。

    • 指定结果的最大长度。

    • 指定(对于 REAL 函数)结果中的最大小数位数。

    • 指定结果是否可以 NULL

  • xxx_deinit()

    取消初始化函数 xxx() 如果存在,它应该释放由初始化函数分配的任何内存。

当一个SQL语句调用时 XXX() ,MySQL调用初始化函数 xxx_init() 让它执行任何所需的设置,例如参数检查或内存分配。 如果 xxx_init() 返回错误,MySQL将使用错误消息中止SQL语句,并且不调用main或deinitialization函数。 否则,MySQL调用main函数 xxx() 为每一行 一次 在处理完所有行之后,MySQL调用deinitialization函数, xxx_deinit() 以便它可以执行任何所需的清理。

对于类似的聚合函数 SUM() ,还必须提供以下函数:

  • xxx_clear()

    重置当前聚合值但不要将参数作为新组的初始聚合值插入。

  • xxx_add()

    将参数添加到当前聚合值。

MySQL处理聚合UDF,如下所示:

  1. 调用 xxx_init() 让聚合函数分配存储结果所需的任何内存。

  2. 根据 GROUP BY 表达式 对表进行排序

  3. 调用 xxx_clear() 每个新组中的第一行。

  4. 调用 xxx_add() 属于同一组的每一行。

  5. 调用 xxx() 以在组更改时或在处理完最后一行之后获取聚合的结果。

  6. 重复步骤3到5,直到处理完所有行

  7. 调用 xxx_deinit() 让UDF释放它已分配的任何内存。

所有功能必须是线程安全的。 这不仅包括main函数,还包括初始化和取消初始化函数,以及聚合函数所需的附加函数。 此要求的结果是您不允许分配任何更改的全局变量或静态变量! 如果你需要内存,你应该分配它 xxx_init() 并释放它 xxx_deinit()

29.4.2.1简单函数的UDF调用序列

本节介绍创建简单UDF时需要定义的不同功能。 第29.4.2节“添加新的用户定义函数” 描述了MySQL调用这些函数的顺序。

主要 xxx() 功能应该如本节所示声明。 请注意,返回类型和参数不同,这取决于你是否声明SQL函数 XXX() 返回 STRING INTEGER REAL CREATE FUNCTION 语句:

对于 STRING 功能:

char * xxx(UDF_INIT * initid,UDF_ARGS * args,
          char * result,unsigned long * length,
          char * is_null,char * error);

对于 INTEGER 功能:

long long xxx(UDF_INIT * initid,UDF_ARGS * args,
              char * is_null,char * error);

对于 REAL 功能:

double xxx(UDF_INIT * initid,UDF_ARGS * args,
              char * is_null,char * error);

DECIMAL 函数返回字符串值,应该以与 STRING 函数 相同的方式声明 ROW 功能未实现。

初始化和取消初始化函数声明如下:

bool xxx_init(UDF_INIT * initid,UDF_ARGS * args,char * message);

void xxx_deinit(UDF_INIT * initid);

initid 参数传递给所有三个函数。 它指向 UDF_INIT 用于在函数之间传递信息 结构。 UDF_INIT 结构成员遵循。 初始化函数应填写它希望更改的任何成员。 (要使用成员的默认值,请保持不变。)

  • bool maybe_null

    xxx_init() 应该设置 maybe_null 1 if是否 xxx() 可以返回 NULL 1 如果声明了任何参数,则 默认值为 maybe_null

  • unsigned int decimals

    小数点右侧的小数位数。 默认值是传递给main函数的参数中的最大小数位数。 例如,如果函数传递 1.34 1.345 1.3 ,默认是3,因为 1.345 有3个十进制数字。

    对于具有小数的没有固定数量的参数,所述 decimals 值被设置为31,这比允许的小数的最大数量更多的 DECIMAL FLOAT DOUBLE 数据类型。 该值可作为恒定 NOT_FIXED_DEC mysql_com.h 头文件。

    decimals 的31值用于在箱子参数诸如 FLOAT DOUBLE 柱无小数的明确声明数(例如, FLOAT 而不是 FLOAT(10,3) )和浮点常数如 1345E-3 它还用于字符串和其他非编号参数,这些参数可能在函数内转换为数字形式。

    到的值 decimals 构件被初始化仅仅是一个缺省值。 可以在函数内更改它以反映执行的实际计算。 确定默认值,以便使用参数的最大小数位数。 如果小数位数 NOT_FIXED_DEC 甚至是其中一个参数,那就是用于的值 decimals

  • unsigned int max_length

    结果的最大长度。 默认 max_length 值因功能的结果类型而异。 对于字符串函数,缺省值是最长参数的长度。 对于整数函数,默认值为21位。 对于实函数,默认值为13加上指示的小数位数 initid->decimals (对于数字函数,长度包括任何符号或小数点字符。)

    如果要返回blob值,可以设置 max_length 为65KB或16MB。 此内存未分配,但该值用于决定在需要临时存储数据时使用哪种数据类型。

  • char *ptr

    函数可以用于其自身目的的指针。 例如,函数可用于 initid->ptr 在它们之间传递分配的内存。 xxx_init() 应分配内存并将其分配给此指针:

    initid-> ptr = allocated_memory;
    

    xxx() 和中 xxx_deinit() ,请参阅 initid->ptr 使用或取消分配内存。

  • bool const_item

    xxx_init() 应该设置 const_item 1 if xxx() 始终返回相同的值, 0 否则。

29.4.2.2聚合函数的UDF调用序列

本节介绍在创建聚合UDF时需要定义的不同功能。 第29.4.2节“添加新的用户定义函数” 描述了MySQL调用这些函数的顺序。

  • xxx_reset()

    当MySQL找到新组中的第一行时,将调用此函数。 它应该重置任何内部摘要变量,然后使用给定的 UDF_ARGS 参数作为组内部汇总值的第一个值。 声明 xxx_reset() 如下:

    void xxx_reset(UDF_INIT * initid,UDF_ARGS * args,
                   char * is_null,char * error);
    

    xxx_reset() 在MySQL 8.0中不需要或使用,而UDF接口则使用它 xxx_clear() 但是,您可以定义两者 xxx_reset() 以及 xxx_clear() 是否要让UDF与旧版本的服务器一起使用。 (如果确实包含这两个函数,则 xxx_reset() 在许多情况下 函数可以通过调用 xxx_clear() 重置所有变量 在内部实现 ,然后调用 xxx_add() 以将 UDF_ARGS 参数 添加 为组中的第一个值。)

  • xxx_clear()

    当MySQL需要重置摘要结果时,将调用此函数。 它在每个新组的开头调用,但也可以调用以重置没有匹配行的查询的值。 声明 xxx_clear() 如下:

    void xxx_clear(UDF_INIT * initid,char * is_null,char * error);
    

    is_null 设置为 CHAR(0) 在调用之前 指向 xxx_clear()

    如果出现问题,您可以在 error 参数指向 的变量中存储值 error 指向单字节变量,而不是字符串缓冲区。

    xxx_clear() 是MySQL 8.0所必需的。

  • xxx_add()

    为属于同一组的所有行调用此函数。 您应该使用它将 UDF_ARGS 参数中 的值添加 到内部摘要变量中。

    void xxx_add(UDF_INIT * initid,UDF_ARGS * args,
                 char * is_null,char * error);
    

xxx() 应该以与非聚合UDF相同的方式声明聚合UDF 函数。 请参见 第29.4.2.1节“简单函数的UDF调用序列”

对于聚合UDF,MySQL xxx() 在处理完组中的所有行之后 调用该 函数。 您通常不应该 UDF_ARGS 在此处 访问其 参数,而是根据您的内部汇总变量返回一个值。

返回值处理 xxx() 应该与非聚合UDF的 处理 方式相同。 请参见 第29.4.2.4节“UDF返回值和错误处理”

xxx_reset() xxx_add() 函数处理他们的 UDF_ARGS 论据的方式与非聚集UDF的功能相同。 请参见 第29.4.2.3节“UDF参数处理”

指针参数 is_null error 是相同的所有来电 xxx_reset() xxx_clear() xxx_add() xxx() 您可以使用它来记住您遇到错误或 xxx() 函数 是否 应该返回 NULL 你不应该存储一个字符串 *error error 指向单字节变量,而不是字符串缓冲区。

*is_null 为每个组重置(在呼叫之前 xxx_clear() )。 *error 永远不会重置。

如果 返回 时设置 *is_null *error 设置 xxx() ,则MySQL返回 NULL 组功能的结果。

29.4.2.3 UDF参数处理

args 参数指向 UDF_ARGS 具有此处列出的成员 结构:

  • unsigned int arg_count

    参数的数量。 如果需要使用特定数量的参数调用函数,请在初始化函数中检查此值。 例如:

    if(args-> arg_count!= 2)
    {
        strcpy(消息,“XXX()需要两个参数”);
        返回1;
    }
    

    对于 UDF_ARGS 作为数组的 其他 成员值,数组引用从零开始。 也就是说,使用从0到 args->arg_count -1的 索引值来引用数组成员

  • enum Item_result *arg_type

    指向包含每个参数类型的数组的指针。 可能的类型的值是 STRING_RESULT INT_RESULT REAL_RESULT ,和 DECIMAL_RESULT

    要确保参数属于给定类型并返回错误(如果不是),请检查 arg_type 初始化函数中 数组。 例如:

    if(args-> arg_type [0]!= STRING_RESULT ||
        args-> arg_type [1]!= INT_RESULT)
    {
        strcpy(消息,“XXX()需要一个字符串和一个整数”);
        返回1;
    }
    

    类型的参数 DECIMAL_RESULT 作为字符串传递,因此您应该以与 STRING_RESULT 相同的方式处理它们

    作为要求函数的参数具有特定类型的替代方法,您可以使用初始化函数将 arg_type 元素 设置 为所需的类型。 这会导致MySQL为每次调用强制转换这些类型的参数 xxx() 例如,要指定前两个参数应分别强制转换为字符串和整数,请执行以下操作 xxx_init()

    args-> arg_type [0] = STRING_RESULT;
    args-> arg_type [1] = INT_RESULT;
    

    精确值十进制参数(如 1.3 DECIMAL 列值)的类型为 DECIMAL_RESULT 但是,值将作为字符串传递。 如果要接收数字,请使用初始化函数指定应将参数强制转换为 REAL_RESULT 值:

    args-> arg_type [2] = REAL_RESULT;
    
  • char **args

    args->args 将信息传递给初始化函数,以了解传递给函数的参数的一般性质。 对于常量参数 i args->args[i] 指向参数值。 (有关如何正确访问该值的说明,请参阅后面的内容。)对于非常量参数, args->args[i] 0 常量参数是仅使用常量的表达式,例如 3 or 4*7-2 SIN(3.14) 非常量参数是一个表达式,它引用可能在行之间更改的值,例如使用非常量参数调用的列名或函数。

    对于main函数的每次调用, args->args 包含为当前正在处理的行传递的实际参数。

    如果参数 i 表示 NULL args->args[i] 则为空指针(0)。 如果参数不是 NULL ,函数可以引用如下:

    • 类型的参数 STRING_RESULT 作为字符串指针加上长度给出,以便能够处理二进制数据或任意长度的数据。 字符串内容可用, args->args[i] 字符串长度为 args->lengths[i] 不要假设该字符串以空值终止。

    • 对于类型的参数 INT_RESULT ,必须强制 args->args[i] 转换为 long long 值:

      long long int_val;
      int_val = *((long long *)args-> args [i]);
      
    • 对于类型的参数 REAL_RESULT ,必须强制 args->args[i] 转换为 double 值:

      double real_val;
      real_val = *((double *)args-> args [i]);
      
    • 对于类型的参数 DECIMAL_RESULT ,该值作为字符串传递,应该像 STRING_RESULT 一样处理

    • ROW_RESULT 参数没有实现。

  • unsigned long *lengths

    对于初始化函数, lengths 数组指示每个参数的最大字符串长度。 你不应该改变这些。 对于main函数的每次调用, lengths 包含为当前正在处理的行传递的任何字符串参数的实际长度。 对于类型的参数 INT_RESULT REAL_RESULT lengths 仍包含该参数的最大长度(作为用于初始化功能)。

  • char *maybe_null

    对于初始化函数, maybe_null 数组为每个参数指示参数值是否为空(如果不是0,如果是,则为1)。

  • char **attributes

    args->attributes 传递有关UDF参数名称的信息。 对于参数 i ,属性名称可用作字符串, args->attributes[i] 属性长度为 args->attribute_lengths[i] 不要假设该字符串以空值终止。

    默认情况下,UDF参数的名称是用于指定参数的表达式的文本。 对于UDF,参数也可以有一个可选 子句,在这种情况下参数名称是 因此 ,每个参数 值取决于是否给出了别名。 [AS] alias_name alias_name attributes

    假设 my_udf() 调用 UDF 如下:

    SELECT my_udf(expr1,expr2 AS alias1,expr3 alias2);
    

    在这种情况下, attributes attribute_lengths 数组将具有以下值:

    args-> attributes [0] =“expr1”
    args-> attribute_lengths [0] = 5
    
    args-> attributes [1] =“alias1”
    args-> attribute_lengths [1] = 6
    
    args-> attributes [2] =“alias2”
    args-> attribute_lengths [2] = 6
    
  • unsigned long *attribute_lengths

    attribute_lengths 阵列表示每个参数的名称的长度。

29.4.2.4 UDF返回值和错误处理

0 如果没有发生错误, 则应返回初始化函数 1 否则 返回 如果发生错误, xxx_init() 应在 message 参数中 存储以null结尾的错误消息 消息将返回给客户端。 消息缓冲区是 MYSQL_ERRMSG_SIZE 字符长,但您应该尝试将消息保持少于80个字符,以使其适合标准终端屏幕的宽度。

main函数的返回值 xxx() 是函数值,for long long double 函数。 字符串函数应返回指向结果的指针,并设置 *length 为返回值的长度(以字节为单位)。 例如:

memcpy(结果,“结果字符串”,13);
*长度= 13;

MySQL xxx() 使用 result 参数 将缓冲区传递给 函数 此缓冲区足够长,可容纳255个字符,可以是多字节字符。 如果 xxx() 函数适合, 函数可以将结果存储在此缓冲区中,在这种情况下,返回值应该是指向缓冲区的指针。 如果函数将结果存储在不同的缓冲区中,它应该返回指向该缓冲区的指针。

如果您的字符串函数不使用提供的缓冲区(例如,如果它需要返回长度超过255个字符的字符串),则必须 malloc() xxx_init() 函数或 xxx() 函数中 为自己的缓冲区分配空间,并 函数中释放它 xxx_deinit() 您可以将分配的内存存储在 结构 ptr 插槽中,以 UDF_INIT 供将来的 xxx() 调用 重用 请参见 第29.4.2.1节“简单函数的UDF调用序列”

要指示 NULL main函数 的返回值 ,请设置 *is_null 1

* is_null = 1;

要在main函数中指示错误返回,请设置 *error 1

*错误= 1;

如果 xxx() *error 1 任何行,函数值是 NULL 当前行,并通过在该语句处理任何后续行 XXX() 被调用。 xxx() 甚至没有为后续行调用。)

29.4.2.5 UDF编译和安装

必须在运行服务器的主机上编译和安装实现UDF的文件。 此处描述 sql/udf_example.cc 了MySQL源代码分发中包含 的示例UDF文件的过程 有关UDF安装的其他信息,请参见 第5.7.1节“安装和卸载用户定义的函数”

如果将在将要复制到从属服务器的语句中引用UDF,则必须确保每个从属服务器都具有可用的功能。 否则,当从属服务器尝试调用该函数时,复制将失败。

紧接着的说明适用于Unix。 Windows的说明将在本节后面给出。

udf_example.cc 文件包含以下功能:

  • metaphon() 返回字符串参数的metaphon字符串。 这类似于soundex字符串,但它更适合英语。

  • myfunc_double() 返回其参数中字符的ASCII值之和除以其参数长度的总和。

  • myfunc_int() 返回其参数长度的总和。

  • sequence([const int]) 如果没有给出数字,则返回从给定数字开始的序列或1。

  • lookup() 返回主机名的IP地址。

  • reverse_lookup() 返回IP地址的主机名。 可以使用表单的单个字符串参数 'xxx.xxx.xxx.xxx' 或使用四个数字 来调用该函数

  • avgcost() 返回平均成本。 这是一个聚合函数。

应该将可动态加载的文件编译为可共享的库文件,使用如下命令:

gcc -shared -o udf_example.so udf_example.cc

如果你正在使用 CMK CMake (这是MySQL本身的配置),你应该能够 udf_example.so 用一个更简单的命令 创建

make udf_example

编译包含UDF的共享对象后,必须安装它并告诉MySQL它。 udf_example.cc 使用 gcc 直接 编译共享对象 会生成一个名为的文件 udf_example.so 将共享对象复制到服务器的插件目录并命名 udf_example.so 该目录由 plugin_dir 系统变量 的值给出

在某些系统上, 配置动态链接器 ldconfig 程序无法识别共享对象,除非其名称以 lib 在这种情况下,您应该重命名文件,如 udf_example.so to libudf_example.so

在Windows上,使用以下过程编译用户定义的函数:

  1. 获取MySQL源代码分发。 请参见 第2.1.2节“如何获取MySQL”

  2. 如有必要,从 http://www.cmake.org 获取 CMake 构建实用程序 (需要2.6或更高版本)。

  3. 在源代码树中,在 sql 目录中查找名为 udf_example.def 和的 文件 udf_example.cc 将这个目录中的两个文件复制到您的工作目录。

  4. 使用以下内容 创建一个 CMake makefile CMakeLists.txt ):

    PROJECT(udf_example)
    
    #SQL包含目录的路径
    INCLUDE_DIRECTORIES( “C:/ MySQL的/包括”)
    
    ADD_DEFINITIONS( “ -  DHAVE_DLOPEN”)
    ADD_LIBRARY(udf_example MODULE udf_example.cc udf_example.def)
    TARGET_LINK_LIBRARIES(udf_example wsock32)
    
  5. 创建VC项目和解决方案文件,替换适当的 generator 值:

    cmake -G“ generator

    调用 cmake --help会 显示有效生成器的列表。

  6. 创建 udf_example.dll

    devenv udf_example.sln / build发布
    

在所有平台上,将共享库文件复制到 plugin_dir 目录后, 使用以下语句 通知 mysqld 有关新函数的信息。 如果库文件的后缀与 .so 系统上 的后缀不同,请 在整个过程中替换正确的后缀(例如, .dll 在Windows上)。

创建功能metaphon返回STRING
  SONAME'udf_example.so';
创建功能myfunc_double RETURNS REAL
  SONAME'udf_example.so';
创建函数myfunc_int RETURNS INTEGER
  SONAME'udf_example.so';
CREATE FUNCTION序列RETURNS INTEGER
  SONAME'udf_example.so';
CREATE FUNCTION查找RETURNS STRING
  SONAME'udf_example.so';
创建功能reverse_lookup返回STRING
  SONAME'udf_example.so';
创造集合功能avgcost RETURNS REAL
  SONAME'udf_example.so';

安装后,功能将一直保持安装状态,直到卸载为止。

要删除功能,请使用 DROP FUNCTION

DROP FUNCTION metaphon;
DROP FUNCTION myfunc_double;
DROP FUNCTION myfunc_int;
DROP FUNCTION序列;
DROP FUNCTION查找;
DROP FUNCTION reverse_lookup;
DROP FUNCTION avgcost;

CREATE FUNCTION DROP FUNCTION 语句更新 func 表中 mysql 充当UDF登记系统数据库。 函数的名称,类型和共享库名称保存在 mysql.func 表中。 要创建函数,您必须具有 数据库 INSERT 特权 mysql 要删除函数,您必须具有 数据库 DELETE 权限 mysql

您无法用于 CREATE FUNCTION 重新安装以前安装的功能。 要重新安装某个功能,请先将其删除 DROP FUNCTION ,然后再安装 CREATE FUNCTION 您需要执行此操作,例如,如果升级到提供该函数的更新实现的新版本的MySQL,或者您重新编译已编写的函数的新版本。 否则,服务器继续使用旧版本。

活动功能是已加载但 CREATE FUNCTION 未被删除的功能 DROP FUNCTION 每次服务器启动时都会重新加载所有活动函数,除非您 使用该 选项 启动 mysqld --skip-grant-tables 在这种情况下,服务器在启动期间不加载UDF并且UDF不可用。

29.4.2.6 UDF安全预防措施

MySQL采取了一些措施来防止滥用用户定义的函数。

UDF库文件不能放在任意目录中。 它们必须位于服务器的插件目录中。 该目录由值给出 plugin_dir 系统变量

要使用 CREATE FUNCTION DROP FUNCTION ,您必须 分别拥有 数据库的 权限 INSERT DELETE 权限 mysql 这是必要的,因为这些语句会添加和删除 mysql.func 表中的

除了 xxx 与main xxx() 函数 对应 符号 之外,UDF还应至少定义一个符号 这些辅助符号对应 xxx_init() xxx_deinit() xxx_reset() xxx_clear() ,和 xxx_add() 功能。 mysqld 还支持一个 --allow-suspicious-udfs 选项,用于控制是否 xxx 可以加载 仅包含 符号的 UDF 默认情况下,该选项处于关闭状态,以防止尝试从包含合法UDF的共享库文件加载函数。 如果您有较旧的UDF只包含 xxx 符号且无法重新编译的 包含辅助符号,可能需要指定该 --allow-suspicious-udfs 选项。 否则,您应该避免启用此功能。

29.4.3添加新的本机功能

要添加新的本机MySQL函数,请使用此处描述的过程,该过程要求您使用源分发。 您无法将本机函数添加到二进制分发版中,因为必须修改MySQL源代码并从修改后的源代码编译MySQL。 如果迁移到另一个版本的MySQL(例如,发布新版本时),则必须使用新版本重复此过程。

如果将在将复制到从属服务器的语句中引用新的本机函数,则必须确保每个从属服务器也具有可用的函数。 否则,当从属设备尝试调用该函数时,复制将失败。

要添加新的本机函数,请按照以下步骤修改目录中的源文件 sql

  1. 在以下位置为函数创建子类 item_create.cc

    • 如果函数采用的参数的固定数,创建的一个子类 Create_func_arg0 Create_func_arg1 Create_func_arg2 ,或 Create_func_arg3 ,分别取决于功能是否需要零个,一个,两个或三个参数。 举例来说,看到的 Create_func_uuid Create_func_abs Create_func_pow ,和 Create_func_lpad 类。

    • 如果函数采用可变数量的参数,则创建一个子类 Create_native_func 有关示例,请参阅 Create_func_concat

  2. 要提供可在SQL语句中引用该函数的名称,请 item_create.cc 通过向此数组添加一行来 注册该名称

    static Native_func_registry func_array []
    

    您可以为同一功能注册多个名称。 例如,请参阅for "LCASE" 的行 "LOWER" ,它们是别名 Create_func_lcase

  3. item_func.h ,声明一个继承自 Item_num_func 的类 Item_str_func ,取决于函数是返回数字还是字符串。

  4. item_func.cc ,添加以下声明之一,具体取决于您是否定义数字或字符串函数:

    double Item_func_newname :: val()
    longlong Item_func_newname :: val_int()
    String * Item_func_newname :: Str(String * str)
    

    如果从任何标准项(例如 Item_num_func 继承对象 ,则可能只需定义其中一个函数,并让父对象处理其他函数。 例如, Item_str_func 该类定义了一个 对返回的值 val() 执行 函数 atof() ::str()

  5. 如果函数是非确定性的,请在项构造函数中包含以下语句,以指示不应缓存函数结果:

    current_thd-> lex-> safe_to_cache_query = 0;
    

    如果给定参数的固定值,它可以为不同的调用返回不同的结果,那么函数是不确定的。

  6. 您还应该定义以下对象函数:

    void Item_func_newname :: fix_length_and_dec()
    

    此函数至少应 max_length 根据给定的参数进行 计算 max_length 是函数可能返回的最大字符数。 maybe_null = 0 如果主函数不能返回 NULL 值, 此函数也应该设置 该函数可以 NULL 通过检查参数的 maybe_null 变量 来检查是否有任何函数参数可以返回 请查看 Item_func_mod::fix_length_and_dec 如何执行此操作的典型示例。

所有功能必须是线程安全的。 换句话说,不要在函数中使用任何全局变量或静态变量而不用互斥体保护它们。

如果 NULL 要从 ::val() ,, ::val_int() 或者 返回 ::str() 则应设置 null_value 为1并返回0。

对于 ::str() 对象函数,还需要注意以下注意事项:

  • String *str 参数提供了一个字符串缓冲区,可用于保存结果。 (有关 String 类型的 更多信息 ,请查看该 sql_string.h 文件。)

  • ::str() 函数应该返回保存结果的字符串,或者 (char*) 0 结果是 NULL

  • 除非绝对必要,否则所有当前字符串函数都会尝试避免分配任何内存

29.5调试和移植MySQL

本节帮助您将MySQL移植到其他操作系统。 首先检查当前支持的操作系统列表。 请参阅 https://www.mysql.com/support/supportedplatforms/database.html 如果您已经创建了一个新的MySQL端口,请告诉我们,以便我们在此处和我们的网站( http://www.mysql.com/ 上列出 ,推荐给其他用户。

注意

如果您创建一个新的MySQL端口,您可以根据GPL许可证自由复制和分发它,但它不会使您成为MySQL的版权所有者。

服务器需要一个正在运行的POSIX线程库。

要从源代码构建MySQL,您的系统必须满足 第2.9节“从源代码安装MySQL”中 列出的工具要求

如果您遇到新端口问题,您可能需要对MySQL进行一些调试! 请参见 第29.5.1节“调试MySQL服务器”

注意

在开始调试 mysqld 之前 ,首先让测试程序运行 mysys/thr_lock 这可以确保您的线程安装甚至可以远程工作!

注意

MySQL源代码包含使用Doxygen编写的内部文档。 本文档对于从开发人员的角度理解MySQL的工作原理非常有用。 生成的Doxygen内容可从 https://dev.mysql.com/doc/index-other.html获得 也可以使用 第2.9.7节“生成MySQL Doxygen文档内容”中 的说明从MySQL源代码分发本地生成此内容

29.5.1调试MySQL服务器

如果您使用的一些功能是在MySQL很新,你可以尝试运行 mysqld的 --skip-new (即禁用所有新的,潜在的不安全功能)。 请参见 第B.4.3.3节“如果MySQL不断崩溃该怎么办”

如果 mysqld 不想启动,请确认您没有 my.cnf 干扰您的设置的文件! 您可以 my.cnf 使用 mysqld --print-defaults 检查您的 参数, 并避免使用 mysqld --no-defaults ... 开头

如果 mysqld 开始占用CPU或内存或者它 挂起 ”, 你可以使用 mysqladmin processlist status 来查明某人是否正在执行需要很长时间的查询。