MySQL8-中文参考-二十八-

MySQL8 中文参考(二十八)

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

原文:dev.mysql.com/doc/refman/8.0/en/reset-replica.html

15.4.2.4 RESET REPLICA Statement

RESET REPLICA [ALL] [*channel_option*]

*channel_option*:
    FOR CHANNEL *channel*

RESET REPLICA使副本忘记其在源二进制日志中的位置。从 MySQL 8.0.22 开始,请使用RESET REPLICA代替从该版本开始已弃用的RESET SLAVE。在 MySQL 8.0.22 之前的版本中,请使用RESET SLAVE

此语句用于进行清理启动;它清除了复制元数据存储库,删除了所有中继日志文件,并启动了一个新的中继日志文件。它还将使用CHANGE REPLICATION SOURCE TO语句(从 MySQL 8.0.23 开始)或CHANGE MASTER TO语句(在 MySQL 8.0.23 之前)指定的复制延迟重置为 0。

注意

所有中继日志文件都将被删除,即使它们尚未完全被复制 SQL 线程执行。(这是一个可能存在的情况,如果您已经发出了STOP REPLICA语句或者如果副本负载很高。)

对于使用 GTIDs 的服务器(gtid_modeON),执行RESET REPLICA对 GTID 执行历史没有影响。该语句不会更改gtid_executedgtid_purged的值,也不会更改mysql.gtid_executed表。如果需要重置 GTID 执行历史,请使用RESET MASTER,即使启用了 GTID 的服务器是一个禁用了二进制日志记录的副本。

RESET REPLICA需要RELOAD权限。

要使用RESET REPLICA,必须停止复制 SQL 线程和复制 I/O(接收器)线程,因此在运行中的副本上,在执行RESET REPLICA之前,请使用STOP REPLICA。要在 Group Replication 组成员上使用RESET REPLICA,成员状态必须为OFFLINE,表示插件已加载但成员当前不属于任何组。可以通过使用STOP GROUP REPLICATION语句将组成员下线。

可选的 FOR CHANNEL *channel* 子句使您可以命名语句适用于哪个复制通道。提供 FOR CHANNEL *channel* 子句将 RESET REPLICA 语句应用于特定的复制通道。将 FOR CHANNEL *channel* 子句与 ALL 选项结合使用会删除指定的通道。如果未命名通道且不存在额外通道,则该语句适用于默认通道。当存在多个复制通道时,发出不带 FOR CHANNEL *channel* 子句的 RESET REPLICA ALL 语句会删除 所有 复制通道,并仅重新创建默认通道。有关更多信息,请参见 Section 19.2.2, “Replication Channels”。

RESET REPLICA 不会更改任何复制连接参数,包括源主机名和端口、复制用户帐户及其密码、PRIVILEGE_CHECKS_USER 帐户、REQUIRE_ROW_FORMAT 选项、REQUIRE_TABLE_PRIMARY_KEY_CHECK 选项和 ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS 选项。如果要更改任何复制连接参数,可以在服务器启动后使用 CHANGE REPLICATION SOURCE TO 语句(从 MySQL 8.0.23 开始)或 CHANGE MASTER TO 语句(MySQL 8.0.23 之前)来实现。如果要删除所有复制连接参数,请使用 RESET REPLICA ALLRESET REPLICA ALL 还会清除由 CHANGE REPLICATION SOURCE TO | CHANGE MASTER TO 设置的 IGNORE_SERVER_IDS 列表。当使用了 RESET REPLICA ALL 后,如果要再次将实例用作复制品,则需要在服务器启动后发出 CHANGE REPLICATION SOURCE TO | CHANGE MASTER TO 语句以指定新的连接参数。

从 MySQL 8.0.27 开始,可以在 CHANGE REPLICATION SOURCE TO 语句中设置 GTID_ONLY 选项,以阻止复制通道在复制元数据存储库中持久化文件名和文件位置。当发出 RESET REPLICA 语句时,复制元数据存储库会同步。RESET REPLICA ALL 删除而不是更新存储库,因此它们会隐式同步。

在发出RESET REPLICA但在发出START REPLICA之前发生意外服务器退出或故意重启时,复制连接参数的保留取决于用于复制元数据的存储库:

  • 当服务器上设置了master_info_repository=TABLErelay_log_info_repository=TABLE时(这是 MySQL 8.0 的默认设置),复制连接参数将在InnoDBmysql.slave_master_infomysql.slave_relay_log_info中作为RESET REPLICA操作的一部分保留在崩溃安全的表中。它们也会保留在内存中。在发出RESET REPLICA但在发出START REPLICA之前发生意外服务器退出或故意重启时,复制连接参数将从表中检索并重新应用到通道上。这种情况适用于 MySQL 8.0.13 的连接元数据存储库,以及 MySQL 8.0.19 的应用程序元数据存储库。

  • 如果服务器上设置了master_info_repository=FILErelay_log_info_repository=FILE,这在 MySQL 8.0 之前已经被弃用,或者 MySQL 服务器版本早于上述版本,则复制连接参数仅保留在内存中。如果由于意外服务器退出或故意重启而在发出RESET REPLICA后立即重新启动副本mysqld,连接参数将丢失。在这种情况下,您必须在服务器启动后发出CHANGE REPLICATION SOURCE TO语句(从 MySQL 8.0.23 开始)或CHANGE MASTER TO语句(在 MySQL 8.0.23 之前)来重新指定连接参数,然后再发出START REPLICA

RESET REPLICA不会更改任何受该语句影响的通道的复制过滤器设置(例如--replicate-ignore-table)。但是,RESET REPLICA ALL会删除该语句删除的通道上设置的复制过滤器。当删除的通道或通道重新创建时,将复制为副本指定的任何全局复制过滤器,并且不应用特定于通道的复制过滤器。有关更多信息,请参见第 19.2.5.4 节,“基于复制通道的过滤器”。

RESET REPLICA会导致正在进行的事务隐式提交。参见 Section 15.3.3, “Statements That Cause an Implicit Commit”。

如果复制 SQL 线程在停止时正在复制临时表,并且发出RESET REPLICA,则这些复制的临时表将在副本上被删除。

注意

当在 NDB 集群副本 SQL 节点上使用RESET REPLICA时,会清除mysql.ndb_apply_status表。在使用此语句时,应该记住ndb_apply_status使用NDB存储引擎,因此被附加到集群的所有 SQL 节点共享。

您可以通过在执行RESET REPLICA之前发出SET GLOBAL @@``ndb_clear_apply_status=OFF来覆盖此行为,这样可以防止副本在这种情况下清除ndb_apply_status表。

原文:dev.mysql.com/doc/refman/8.0/en/reset-slave.html

15.4.2.5 重置从库语句

RESET {SLAVE | REPLICA} [ALL] [*channel_option*]

*channel_option*:
    FOR CHANNEL *channel*

使复制品忘记其在源二进制日志中的位置。从 MySQL 8.0.22 开始,RESET SLAVE已被弃用,应改用别名RESET REPLICA。在 MySQL 8.0.22 之前的版本中,请使用RESET SLAVE。该语句的工作方式与以前相同,只是语句及其输出所使用的术语已更改。使用时,两个版本的语句会更新相同的状态变量。请参阅RESET REPLICA的文档以获取有关该语句的描述。

原文:dev.mysql.com/doc/refman/8.0/en/start-replica.html

15.4.2.6 启动 REPLICA 语句

START REPLICA [*thread_types*] [*until_option*] [*connection_options*] [*channel_option*]

*thread_types*:
    [*thread_type* [, *thread_type*] ... ]

*thread_type*:
    IO_THREAD | SQL_THREAD

*until_option*:
    UNTIL {   {SQL_BEFORE_GTIDS | SQL_AFTER_GTIDS} = *gtid_set*
          |   MASTER_LOG_FILE = '*log_name*', MASTER_LOG_POS = *log_pos*
          |   SOURCE_LOG_FILE = '*log_name*', SOURCE_LOG_POS = *log_pos*
          |   RELAY_LOG_FILE = '*log_name*', RELAY_LOG_POS = *log_pos*
          |   SQL_AFTER_MTS_GAPS  }

*connection_options*:
    [USER='*user_name*'] [PASSWORD='*user_pass*'] [DEFAULT_AUTH='*plugin_name*'] [PLUGIN_DIR='*plugin_dir*']

*channel_option*:
    FOR CHANNEL *channel*

*gtid_set*:
    *uuid_set* [, *uuid_set*] ...
    | ''

*uuid_set*:
    *uuid*:*interval*[:*interval*]...

*uuid*:
    *hhhhhhhh*-*hhhh*-*hhhh*-*hhhh*-*hhhhhhhhhhhh*

*h*:
    [0-9,A-F]

*interval*:
    *n*[-*n*]

    (*n* >= 1)

START REPLICA启动复制线程,可以一起启动或分开启动。从 MySQL 8.0.22 开始,使用START REPLICA代替从该版本开始弃用的START SLAVE。在 MySQL 8.0.22 之前的版本中,请使用START SLAVE

START REPLICA需要REPLICATION_SLAVE_ADMIN权限(或已弃用的SUPER权限)。START REPLICA导致正在进行的事务隐式提交。请参阅第 15.3.3 节,“导致隐式提交的语句”。

对于线程类型选项,您可以指定IO_THREADSQL_THREAD,这两者,或者都不指定。只有启动的线程受语句影响。

  • 没有线程类型选项的START REPLICA启动所有复制线程,同时具有线程类型选项的START REPLICA也是如此。

  • IO_THREAD启动复制接收器线程,从源服务器读取事件并将其存储在中继日志中。

  • SQL_THREAD启动复制应用程序线程,从中继日志中读取事件并执行它们。多线程复制(使用replica_parallel_workersslave_parallel_workers>0)使用协调器线程和多个应用程序线程应用事务,而SQL_THREAD启动所有这些线程。

重要提示

START REPLICA在所有复制线程启动后向用户发送确认。然而,复制接收线程可能尚未成功连接到源,或者应用程序线程可能在启动后立即停止应用事件。START REPLICA在启动线程后不会继续监视线程,因此如果线程随后停止或无法连接,则不会警告您。您必须检查复制的错误日志以查看复制线程生成的错误消息,或者使用SHOW REPLICA STATUS检查它们是否正常运行。成功的START REPLICA语句会导致SHOW REPLICA STATUS显示Replica_SQL_Running=Yes,但可能会或可能不会显示Replica_IO_Running=Yes,因为只有在接收线程同时运行和连接时才会显示Replica_IO_Running=Yes。有关更多信息,请参见第 19.1.7.1 节,“检查复制状态”。

可选的FOR CHANNEL *channel*子句使您能够命名语句适用于哪个复制通道。提供FOR CHANNEL *channel*子句将START REPLICA语句应用于特定的复制通道。如果未命名任何子句且没有额外的通道存在,则该语句适用于默认通道。如果START REPLICA语句在使用多个通道时未定义通道,则此语句将为所有通道启动指定的线程。有关更多信息,请参见第 19.2.2 节,“复制通道”。

Group Replication 的复制通道(group_replication_appliergroup_replication_recovery)由服务器实例自动管理。START REPLICA不能与group_replication_recovery通道一起使用,只能在 Group Replication 未运行时才能与group_replication_applier通道一起使用。group_replication_applier通道只有一个应用程序线程,没有接收线程,因此如果需要,可以使用SQL_THREAD选项启动它,而不使用IO_THREAD选项。

START REPLICA支持可插拔的用户密码身份验证(参见第 8.2.17 节,“可插拔身份验证”),使用USERPASSWORDDEFAULT_AUTHPLUGIN_DIR选项,如下列表所述。当您使用这些选项时,必须启动接收线程(IO_THREAD选项)或所有复制线程;不能仅启动复制应用程序线程(SQL_THREAD选项)。

USER

用户账户的用户名。如果使用PASSWORD,则必须设置此选项。该选项不能设置为空或 null 字符串。

PASSWORD

指定用户账户的密码。

DEFAULT_AUTH

认证插件的名称。默认为 MySQL 本机认证。

PLUGIN_DIR

认证插件的位置。

重要

使用START REPLICA设置的密码在写入 MySQL Server 的日志、性能模式表和显示进程列表语句时会被掩码。但是,在传输过程中,它以明文形式发送到副本服务器实例。为了保护传输中的密码,使用 SSL/TLS 加密、SSH 隧道或其他方法保护连接,以防止未经授权的查看,用于发出START REPLICA的客户端与副本服务器实例之间的连接。

UNTIL子句使副本开始复制,然后处理事务直到您在UNTIL子句中指定的点,然后再次停止。UNTIL子句可用于使副本继续进行,直到您想要跳过不需要的事务的点之前,然后如第 19.1.7.3 节,“跳过事务”中所述跳过事务。要识别事务,您可以使用源二进制日志或副本中继日志的mysqlbinlog,或使用显示二进制日志事件语句。

您还可以使用UNTIL子句来通过逐个处理事务或分段处理事务来调试复制。如果您使用UNTIL子句来执行此操作,请使用--skip-slave-start选项启动副本,或从 MySQL 8.0.24 开始,使用skip_slave_start系统变量,以防止副本服务器启动时运行 SQL 线程。在过程完成后,删除选项或系统变量设置,以防止在意外服务器重新启动时被遗忘。

显示副本状态语句包括显示UNTIL条件当前值的输出字段。UNTIL条件持续时间取决于受影响线程是否仍在运行,并在它们停止时被移除。

UNTIL子句作用于复制应用程序线程(SQL_THREAD选项)。您可以使用SQL_THREAD选项或让副本默认启动两个线程。如果仅使用IO_THREAD选项,则UNTIL子句将被忽略,因为未启动应用程序线程。

您在UNTIL子句中指定的点可以是以下选项中的任何一个(且仅一个):

SOURCE_LOG_FILESOURCE_LOG_POS(来自 MySQL 8.0.23),或 MASTER_LOG_FILEMASTER_LOG_POS(到 MySQL 8.0.22)

这些选项使得复制应用程序处理事务直到其中继日志中的位置,该位置由源服务器上二进制日志中相应点的文件名和文件位置标识。应用程序线程在指定位置之后或之后找到最近的事务边界,完成应用事务,并在那里停止。对于压缩的事务有效载荷,请指定压缩的Transaction_payload_event的结束位置。

当在CHANGE REPLICATION SOURCE TO语句上设置了GTID_ONLY选项以阻止复制通道在复制元数据存储库中持久化文件名和文件位置时,仍然可以使用这些选项。文件名和文件位置在内存中被跟踪。

RELAY_LOG_FILERELAY_LOG_POS

这些选项使得复制应用程序处理事务直到副本的中继日志中的位置,该位置由中继日志文件名和该文件中的位置标识。应用程序线程在指定位置之后或之后找到最近的事务边界,完成应用事务,并在那里停止。对于压缩的事务有效载荷,请指定压缩的Transaction_payload_event的结束位置。

当在CHANGE REPLICATION SOURCE TO语句上设置了GTID_ONLY选项以阻止复制通道在复制元数据存储库中持久化文件名和文件位置时,仍然可以使用这些选项。文件名和文件位置在内存中被跟踪。

SQL_BEFORE_GTIDS

此选项使得复制应用程序开始处理事务,并在遇到任何位于指定 GTID 集中的事务时停止。来自 GTID 集的遇到的事务不会被应用,也不会应用 GTID 集中的任何其他事务。该选项接受包含一个或多个全局事务标识符的 GTID 集作为参数(参见 GTID Sets)。GTID 集中的事务不一定按照其 GTID 的顺序出现在复制流中,因此应用程序停止之前的事务不一定是最早的。

SQL_AFTER_GTIDS

此选项使得复制应用程序开始处理事务,并在处理完指定 GTID 集中的所有事务时停止。该选项接受包含一个或多个全局事务标识符的 GTID 集作为参数(参见 GTID Sets)。

使用 SQL_AFTER_GTIDS 后,复制线程在处理完 GTID 集中的所有事务后停止。事务按接收顺序处理,因此可能包括不属于 GTID 集的事务,但在集合中的所有事务提交之前接收(并处理)。例如,执行 START REPLICA UNTIL SQL_AFTER_GTIDS = 3E11FA47-71CA-11E1-9E33-C80AA9429562:11-56 会导致复制获取(并处理)源中的所有事务,直到处理完序列号为 11 到 56 的所有事务,然后在达到该点后停止,不再处理任何额外的事务。

SQL_AFTER_GTIDS 与多线程应用程序不兼容。如果在多线程应用程序中使用此选项,将会引发警告,并且复制将切换到单线程模式。根据用例,可能可以使用 START REPLICA UNTIL MASTER_LOG_POSSTART REPLICA UNTIL SQL_BEFORE_GTIDS。您还可以使用 WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS(),它会等待到达正确位置,但不会停止应用程序线程。

SQL_AFTER_MTS_GAPS

对于仅多线程复制(具有replica_parallel_workersslave_parallel_workers > 0)的情况,此选项使复制进程处理事务,直到从中继日志执行的事务序列中不再存在间隙为止。在使用多线程复制时,以下情况可能会导致间隙出现:

  • 协调器线程停止。

  • 应用程序线程发生错误。

  • mysqld 意外关闭。

当复制通道存在间隙时,复制的数据库处于可能从未存在于源上的状态。复制在内部跟踪间隙并禁止执行会删除间隙信息的 CHANGE REPLICATION SOURCE TO 语句。

在 MySQL 8.0.26 之前,在具有从中继日志执行的事务序列中存在间隙的多线程复制上发出 START REPLICA 会生成警告。要纠正这种情况,解决方案是使用 START REPLICA UNTIL SQL_AFTER_MTS_GAPS。有关更多信息,请参见 Section 19.5.1.34, “Replication and Transaction Inconsistencies”。

从 MySQL 8.0.26 开始,当使用基于 GTID 的复制和 GTID 自动定位(SOURCE_AUTO_POSITION=1)用于通道时,完全跳过检查事务序列中的间隙的过程,因为可以使用 GTID 自动定位来解决事务中的间隙。在这种情况下,START REPLICA UNTIL SQL_AFTER_MTS_GAPS只是在找到要执行的第一个事务时停止应用程序线程,并且不尝试检查事务序列中的间隙。您也可以像往常一样继续使用CHANGE REPLICATION SOURCE TO语句,并且通道可以进行中继日志恢复。

从 MySQL 8.0.27 开始,默认情况下所有副本都是多线程的。当replica_preserve_commit_order=ONslave_preserve_commit_order=ON为副本设置时,这也是从 MySQL 8.0.27 开始的默认设置,除了在replica_preserve_commit_orderslave_preserve_commit_order的描述中列出的特定情况外,不应该出现间隙。如果为副本设置了replica_preserve_commit_order=OFFslave_preserve_commit_order=OFF,这是在 MySQL 8.0.27 之前的默认设置,事务的提交顺序不会被保留,因此出现间隙的可能性要大得多。

如果没有使用 GTIDs,并且您需要将失败的多线程副本更改为单线程模式,您可以按照以下顺序发出以下一系列语句:

START SLAVE UNTIL SQL_AFTER_MTS_GAPS;
SET @@GLOBAL.slave_parallel_workers = 0;
START SLAVE SQL_THREAD;

Or from MySQL 8.0.26:
START REPLICA UNTIL SQL_AFTER_MTS_GAPS;
SET @@GLOBAL.replica_parallel_workers = 0;
START REPLICA SQL_THREAD;

原文:dev.mysql.com/doc/refman/8.0/en/start-slave.html

15.4.2.7 启动从库语句

START {SLAVE | REPLICA} [*thread_types*] [*until_option*] [*connection_options*] [*channel_option*]

*thread_types*:
    [*thread_type* [, *thread_type*] ... ]

*thread_type*:
    IO_THREAD | SQL_THREAD

*until_option*:
    UNTIL {   {SQL_BEFORE_GTIDS | SQL_AFTER_GTIDS} = *gtid_set*
          |   MASTER_LOG_FILE = '*log_name*', MASTER_LOG_POS = *log_pos*
          |   SOURCE_LOG_FILE = '*log_name*', SOURCE_LOG_POS = *log_pos*
          |   RELAY_LOG_FILE = '*log_name*', RELAY_LOG_POS = *log_pos*
          |   SQL_AFTER_MTS_GAPS  }

*connection_options*:
    [USER='*user_name*'] [PASSWORD='*user_pass*'] [DEFAULT_AUTH='*plugin_name*'] [PLUGIN_DIR='*plugin_dir*']

*channel_option*:
    FOR CHANNEL *channel*

*gtid_set*:
    *uuid_set* [, *uuid_set*] ...
    | ''

*uuid_set*:
    *uuid*:*interval*[:*interval*]...

*uuid*:
    *hhhhhhhh*-*hhhh*-*hhhh*-*hhhh*-*hhhhhhhhhhhh*

*h*:
    [0-9,A-F]

*interval*:
    *n*[-*n*]

    (*n* >= 1)

启动复制线程。从 MySQL 8.0.22 开始,START SLAVE 已被弃用,应改用别名 START REPLICA。该语句的工作方式与以前相同,只是语句及其输出所使用的术语已更改。使用时,两个版本的语句都会更新相同的状态变量。请参阅 START REPLICA 的文档以了解该语句的描述。

原文:dev.mysql.com/doc/refman/8.0/en/stop-replica.html

15.4.2.8 STOP REPLICA 语句

STOP REPLICA [*thread_types*] [*channel_option*]

*thread_types*:
    [*thread_type* [, *thread_type*] ... ]

*thread_type*: IO_THREAD | SQL_THREAD

*channel_option*:
    FOR CHANNEL *channel*

停止复制线程。从 MySQL 8.0.22 开始,使用 STOP REPLICA 替代已经废弃的 STOP SLAVE。在 MySQL 8.0.22 之前的版本中,请使用 STOP SLAVE

STOP REPLICA 需要 REPLICATION_SLAVE_ADMIN 权限(或已废弃的 SUPER 权限)。推荐的最佳实践是在停止复制服务器之前在副本上执行 STOP REPLICA(有关更多信息,请参见 Section 7.1.19, “The Server Shutdown Process”)。

START REPLICA 类似,此语句可以与 IO_THREADSQL_THREAD 选项一起使用,以命名要停止的复制线程。请注意,Group Replication 应用程序通道(group_replication_applier)没有复制 I/O(接收器)线程,只有一个复制 SQL(应用程序)线程。因此,使用 SQL_THREAD 选项会完全停止此通道。

STOP REPLICA 导致正在进行的事务隐式提交。请参见 Section 15.3.3, “Statements That Cause an Implicit Commit”。

gtid_next 在执行此语句之前必须设置为 AUTOMATIC

您可以通过设置系统变量 rpl_stop_replica_timeout(从 MySQL 8.0.26 开始)或 rpl_stop_slave_timeout(在 MySQL 8.0.26 之前)来控制 STOP REPLICA 在超时之前等待的时间。这可以用于避免 STOP REPLICA 与使用不同客户端连接到副本的其他 SQL 语句之间的死锁。当达到超时值时,发出命令的客户端会返回错误消息并停止等待,但 STOP REPLICA 指令仍然有效。一旦复制线程不再忙碌,STOP REPLICA 语句就会执行,副本就会停止。

在副本运行时允许一些CHANGE REPLICATION SOURCE TO | CHANGE MASTER TO语句,具体取决于复制线程的状态。但是,在这种情况下,在执行CHANGE REPLICATION SOURCE TO | CHANGE MASTER TO语句之前使用STOP REPLICA仍然受支持。有关更多信息,请参见第 15.4.2.3 节,“CHANGE REPLICATION SOURCE TO Statement”,第 15.4.2.1 节,“CHANGE MASTER TO Statement”和第 19.4.8 节,“故障切换期间切换源”。

可选的FOR CHANNEL *channel*子句使您可以命名语句适用于哪个复制通道。提供FOR CHANNEL *channel*子句将STOP REPLICA语句应用于特定的复制通道。如果未命名通道且没有额外通道存在,则该语句适用于默认通道。如果STOP REPLICA语句在使用多个通道时未命名通道,则此语句将停止所有通道的指定线程。有关更多信息,请参见第 19.2.2 节,“复制通道”。

Group Replication 的复制通道(group_replication_appliergroup_replication_recovery)由服务器实例自动管理。STOP REPLICA不能与group_replication_recovery通道一起使用,并且仅在 Group Replication 未运行时才应与group_replication_applier通道一起使用。group_replication_applier通道仅具有一个应用程序线程,没有接收器线程,因此可以通过使用SQL_THREAD选项而不使用IO_THREAD选项来停止它。

当复制品是多线程的(replica_parallel_workersslave_parallel_workers 的值不为零),从中继日志执行的事务序列中的任何间隙都将作为停止工作线程的一部分而关闭。如果复制品意外停止(例如由于工作线程中的错误或其他线程发出 KILL),而在执行 STOP REPLICA 语句时,从中继日志执行的事务序列可能变得不一致。有关更多信息,请参见 第 19.5.1.34 节,“复制和事务不一致性”。

当源使用基于行的二进制日志格式时,如果正在复制使用非事务性存储引擎的任何表,则应在关闭复制品服务器之前在复制品上执行 STOP REPLICASTOP REPLICA SQL_THREAD。如果当前的复制事件组已修改了一个或多个非事务性表,STOP REPLICA 将等待最多 60 秒以完成事件组,除非您为复制 SQL 线程发出 KILL QUERYKILL CONNECTION 语句。如果超时后事件组仍未完成,将记录错误消息。

当源使用基于语句的二进制日志格式时,在源具有打开临时表时更改源是潜在不安全的。这是为什么不建议使用基于语句的复制临时表的原因之一。您可以通过检查 Replica_open_temp_tablesSlave_open_temp_tables 的值来查看复制品上是否有任何临时表。在使用基于语句的复制时,在执行 CHANGE REPLICATION SOURCE TO | CHANGE MASTER TO 之前,此值应为 0。如果在复制品上有任何临时表打开,在发出 STOP REPLICA 后再发出 CHANGE REPLICATION SOURCE TO | CHANGE MASTER TO 语句会导致一个 ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO 警告。

原文:dev.mysql.com/doc/refman/8.0/en/stop-slave.html

15.4.2.9 停止从库语句

STOP {SLAVE | REPLICA} [*thread_types*] [*channel_option*]

*thread_types*:
    [*thread_type* [, *thread_type*] ... ]

*thread_type*: IO_THREAD | SQL_THREAD

*channel_option*:
    FOR CHANNEL *channel*

停止复制线程。从 MySQL 8.0.22 开始,STOP SLAVE已被弃用,应改用别名STOP REPLICA。该语句的工作方式与以前相同,只是语句和输出的术语已更改。使用时,两个版本的语句都会更新相同的状态变量。请参阅STOP REPLICA的文档,了解该语句的描述。

15.4.3 用于控制组复制的 SQL 语句

原文:dev.mysql.com/doc/refman/8.0/en/replication-statements-group.html

15.4.3.1 启动 GROUP_REPLICATION 语句

15.4.3.2 停止 GROUP_REPLICATION 语句

本节提供了用于控制组复制的语句的信息。

原文:dev.mysql.com/doc/refman/8.0/en/start-group-replication.html

15.4.3.1 START GROUP_REPLICATION Statement

 START GROUP_REPLICATION
          [USER='*user_name*']
          [, PASSWORD='*user_pass*']
          [, DEFAULT_AUTH='*plugin_name*']

启动组复制。此语句需要GROUP_REPLICATION_ADMIN权限(或已弃用的SUPER权限)。如果设置了super_read_only=ON并且成员应作为主服务器加入,则一旦 Group Replication 成功启动,super_read_only将设置为OFF

参与单主模式组的服务器应使用skip_replica_start=ON。否则,服务器不允许作为辅助服务器加入组。

在 MySQL 8.0.21 及更高版本中,您可以使用USERPASSWORDDEFAULT_AUTH选项在START GROUP_REPLICATION语句中指定分布式恢复的用户凭据,如下所示:

  • USER: 用于分布式恢复的复制用户。有关设置此帐户的说明,请参见 Section 20.2.1.3, “User Credentials For Distributed Recovery”。如果指定了PASSWORD,则不能指定空字符串或 null 字符串,也不能省略USER选项。

  • PASSWORD: 复制用户帐户的密码。密码不能加密,但在查询日志中被掩码。

  • DEFAULT_AUTH: 用于复制用户帐户的身份验证插件的名称。如果不指定此选项,则假定使用 MySQL 本机身份验证(mysql_native_password插件)。此选项作为服务器的提示,并且在分布式恢复的捐赠者上,如果与用户帐户关联的不同插件,则会覆盖它。在 MySQL 8 中创建用户帐户时默认使用的身份验证插件是缓存 SHA-2 身份验证插件(caching_sha2_password)。有关身份验证插件的更多信息,请参见 Section 8.2.17, “Pluggable Authentication”。

这些凭据用于group_replication_recovery通道上的分布式恢复。当您在START GROUP_REPLICATION上指定用户凭据时,这些凭据仅保存在内存中,并且通过STOP GROUP_REPLICATION语句或服务器关闭而删除。您必须发出START GROUP_REPLICATION语句以再次提供凭据。因此,此方法与根据group_replication_start_on_boot系统变量在服务器启动时自动启动 Group Replication 不兼容。

START GROUP_REPLICATION中指定的用户凭据优先于使用CHANGE REPLICATION SOURCE TO语句(从 MySQL 8.0.23 开始)或CHANGE MASTER TO语句(MySQL 8.0.23 之前)为group_replication_recovery通道设置的任何用户凭据。请注意,使用这些语句设置的用户凭据存储在复制元数据存储库中,并且在未指定用户凭据的情况下指定START GROUP_REPLICATION时使用,包括如果group_replication_start_on_boot系统变量设置为ON时的自动启动。为了获得在START GROUP_REPLICATION上指定用户凭据的安全性好处,请确保group_replication_start_on_boot设置为OFF(默认为ON),并按照第 20.6.3 节,“保护分布式恢复连接”中的说明清除先前为group_replication_recovery通道设置的任何用户凭据。

当成员重新加入复制组时,在组完成兼容性检查并接受其为成员之前,其状态可能显示为OFFLINEERROR。当成员正在赶上组的事务时,其状态为RECOVERING

原文:dev.mysql.com/doc/refman/8.0/en/stop-group-replication.html

15.4.3.2 停止 GROUP_REPLICATION 语句

STOP GROUP_REPLICATION

停止 Group Replication。此语句需要 GROUP_REPLICATION_ADMIN 权限(或已弃用的 SUPER 权限)。一旦您发出 STOP GROUP_REPLICATION,该成员将被设置为 super_read_only=ON,这确保在 Group Replication 停止时无法对成员进行写入。该成员上运行的任何其他异步复制通道也将停止。在启动此成员上的 Group Replication 时在 START GROUP_REPLICATION 语句中指定的任何用户凭据将从内存中删除,并且在再次启动 Group Replication 时必须提供。

警告

使用此语句时要非常小心,因为它会将服务器实例从组中移除,这意味着它不再受到 Group Replication 一致性保证机制的保护。为了完全安全起见,在发出此语句之前,请确保您的应用程序无法连接到该实例,以避免任何陈旧读取的可能性。

STOP GROUP_REPLICATION 语句会停止组成员上的异步复制通道,但不像 STOP REPLICA 那样隐式提交正在进行的事务。这是因为在 Group Replication 组成员上,关闭操作期间提交的其他事务会使成员与组不一致,并导致重新加入时出现问题。为了避免在停止 Group Replication 时正在进行的事务失败提交,从 MySQL 8.0.28 开始,当将 GTID 分配为 gtid_next 系统变量的值时,不能发出 STOP GROUP_REPLICATION 语句。

group_replication_components_stop_timeout 系统变量指定了组复制在发出此语句后等待其各个模块完成正在进行的进程的时间。超时用于解决组复制组件无法正常停止的情况,这可能发生在成员在错误状态下被驱逐出组,或者在诸如 MySQL Enterprise Backup 正在持有成员表的全局锁时。在这种情况下,成员无法停止应用程序线程或完成分布式恢复过程以重新加入。STOP GROUP_REPLICATION 不会完成,直到情况得到解决(例如,通过释放锁),或者组件超时到期并且模块无论其状态如何都会关闭。在 MySQL 8.0.27 之前,默认组件超时为 31536000 秒,即 365 天。使用此设置,组件超时对于刚才描述的情况并不起作用,因此建议在这些 MySQL 8.0 版本中使用较低的设置。从 MySQL 8.0.27 开始,默认值为 300 秒;这意味着如果在此之前未解决情况,则在 5 分钟后停止组复制组件,从而允许成员重新启动并重新加入。

15.5 准备语句

原文:dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html

15.5.1 PREPARE 语句

15.5.2 EXECUTE 语句

15.5.3 DEALLOCATE PREPARE 语句

MySQL 8.0 支持服务器端准备语句。此支持利用了高效的客户端/服务器二进制协议。使用带有参数值占位符的准备语句具有以下优点:

  • 每次执行语句时解析语句的开销较小。通常,数据库应用程序处理大量几乎相同的语句,只有在查询和删除的WHERE、更新的SET以及插入的VALUES等子句中的文字或变量值发生变化。

  • 防止 SQL 注入攻击。参数值可以包含未转义的 SQL 引号和分隔符字符。

以下各节概述了准备语句的特性:

  • 应用程序中的准备语句

  • SQL 脚本中的准备语句

  • PREPARE、EXECUTE 和 DEALLOCATE PREPARE 语句

  • 准备语句中允许的 SQL 语法

应用程序中的准备语句

您可以通过客户端编程接口使用服务器端准备语句,包括用于 C 程序的 MySQL C API 客户端库,用于 Java 程序的 MySQL Connector/J,以及用于使用.NET 技术的程序的 MySQL Connector/NET。例如,C API 提供了一组构成其准备语句 API 的函数调用。请参阅 C API 准备语句接口。其他语言接口可以通过链接 C 客户端库提供支持,其中一个例子是mysqli扩展,可在 PHP 5.0 及更高版本中使用。

SQL 脚本中的准备语句

可用另一种 SQL 接口来处理准备语句。这种接口不如通过准备语句 API 使用二进制协议高效,但不需要编程,因为它直接在 SQL 级别提供:

  • 当您无法使用编程接口时,可以使用它。

  • 您可以从任何可以向服务器发送 SQL 语句以执行的程序中使用它,例如mysql客户端程序。

  • 即使客户端使用旧版本的客户端库,也可以使用它。

准备好的语句的 SQL 语法旨在用于以下情况:

  • 在编码之前测试准备好的语句在您的应用程序中的工作方式。

  • 当您没有访问支持它们的编程 API 时使用准备好的语句。

  • 与准备好的语句交互式地解决应用程序问题。

  • 创建一个重现准备好的语句问题的测试用例,以便您可以提交 bug 报告。

PREPARE、EXECUTE 和 DEALLOCATE PREPARE 语句

准备好的语句的 SQL 语法基于三个 SQL 语句:

  • PREPARE 为执行准备了一个语句(参见 Section 15.5.1, “PREPARE Statement”)。

  • EXECUTE 执行一个准备好的语句(参见 Section 15.5.2, “EXECUTE Statement”)。

  • DEALLOCATE PREPARE 释放一个准备好的语句(参见 Section 15.5.3, “DEALLOCATE PREPARE Statement”)。

以下示例展示了两种等效的准备好一个语句计算三角形的斜边长度的方法。

第一个示例展示了如何通过使用字符串文字来提供语句的文本来创建一个准备好的语句:

mysql> PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
mysql> SET @a = 3;
mysql> SET @b = 4;
mysql> EXECUTE stmt1 USING @a, @b;
+------------+
| hypotenuse |
+------------+
|          5 |
+------------+
mysql> DEALLOCATE PREPARE stmt1;

第二个示例类似,但将语句的文本作为用户变量提供:

mysql> SET @s = 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
mysql> PREPARE stmt2 FROM @s;
mysql> SET @a = 6;
mysql> SET @b = 8;
mysql> EXECUTE stmt2 USING @a, @b;
+------------+
| hypotenuse |
+------------+
|         10 |
+------------+
mysql> DEALLOCATE PREPARE stmt2;

以下是另一个示例,演示如何在运行时选择要执行查询的表,方法是将表名存储为用户变量:

mysql> USE test;
mysql> CREATE TABLE t1 (a INT NOT NULL);
mysql> INSERT INTO t1 VALUES (4), (8), (11), (32), (80);

mysql> SET @table = 't1';
mysql> SET @s = CONCAT('SELECT * FROM ', @table);

mysql> PREPARE stmt3 FROM @s;
mysql> EXECUTE stmt3;
+----+
| a  |
+----+
|  4 |
|  8 |
| 11 |
| 32 |
| 80 |
+----+

mysql> DEALLOCATE PREPARE stmt3;

准备好的语句特定于创建它的会话。如果您终止一个会话而没有取消分配先前准备好的语句,服务器会自动取消分配它。

准备好的语句也是会话全局的。如果您在存储过程中创建了一个准备好的语句,当存储过程结束时不会取消分配它。

为防止同时创建太多准备好的语句,设置 max_prepared_stmt_count 系统变量。要防止使用准备好的语句,将值设置为 0。

允许在准备好的语句中使用的 SQL 语法

以下 SQL 语句可用作准备好的语句:

ALTER TABLE
ALTER USER
ANALYZE TABLE
CACHE INDEX
CALL
CHANGE MASTER
CHECKSUM {TABLE | TABLES}
COMMIT
{CREATE | DROP} INDEX
{CREATE | RENAME | DROP} DATABASE
{CREATE | DROP} TABLE
{CREATE | RENAME | DROP} USER
{CREATE | DROP} VIEW
DELETE
DO
FLUSH {TABLE | TABLES | TABLES WITH READ LOCK | HOSTS | PRIVILEGES
  | LOGS | STATUS | MASTER | SLAVE | USER_RESOURCES}
GRANT
INSERT
INSTALL PLUGIN
KILL
LOAD INDEX INTO CACHE
OPTIMIZE TABLE
RENAME TABLE
REPAIR TABLE
REPLACE
RESET {MASTER | SLAVE}
REVOKE
SELECT
SET
SHOW BINLOG EVENTS
SHOW CREATE {PROCEDURE | FUNCTION | EVENT | TABLE | VIEW}
SHOW {MASTER | BINARY} LOGS
SHOW {MASTER | SLAVE} STATUS
SLAVE {START | STOP}
TRUNCATE TABLE
UNINSTALL PLUGIN
UPDATE

不支持其他语句。

为了符合 SQL 标准,该标准规定诊断语句不可准备,MySQL 不支持以下作为准备好的语句:

  • SHOW WARNINGSSHOW COUNT(*) WARNINGS

  • SHOW ERRORSSHOW COUNT(*) ERRORS

  • 包含对 warning_counterror_count 系统变量的任何引用的语句。

通常,SQL 预处理语句中不允许的语句在存储程序中也不允许。特殊情况在第 27.8 节,“存储程序的限制”中有说明。

当预处理语句引用的表或视图的元数据发生更改时,会检测到并在下次执行时自动重新准备该语句。更多信息,请参见第 10.10.3 节,“预处理语句和存储程序的缓存”。

使用预处理语句时,可以为LIMIT子句的参数使用占位符。参见第 15.2.13 节,“SELECT 语句”。

在与PREPAREEXECUTE一起使用的预处理CALL语句中,从 MySQL 8.0 开始支持OUTINOUT参数的占位符。请参见第 15.2.1 节,“CALL 语句”,以获取一个示例和早期版本的解决方法。无论版本如何,都可以为IN参数使用占位符。

预处理语句的 SQL 语法不能以嵌套方式使用。也就是说,传递给PREPARE的语句本身不能是PREPAREEXECUTEDEALLOCATE PREPARE语句。

预处理语句的 SQL 语法与使用预处理语句 API 调用是不同的。例如,你不能使用mysql_stmt_prepare() C API 函数来准备PREPAREEXECUTEDEALLOCATE PREPARE语句。

预处理语句的 SQL 语法可以在存储过程中使用,但不能在存储函数或触发器中使用。然而,不能对使用PREPAREEXECUTE准备和执行的动态语句使用游标。游标的语句在游标创建时进行检查,因此语句不能是动态的。

预处理语句的 SQL 语法不支持多语句(即,在一个字符串中由;字符分隔的多个语句)。

要编写使用CALL SQL 语句执行包含准备语句的存储过程的 C 程序,必须启用CLIENT_MULTI_RESULTS标志。这是因为每个CALL都会返回一个结果以指示调用状态,除了存储过程内执行的语句可能返回的任何结果集。

当您调用mysql_real_connect()时,可以通过显式传递CLIENT_MULTI_RESULTS标志或隐式传递CLIENT_MULTI_STATEMENTS(也会启用CLIENT_MULTI_RESULTS)来启用CLIENT_MULTI_RESULTS。有关更多信息,请参见第 15.2.1 节,“CALL 语句”。

15.5.1 准备语句

原文:dev.mysql.com/doc/refman/8.0/en/prepare.html

PREPARE *stmt_name* FROM *preparable_stmt*

PREPARE语句准备一个 SQL 语句,并分配一个名称stmt_name,以便以后引用该语句。准备好的语句通过EXECUTE执行,并通过DEALLOCATE PREPARE释放。有关示例,请参见第 15.5 节,“准备语句”。

语句名称不区分大小写。preparable_stmt可以是字符串文字或包含 SQL 语句文本的用户变量。文本必须表示单个语句,而不是多个语句。在语句内部,?字符可用作参数标记,指示稍后在执行时要将数据值绑定到查询的位置。?字符不应该被引号括起,即使您打算将它们绑定到字符串值。参数标记只能用于数据值应出现的地方,而不能用于 SQL 关键字、标识符等。

如果具有给定名称的准备语句已经存在,则在准备新语句之前会隐式释放它。这意味着如果新语句包含错误且无法准备,则会返回错误,并且不存在具有给定名称的语句。

准备语句的范围是创建它的会话,这有几个含义:

  • 在一个会话中创建的准备语句对其他会话不可用。

  • 当会话结束时,无论是正常结束还是异常结束,其准备的语句都不再存在。如果启用了自动重新连接,则客户端不会收到连接丢失的通知。因此,客户端可能希望禁用自动重新连接。请参见自动重新连接控制。

  • 在存储程序中创建的准备语句在程序执行完成后继续存在,并且可以在程序外部稍后执行。

  • 在存储程序上下文中准备的语句不能引用存储过程或函数参数或局部变量,因为它们在程序结束时超出范围,如果稍后在程序外执行该语句,则将无法使用。作为解决方法,可以引用会话范围的用户定义变量,参见第 11.4 节,“用户定义变量”。

从 MySQL 8.0.22 开始,用于准备语句的参数在语句首次准备时确定其类型,并且在为该准备语句调用EXECUTE时保留此类型(除非语句被重新准备,如本节后面所述)。确定参数类型的规则如下:

  • 作为二元算术运算符的操作数的参数具有与另一个操作数相同的数据类型。

  • 如果二元算术运算符的两个操作数都是参数,则参数的类型由运算符的上下文决定。

  • 如果参数是一元算术运算符的操作数,则参数的类型由运算符的上下文决定。

  • 如果算术运算符没有类型确定的上下文,则涉及的任何参数的派生类型为DOUBLE PRECISION。例如,当参数是SELECT列表中的顶层节点时,或者当它是比较运算符的一部分时,就会发生这种情况。

  • 作为字符串运算符的操作数的参数具有与其他操作数的聚合类型相同的派生类型。如果运算符的所有操作数都是参数,则派生类型为VARCHAR;其排序规则由collation_connection的值确定。

  • 作为时间运算符的操作数具有类型DATETIME,如果运算符返回DATETIME,则操作数的类型为TIME,如果运算符返回TIME,则操作数的类型为DATE,如果运算符返回DATE

  • 二元比较运算符的操作数具有与比较的另一个操作数相同的派生类型。

  • 作为BETWEEN等三元比较运算符的操作数的参数具有与其他操作数的聚合类型相同的派生类型。

  • 如果比较运算符的所有操作数都是参数,则它们每个的派生类型都是VARCHAR,其排序规则由collation_connection的值确定。

  • 作为CASECOALESCEIFIFNULLNULLIF的输出操作数之一的参数具有与运算符的其他输出操作数的聚合类型相同的派生类型。

  • 如果任何CASECOALESCEIFIFNULLNULLIF的所有输出操作数都是参数,或它们都是NULL,则参数的类型由运算符的上下文决定。

  • 如果参数是任何CASECOALESCE()IFIFNULL的操作数,并且没有类型确定的上下文,则涉及的每个参数的派生类型都是VARCHAR,其排序规则由collation_connection的值确定。

  • 作为CAST()的操作数的参数具有与CAST()指定的类型相同的类型。

  • 如果参数是不是INSERT语句的一部分的SELECT列表的直接成员,则参数的派生类型是VARCHAR,其排序规则由collation_connection的值确定。

  • 如果参数是INSERT](insert.html "15.2.7 INSERT Statement")语句的一部分的SELECT列表的直接成员,则参数的派生类型是将参数插入的相应列的类型。

  • 如果参数用作UPDATE语句的SET子句中的赋值的源,或者用作INSERT语句的ON DUPLICATE KEY UPDATE子句中的赋值的源,则参数的派生类型是由SETON DUPLICATE KEY UPDATE子句更新的相应列的类型。

  • 如果参数是函数的参数,则派生类型取决于函数的返回类型。

对于实际类型和派生类型的某些组合,会触发语句的自动重新准备,以确保与 MySQL 先前版本更紧密的兼容性。如果以下任一条件为真,则不会发生重新准备:

  • 使用NULL作为实际参数值。

  • 参数是CAST()的操作数。(相反,会尝试将其转换为派生类型,如果转换失败则引发异常。)

  • 参数是字符串。(在这种情况下,会执行隐式的CAST(? AS *derived_type*)。)

  • 参数的派生类型和实际类型都是INTEGER,并且符号相同。

  • 参数的派生类型是DECIMAL,实际类型是DECIMALINTEGER

  • 派生类型是DOUBLE,实际类型是任何数值类型。

  • 派生类型和实际类型都是字符串类型。

  • 如果派生类型是时间型,实际类型也是时间型。异常情况:派生类型是TIME,实际类型不是TIME;派生类型是DATE,实际类型不是DATE

  • 派生类型是时间型,实际类型是数值型。

对于除上述情况之外的情况,语句将重新准备,并使用实际参数类型而不是派生参数类型。

这些规则也适用于准备语句中引用的用户变量。

在准备语句中为给定参数或用户变量使用不同的数据类型,导致语句需要重新准备。这样效率较低;也可能导致参数(或变量)的实际类型变化,从而导致后续执行准备语句时结果不一致。因此,建议在重新执行准备语句时为给定参数使用相同的数据类型。

15.5.2 执行语句

原文:dev.mysql.com/doc/refman/8.0/en/execute.html

EXECUTE *stmt_name*
    [USING @*var_name* [, @*var_name*] ...]

在使用 PREPARE 准备语句之后,您可以使用一个 EXECUTE 语句来执行它,该语句引用了准备好的语句名称。如果准备的语句包含任何参数标记,您必须提供一个 USING 子句,列出包含要绑定到参数的值的用户变量。参数值只能由用户变量提供,并且 USING 子句必须命名与语句中参数标记数量完全相同的变量。

您可以多次执行给定的准备语句,向其传递不同的变量或在每次执行之前设置变量为不同的值。

有关示例,请参见 Section 15.5, “Prepared Statements”。

15.5.3 释放准备语句

原文:dev.mysql.com/doc/refman/8.0/en/deallocate-prepare.html

{DEALLOCATE | DROP} PREPARE *stmt_name*

要释放使用PREPARE生成的准备语句,使用一个引用准备语句名称的DEALLOCATE PREPARE语句。在释放准备语句后尝试执行它会导致错误。如果创建了太多准备语句,并且没有通过DEALLOCATE PREPARE语句或会话结束释放,您可能会遇到由max_prepared_stmt_count系统变量强制执行的上限。

有关示例,请参见第 15.5 节,“准备语句”。

15.6 复合语句语法

原文:dev.mysql.com/doc/refman/8.0/en/sql-compound-statements.html

15.6.1 BEGIN ... END 复合语句

15.6.2 语句标签

15.6.3 DECLARE 语句

15.6.4 存储程序中的变量

15.6.5 流程控制语句

15.6.6 游标

15.6.7 条件处理

15.6.8 条件处理的限制

本节描述了BEGIN ... END复合语句和其他可以在存储程序主体中使用的语句的语法:存储过程和函数、触发器和事件。这些对象是以 SQL 代码的形式定义的,存储在服务器上以供以后调用(参见第二十七章,存储对象)。

复合语句是一个可以包含其他块的块;变量声明、条件处理程序和游标;以及循环和条件测试等流程控制结构。

15.6.1 BEGIN ... END Compound Statement

原文:dev.mysql.com/doc/refman/8.0/en/begin-end.html

[*begin_label*:] BEGIN
    [*statement_list*]
END [*end_label*]

BEGIN ... END语法用于编写复合语句,这些语句可以出现在存储程序(存储过程和函数、触发器和事件)中。复合语句可以包含多个语句,由BEGINEND关键字括起来。statement_list表示一个或多个语句的列表,每个语句以分号(;)语句分隔符结尾。statement_list本身是可选的,因此空复合语句(BEGIN END)是合法的。

BEGIN ... END块可以嵌套。

使用多个语句需要客户端能够发送包含语句分隔符的语句字符串。在mysql命令行客户端中,可以使用delimiter命令处理这个问题。更改语句结束分隔符(例如,改为//)允许在程序体中使用。例如,请参阅 Section 27.1, “Defining Stored Programs”。

可以为BEGIN ... END块加标签。请参阅 Section 15.6.2, “Statement Labels”。

不支持可选的[NOT] ATOMIC子句。这意味着在指令块开始时不设置事务保存点,并且在此上下文中使用的BEGIN子句对当前事务没有影响。

注意

在所有存储程序中,解析器将BEGIN [WORK]视为BEGIN ... END块的开始。在这种情况下开始事务,请使用START TRANSACTION

15.6.2 语句标签

原文:dev.mysql.com/doc/refman/8.0/en/statement-labels.html

[*begin_label*:] BEGIN
    [*statement_list*]
END [*end_label*]

[*begin_label*:] LOOP
    *statement_list*
END LOOP [*end_label*]

[*begin_label*:] REPEAT
    *statement_list*
UNTIL *search_condition*
END REPEAT [*end_label*]

[*begin_label*:] WHILE *search_condition* DO
    *statement_list*
END WHILE [*end_label*]

标签允许用于 BEGIN ... END 块以及 LOOPREPEATWHILE 语句。这些语句的标签使用遵循以下规则:

  • begin_label 后必须跟着一个冒号。

  • begin_label 可以单独出现,不需要 end_label。如果有 end_label,它必须与 begin_label 相同。

  • end_label 不能单独出现,必须有 begin_label

  • 同一嵌套级别的标签必须是不同的。

  • 标签最多可以有 16 个字符长。

要引用标记结构内的标签,请使用 ITERATELEAVE 语句。以下示例使用这些语句来继续迭代或终止循环:

CREATE PROCEDURE doiterate(p1 INT)
BEGIN
  label1: LOOP
    SET p1 = p1 + 1;
    IF p1 < 10 THEN ITERATE label1; END IF;
    LEAVE label1;
  END LOOP label1;
END;

块标签的范围不包括在块内声明的处理程序的代码。有关详细信息,请参见 第 15.6.7.2 节,“DECLARE ... HANDLER Statement”。

15.6.3 声明语句

原文:dev.mysql.com/doc/refman/8.0/en/declare.html

DECLARE 语句用于定义程序内部的各种项目:

  • 局部变量。参见 第 15.6.4 节,“存储程序中的变量”。

  • 条件和处理程序。参见 第 15.6.7 节,“条件处理”。

  • 游标。参见 第 15.6.6 节,“游标”。

DECLARE 只允许在 BEGIN ... END 复合语句内部,并且必须位于其开始位置,在任何其他语句之前。

声明必须遵循一定的顺序。游标声明必须出现在处理程序声明之前。变量和条件声明必须出现在游标或处理程序声明之前。

15.6.4 存储程序中的变量

原文:dev.mysql.com/doc/refman/8.0/en/stored-program-variables.html

15.6.4.1 Local Variable DECLARE Statement

15.6.4.2 本地变量作用域和解析

系统变量和用户定义变量可以在存储程序中使用,就像在存储程序上下文之外使用一样。此外,存储程序可以使用DECLARE来定义本地变量,并且存储例程(过程和函数)可以声明接受参数,以在例程和其调用者之间传递值。

  • 要声明本地变量,请使用DECLARE语句,如 Section 15.6.4.1, “Local Variable DECLARE Statement”中所述。

  • 变量可以直接使用SET语句进行设置。参见 Section 15.7.6.1, “SET Syntax for Variable Assignment”。

  • 查询结果可以使用SELECT ... INTO *var_list*将其检索到本地变量中,或者通过打开游标并使用FETCH ... INTO *var_list*来实现。参见 Section 15.2.13.1, “SELECT ... INTO Statement”,以及 Section 15.6.6, “Cursors”。

有关本地变量的作用域以及 MySQL 如何解析模糊名称的信息,请参见 Section 15.6.4.2, “Local Variable Scope and Resolution”。

不允许将值DEFAULT分配给存储过程或函数参数或存储程序本地变量(例如使用SET *var_name* = DEFAULT语句)。在 MySQL 8.0 中,这将导致语法错误。

原文:dev.mysql.com/doc/refman/8.0/en/declare-local-variable.html

15.6.4.1 局部变量 DECLARE 语句

DECLARE *var_name* [, *var_name*] ... *type* [DEFAULT *value*]

此语句在存储程序中声明局部变量。要为变量提供默认值,请包含DEFAULT子句。该值可以指定为表达式;它不必是常量。如果缺少DEFAULT子句,则初始值为NULL

局部变量在数据类型和溢出检查方面与存储过程参数类似。请参阅第 15.1.17 节,“CREATE PROCEDURE 和 CREATE FUNCTION 语句”。

变量声明必须出现在游标或处理程序声明之前。

局部变量名称不区分大小写。允许的字符和引用规则与其他标识符相同,如第 11.2 节,“模式对象名称”中所述。

局部变量的作用域是其声明的BEGIN ... END块。该变量可以在声明块内嵌套的块中引用,除了那些声明具有相同名称变量的块。

有关变量声明的示例,请参阅第 15.6.4.2 节,“局部变量作用域和解析”。

原文:dev.mysql.com/doc/refman/8.0/en/local-variable-scope.html

15.6.4.2 本地变量作用域和解析

本地变量的作用域是其声明的 BEGIN ... END 块。该变量可以在声明块内嵌套的块中引用,除了那些声明具有相同名称变量的块。

因为本地变量仅在存储过程执行期间处于作用域内,所以不允许在存储过程内创建的准备语句中引用它们。准备语句的作用域是当前会话,而不是存储过程,因此该语句可能在程序结束后执行,此时变量将不再处于作用域内。例如,SELECT ... INTO *local_var* 不能作为准备语句使用。此限制也适用于存储过程和函数参数。参见 Section 15.5.1, “PREPARE Statement”。

本地变量不应与表列具有相同的名称。如果 SQL 语句(例如 SELECT ... INTO 语句)包含对具有相同名称的列和声明的本地变量的引用,MySQL 目前将该引用解释为变量的名称。考虑以下过程定义:

CREATE PROCEDURE sp1 (x VARCHAR(5))
BEGIN
  DECLARE xname VARCHAR(5) DEFAULT 'bob';
  DECLARE newname VARCHAR(5);
  DECLARE xid INT;

  SELECT xname, id INTO newname, xid
    FROM table1 WHERE xname = xname;
  SELECT newname;
END;

MySQL 在 SELECT 语句中将 xname 解释为 xname 变量的引用,而不是 xname 的引用。因此,当调用过程 sp1() 时,newname 变量返回值 'bob',而不管 table1.xname 列的值如何。

类似地,以下过程中的游标定义包含一个引用 xnameSELECT 语句。MySQL 将其解释为对该名称变量的引用,而不是列的引用。

CREATE PROCEDURE sp2 (x VARCHAR(5))
BEGIN
  DECLARE xname VARCHAR(5) DEFAULT 'bob';
  DECLARE newname VARCHAR(5);
  DECLARE xid INT;
  DECLARE done TINYINT DEFAULT 0;
  DECLARE cur1 CURSOR FOR SELECT xname, id FROM table1;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

  OPEN cur1;
  read_loop: LOOP
    FETCH FROM cur1 INTO newname, xid;
    IF done THEN LEAVE read_loop; END IF;
    SELECT newname;
  END LOOP;
  CLOSE cur1;
END;

另请参见 Section 27.8, “Restrictions on Stored Programs”。

15.6.5 流程控制语句

原文:dev.mysql.com/doc/refman/8.0/en/flow-control-statements.html

15.6.5.1 CASE 语句

15.6.5.2 IF 语句

15.6.5.3 ITERATE 语句

15.6.5.4 LEAVE 语句

15.6.5.5 LOOP 语句

15.6.5.6 REPEAT 语句

15.6.5.7 RETURN 语句

15.6.5.8 WHILE 语句

MySQL 支持 IFCASEITERATELEAVELOOPWHILEREPEAT 结构用于存储过程中的流程控制。它还支持存储函数中的 RETURN

许多这些结构包含其他语句,如下一节中的语法规范所示。这些结构可以嵌套。例如,一个 IF 语句可能包含一个 WHILE 循环,而该循环本身包含一个 CASE 语句。

MySQL 不支持 FOR 循环。

原文:dev.mysql.com/doc/refman/8.0/en/case.html

15.6.5.1 CASE Statement

CASE *case_value*
    WHEN *when_value* THEN *statement_list*
    [WHEN *when_value* THEN *statement_list*] ...
    [ELSE *statement_list*]
END CASE

或:

CASE
    WHEN *search_condition* THEN *statement_list*
    [WHEN *search_condition* THEN *statement_list*] ...
    [ELSE *statement_list*]
END CASE

存储程序的CASE语句实现了一个复杂的条件构造。

注意

还有一个与此处描述的CASE 语句不同的CASE 运算符。请参阅 Section 14.5, “Flow Control Functions”。CASE语句不能有ELSE NULL子句,并且以END CASE而不是END结束。

对于第一种语法,case_value是一个表达式。将此值与每个WHEN子句中的when_value表达式进行比较,直到找到相等的一个为止。找到相等的when_value后,执行相应的THEN子句statement_list。如果没有相等的when_value,则执行ELSE子句statement_list,如果有的话。

此语法不能用于与NULL进行相等性测试,因为NULL = NULL是错误的。请参阅 Section 5.3.4.6, “Working with NULL Values”。

对于第二种语法,每个WHEN子句search_condition表达式会被评估,直到其中一个为真,此时执行相应的THEN子句statement_list。如果没有相等的search_condition,则执行ELSE子句statement_list,如果有的话。

如果没有匹配被测试值的when_valuesearch_condition,并且CASE语句不包含ELSE子句,则会出现“CASE 语句未找到”错误。

每个statement_list由一个或多个 SQL 语句组成;不允许为空的statement_list

为处理没有任何WHEN子句匹配的情况,请使用包含空的BEGIN ... END块的ELSE,如本示例所示。(此处ELSE子句中使用的缩进仅为了清晰起见,否则并不重要。)

DELIMITER |

CREATE PROCEDURE p()
  BEGIN
    DECLARE v INT DEFAULT 1;

    CASE v
      WHEN 2 THEN SELECT v;
      WHEN 3 THEN SELECT 0;
      ELSE
        BEGIN
        END;
    END CASE;
  END;
  |

原文:dev.mysql.com/doc/refman/8.0/en/if.html

15.6.5.2 IF Statement

IF *search_condition* THEN *statement_list*
    [ELSEIF *search_condition* THEN *statement_list*] ...
    [ELSE *statement_list*]
END IF

用于存储程序的IF语句实现了一个基本的条件构造。

注意

还有一个IF() 函数,它与此处描述的IF 语句不同。请参阅第 14.5 节,“流程控制函数”。IF语句可以有THENELSEELSEIF子句,并以END IF结束。

如果给定的search_condition评估为 true,则相应的THENELSEIF子句statement_list执行。如果没有search_condition匹配,则ELSE子句statement_list执行。

每个statement_list由一个或多个 SQL 语句组成;不允许空的statement_list

一个IF ... END IF块,就像存储程序中使用的所有其他流程控制块一样,必须以分号结束,如本例所示:

DELIMITER //

CREATE FUNCTION SimpleCompare(n INT, m INT)
  RETURNS VARCHAR(20)

  BEGIN
    DECLARE s VARCHAR(20);

    IF n > m THEN SET s = '>';
    ELSEIF n = m THEN SET s = '=';
    ELSE SET s = '<';
    END IF;

    SET s = CONCAT(n, ' ', s, ' ', m);

    RETURN s;
  END //

DELIMITER ;

与其他流程控制结构一样,IF ... END IF块可以嵌套在其他流程控制结构中,包括其他IF语句。每个IF必须由其自己的END IF后跟一个分号来终止。您可以使用缩进使嵌套的流程控制块更容易被人类阅读(尽管 MySQL 不要求),如下所示:

DELIMITER //

CREATE FUNCTION VerboseCompare (n INT, m INT)
  RETURNS VARCHAR(50)

  BEGIN
    DECLARE s VARCHAR(50);

    IF n = m THEN SET s = 'equals';
    ELSE
      IF n > m THEN SET s = 'greater';
      ELSE SET s = 'less';
      END IF;

      SET s = CONCAT('is ', s, ' than');
    END IF;

    SET s = CONCAT(n, ' ', s, ' ', m, '.');

    RETURN s;
  END //

DELIMITER ;

在这个例子中,只有当n不等于m时,内部的IF才会被评估。

原文:dev.mysql.com/doc/refman/8.0/en/iterate.html

15.6.5.3 ITERATE Statement

ITERATE *label*

ITERATE 只能出现在 LOOPREPEATWHILE 语句内。ITERATE 意味着“重新开始循环”。

举例来说,参见第 15.6.5.5 节,“LOOP 语句”。

原文:dev.mysql.com/doc/refman/8.0/en/leave.html

15.6.5.4 LEAVE 语句

LEAVE *label*

此语句用于退出具有给定标签的流程控制结构。如果标签是最外层的存储程序块,LEAVE 将退出程序。

LEAVE 可以在 BEGIN ... END 或循环结构(LOOPREPEATWHILE)中使用。

例如,请参阅 Section 15.6.5.5, “LOOP Statement”。

原文:dev.mysql.com/doc/refman/8.0/en/loop.html

15.6.5.5 LOOP Statement

[*begin_label*:] LOOP
    *statement_list*
END LOOP [*end_label*]

LOOP 实现了一个简单的循环结构,允许对由一个或多个语句组成的语句列表重复执行,每个语句以分号 (;) 作为语句分隔符。循环内的语句将重复执行,直到循环被终止。通常,可以通过 LEAVE 语句来实现循环终止。在存储函数内部,也可以使用 RETURN 语句,该语句完全退出函数。

忽略包含循环终止语句会导致无限循环。

LOOP 语句可以被标记。有关标签使用规则,请参见 第 15.6.2 节,“语句标签”。

示例:

CREATE PROCEDURE doiterate(p1 INT)
BEGIN
  label1: LOOP
    SET p1 = p1 + 1;
    IF p1 < 10 THEN
      ITERATE label1;
    END IF;
    LEAVE label1;
  END LOOP label1;
  SET @x = p1;
END;

原文:dev.mysql.com/doc/refman/8.0/en/repeat.html

15.6.5.6 REPEAT Statement

[*begin_label*:] REPEAT
    *statement_list*
UNTIL *search_condition*
END REPEAT [*end_label*]

REPEAT语句中的语句列表将重复执行,直到search_condition表达式为真。因此,REPEAT总是至少进入循环一次。statement_list由一个或多个语句组成,每个语句以分号(;)作为语句分隔符。

一个REPEAT语句可以被标记。有关标签使用的规则,请参见第 15.6.2 节,“语句标签”。

示例:

mysql> delimiter //

mysql> CREATE PROCEDURE dorepeat(p1 INT)
       BEGIN
         SET @x = 0;
         REPEAT
           SET @x = @x + 1;
         UNTIL @x > p1 END REPEAT;
       END
       //
Query OK, 0 rows affected (0.00 sec)

mysql> CALL dorepeat(1000)//
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @x//
+------+
| @x   |
+------+
| 1001 |
+------+
1 row in set (0.00 sec)

原文:dev.mysql.com/doc/refman/8.0/en/return.html

15.6.5.7 RETURN Statement

RETURN *expr*

RETURN 语句终止存储函数的执行,并将值 expr 返回给函数调用者。存储函数中必须至少有一个 RETURN 语句。如果函数有多个退出点,则可能有多个。

此语句不在存储过程、触发器或事件中使用。LEAVE 语句可用于退出这些类型的存储程序。

原文:dev.mysql.com/doc/refman/8.0/en/while.html

15.6.5.8 WHILE Statement

[*begin_label*:] WHILE *search_condition* DO
    *statement_list*
END WHILE [*end_label*]

WHILE语句中的语句列表会重复执行,只要search_condition表达式为真。statement_list由一个或多个 SQL 语句组成,每个语句以分号(;)作为语句分隔符。

一个带标签的WHILE语句。关于标签使用的规则,请参见第 15.6.2 节,“语句标签”。

示例:

CREATE PROCEDURE dowhile()
BEGIN
  DECLARE v1 INT DEFAULT 5;

  WHILE v1 > 0 DO
    ...
    SET v1 = v1 - 1;
  END WHILE;
END;

15.6.6 游标

原文:dev.mysql.com/doc/refman/8.0/en/cursors.html

15.6.6.1 游标关闭语句

15.6.6.2 游标声明语句

15.6.6.3 游标获取语句

15.6.6.4 游标打开语句

15.6.6.5 服务器端游标的限制

MySQL 支持存储程序内的游标。语法与嵌入式 SQL 相同。游标具有以下属性:

  • Asensitive:服务器可能会或可能不会复制其结果表

  • 只读:不可更新

  • Nonscrollable:只能单向遍历,不能跳过行

游标声明必须出现在处理程序声明之前,并且在变量和条件声明之后。

示例:

CREATE PROCEDURE curdemo()
BEGIN
  DECLARE done INT DEFAULT FALSE;
  DECLARE a CHAR(16);
  DECLARE b, c INT;
  DECLARE cur1 CURSOR FOR SELECT id,data FROM test.t1;
  DECLARE cur2 CURSOR FOR SELECT i FROM test.t2;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

  OPEN cur1;
  OPEN cur2;

  read_loop: LOOP
    FETCH cur1 INTO a, b;
    FETCH cur2 INTO c;
    IF done THEN
      LEAVE read_loop;
    END IF;
    IF b < c THEN
      INSERT INTO test.t3 VALUES (a,b);
    ELSE
      INSERT INTO test.t3 VALUES (a,c);
    END IF;
  END LOOP;

  CLOSE cur1;
  CLOSE cur2;
END;

原文:dev.mysql.com/doc/refman/8.0/en/close.html

15.6.6.1 游标关闭语句

CLOSE *cursor_name*

此语句关闭先前打开的游标。示例请参见第 15.6.6 节,“游标”。

如果游标未打开,则会发生错误。

如果未显式关闭,游标将在声明它的BEGIN ... END块结束时关闭。

原文:dev.mysql.com/doc/refman/8.0/en/declare-cursor.html

15.6.6.2 游标 DECLARE 语句

DECLARE *cursor_name* CURSOR FOR *select_statement*

此语句声明一个游标,并将其与检索游标要遍历的行的SELECT语句相关联。要稍后获取行,请使用FETCH语句。SELECT语句检索的列数必须与FETCH语句中指定的输出变量数相匹配。

SELECT语句不能有INTO子句。

游标声明必须出现在处理程序声明之前,并且在变量和条件声明之后。

一个存储程序可能包含多个游标声明,但在给定块中声明的每个游标必须具有唯一名称。例如,请参阅第 15.6.6 节,“游标”。

通过SHOW语句提供的信息,在许多情况下可以通过使用带有INFORMATION_SCHEMA表的游标获得等效信息。

原文:dev.mysql.com/doc/refman/8.0/en/fetch.html

15.6.6.3 Cursor FETCH Statement

FETCH [[NEXT] FROM] *cursor_name* INTO *var_name* [, *var_name*] ...

该语句获取与指定游标关联的 SELECT 语句的下一行,并移动游标指针。如果存在一行,则获取的列将存储在命名变量中。SELECT 语句检索的列数必须与 FETCH 语句中指定的输出变量数相匹配。

如果没有更多的行可用,则会发生一个 SQLSTATE 值为 '02000' 的 No Data 条件。要检测这种情况,您可以为其设置一个处理程序(或者为 NOT FOUND 条件)。例如,请参见 Section 15.6.6, “Cursors”。

请注意,另一个操作,例如 SELECT 或另一个 FETCH,也可能通过引发相同的条件来导致处理程序执行。如果需要区分哪个操作引发了条件,请将操作放置在其自己的 BEGIN ... END 块中,以便将其与自己的处理程序关联起来。

原文:dev.mysql.com/doc/refman/8.0/en/open.html

15.6.6.4 游标打开语句

OPEN *cursor_name*

这个语句打开了先前声明的游标。例如,请参阅第 15.6.6 节,“游标”。

原文:dev.mysql.com/doc/refman/8.0/en/cursor-restrictions.html

15.6.6.5 服务器端游标的限制

服务器端游标在 C API 中使用mysql_stmt_attr_set()函数实现。存储过程中的游标也使用相同的实现。服务器端游标使得可以在服务器端生成结果集,但除了客户端请求的行之外,不会将其传输到客户端。例如,如果客户端执行查询但只对第一行感兴趣,则不会传输其余行。

在 MySQL 中,服务器端游标被实例化为内部临时表。最初,这是一个MEMORY表,但当其大小超过max_heap_table_sizetmp_table_size系统变量的最小值时,将转换为MyISAM表。为了保存游标的结果集而创建的内部临时表与其他用途的内部临时表一样受到相同的限制。参见 Section 10.4.4, “Internal Temporary Table Use in MySQL”。实现的一个限制是,对于大型结果集,通过游标检索其行可能会很慢。

游标是只读的;不能使用游标更新行。

UPDATE WHERE CURRENT OFDELETE WHERE CURRENT OF未实现,因为不支持可更新游标。

游标不可保持(在提交后不保持打开状态)。

游标是不敏感的。

游标是不可滚动的。

游标没有名称。语句处理程序充当游标 ID。

每个准备语句只能打开一个游标。如果需要多个游标,必须准备多个语句。

如果不支持准备模式中的语句,则不能对生成结果集的语句使用游标。这包括CHECK TABLEHANDLER READSHOW BINLOG EVENTS等语句。

15.6.7 条件处理

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

15.6.7.1 声明...条件语句

15.6.7.2 声明...处理程序语句

15.6.7.3 获取诊断信息语句

15.6.7.4 重新发出语句

15.6.7.5 发出语句

15.6.7.6 处理程序的作用域规则

15.6.7.7 MySQL 诊断区域

15.6.7.8 条件处理和 OUT 或 INOUT 参数

在存储过程执行过程中可能会出现需要特殊处理的情况,比如退出当前程序块或继续执行。可以为一般条件(如警告或异常)或特定条件(如特定错误代码)定义处理程序。特定条件可以被赋予名称,并在处理程序中引用。

要命名条件,请使用DECLARE ... CONDITION语句。要声明处理程序,请使用DECLARE ... HANDLER语句。请参见第 15.6.7.1 节,“声明...条件语句”和第 15.6.7.2 节,“声明...处理程序语句”。有关服务器在条件发生时如何选择处理程序的信息,请参见第 15.6.7.6 节,“处理程序的作用域规则”。

要引发条件,请使用SIGNAL语句。要在条件处理程序中修改条件信息,请使用RESIGNAL。请参见第 15.6.7.1 节,“声明...条件语句”和第 15.6.7.2 节,“声明...处理程序语句”。

要从诊断区域检索信息,请使用GET DIAGNOSTICS语句(参见第 15.6.7.3 节,“获取诊断信息语句”)。有关诊断区域的信息,请参见第 15.6.7.7 节,“MySQL 诊断区域”。

原文:dev.mysql.com/doc/refman/8.0/en/declare-condition.html

15.6.7.1 DECLARE ... CONDITION Statement

DECLARE *condition_name* CONDITION FOR *condition_value*

*condition_value*: {
    *mysql_error_code*
  | SQLSTATE [VALUE] *sqlstate_value*
}

DECLARE ... CONDITION 语句声明了一个命名的错误条件,将一个名称与需要特定处理的条件关联起来。该名称可以在随后的 DECLARE ... HANDLER 语句中引用(参见 Section 15.6.7.2, “DECLARE ... HANDLER Statement”)。

条件声明必须出现在游标或处理程序声明之前。

DECLARE ... CONDITIONcondition_value 指示与条件名称关联的特定条件或条件类。它可以采用以下形式:

  • mysql_error_code: 表示 MySQL 错误代码的整数文字。

    不要使用 MySQL 错误代码 0,因为这表示成功而不是错误条件。有关 MySQL 错误代码的列表,请参见 Server Error Message Reference。

  • SQLSTATE [VALUE] sqlstate_value: 一个表示 SQLSTATE 值的 5 个字符的字符串文字。

    不要使用以 '00' 开头的 SQLSTATE 值,因为这些值表示成功而不是错误条件。有关 SQLSTATE 值的列表,请参见 Server Error Message Reference。

SIGNAL 或使用 RESIGNAL 语句中引用的条件名称必须与 SQLSTATE 值关联,而不是 MySQL 错误代码。

使用条件名称可以帮助使存储过程代码更清晰。例如,此处理程序适用于尝试删除不存在的表,但只有当您知道 1051 是 MySQL 错误代码“未知表”时才明显:

DECLARE CONTINUE HANDLER FOR 1051
  BEGIN
    -- body of handler
  END;

通过为条件声明一个名称,处理程序的目的更容易看到:

DECLARE no_such_table CONDITION FOR 1051;
DECLARE CONTINUE HANDLER FOR no_such_table
  BEGIN
    -- body of handler
  END;

这里有一个命名条件,与相同条件相对应,但基于相应的 SQLSTATE 值而不是 MySQL 错误代码:

DECLARE no_such_table CONDITION FOR SQLSTATE '42S02';
DECLARE CONTINUE HANDLER FOR no_such_table
  BEGIN
    -- body of handler
  END;

原文:dev.mysql.com/doc/refman/8.0/en/declare-handler.html

15.6.7.2 DECLARE ... HANDLER Statement

DECLARE *handler_action* HANDLER
    FOR *condition_value* [, *condition_value*] ...
    *statement*

*handler_action*: {
    CONTINUE
  | EXIT
  | UNDO
}

*condition_value*: {
    *mysql_error_code*
  | SQLSTATE [VALUE] *sqlstate_value*
  | *condition_name*
  | SQLWARNING
  | NOT FOUND
  | SQLEXCEPTION
}

DECLARE ... HANDLER 语句指定处理一个或多个条件的处理程序。如果其中一个条件发生,则执行指定的 statementstatement 可以是一个简单语句,如 SET *var_name* = *value*,或者使用 BEGINEND 编写的复合语句(参见 Section 15.6.1, “BEGIN ... END Compound Statement”)。

处理程序声明必须出现在变量或条件声明之后。

handler_action 值表示处理程序在执行处理程序语句后采取的操作:

  • CONTINUE:当前程序的执行继续。

  • EXIT:执行终止于声明处理程序的 BEGIN ... END 复合语句。即使条件发生在内部块中,这也是正确的。

  • UNDO:不支持。

DECLARE ... HANDLERcondition_value 指示激活处理程序的特定条件或条件类别。它可以采用以下形式:

  • mysql_error_code:表示 MySQL 错误代码的整数文字,例如 1051 表示“未知表”:

    DECLARE CONTINUE HANDLER FOR 1051
      BEGIN
        -- body of handler
      END;
    

    不要使用 MySQL 错误代码 0,因为这表示成功而不是错误条件。有关 MySQL 错误代码的列表,请参阅 Server Error Message Reference。

  • SQLSTATE [VALUE] sqlstate_value:表示 SQLSTATE 值的 5 个字符字符串文字,例如 '42S01' 表示“未知表”:

    DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02'
      BEGIN
        -- body of handler
      END;
    

    不要使用以 '00' 开头的 SQLSTATE 值,因为这些值表示成功而不是错误条件。有关 SQLSTATE 值的列表,请参阅 Server Error Message Reference。

  • condition_name:先前使用 DECLARE ... CONDITION 指定的条件名。条件名可以与 MySQL 错误代码或 SQLSTATE 值关联。请参阅 Section 15.6.7.1, “DECLARE ... CONDITION Statement”。

  • SQLWARNING:简写为以 '01' 开头的 SQLSTATE 值类别。

    DECLARE CONTINUE HANDLER FOR SQLWARNING
      BEGIN
        -- body of handler
      END;
    
  • NOT FOUND:简写为以 '02' 开头的 SQLSTATE 值类别。在游标的上下文中相关,并用于控制当游标到达数据集末尾时发生的情况。如果没有更多行可用,则会发生无数据条件,其 SQLSTATE 值为 '02000'。要检测此条件,可以为其或为 NOT FOUND 条件设置处理程序。

    DECLARE CONTINUE HANDLER FOR NOT FOUND
      BEGIN
        -- body of handler
      END;
    

    举个例子,参见第 15.6.6 节“游标”。NOT FOUND条件也适用于检索不到行的SELECT ... INTO *var_list*语句。

  • SQLEXCEPTION:SQLSTATE 值的类别的简写,这些值不以'00''01''02'开头。

    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
      BEGIN
        -- body of handler
      END;
    

有关服务器在条件发生时选择处理程序的信息,请参见第 15.6.7.6 节“处理程序的作用域规则”。

如果发生未声明处理程序的条件,则采取的操作取决于条件类别:

  • 对于SQLEXCEPTION条件,存储过程在引发条件的语句处终止,就好像有一个EXIT处理程序一样。如果程序是由另一个存储过程调用的,则调用程序使用处理程序选择规则处理条件。

  • 对于SQLWARNING条件,程序会继续执行,就好像有一个CONTINUE处理程序一样。

  • 对于NOT FOUND条件,如果条件正常引发,操作是CONTINUE。如果是由SIGNALRESIGNAL引发的,操作是EXIT

以下示例使用了一个SQLSTATE '23000'的处理程序,该处理程序用于处理重复键错误:

mysql> CREATE TABLE test.t (s1 INT, PRIMARY KEY (s1));
Query OK, 0 rows affected (0.00 sec)

mysql> delimiter //

mysql> CREATE PROCEDURE handlerdemo ()
       BEGIN
         DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1;
         SET @x = 1;
         INSERT INTO test.t VALUES (1);
         SET @x = 2;
         INSERT INTO test.t VALUES (1);
         SET @x = 3;
       END;
       //
Query OK, 0 rows affected (0.00 sec)

mysql> CALL handlerdemo()//
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @x//
 +------+
 | @x   |
 +------+
 | 3    |
 +------+
    1 row in set (0.00 sec)

注意,在过程执行后,@x3,这表明在错误发生后,执行继续到过程结束。如果DECLARE ... HANDLER语句不存在,MySQL 会在第二个由于PRIMARY KEY约束而导致的INSERT失败后采取默认操作(EXIT),SELECT @x将返回2

要忽略一个条件,为其声明一个CONTINUE处理程序,并将其与一个空块关联。例如:

DECLARE CONTINUE HANDLER FOR SQLWARNING BEGIN END;

块标签的作用域不包括在块内声明的处理程序的代码。因此,与处理程序关联的语句不能使用ITERATELEAVE来引用包围处理程序声明的块的标签。考虑以下示例,其中REPEAT块具有retry标签:

CREATE PROCEDURE p ()
BEGIN
  DECLARE i INT DEFAULT 3;
  retry:
    REPEAT
      BEGIN
        DECLARE CONTINUE HANDLER FOR SQLWARNING
          BEGIN
            ITERATE retry;    # illegal
          END;
        IF i < 0 THEN
          LEAVE retry;        # legal
        END IF;
        SET i = i - 1;
      END;
    UNTIL FALSE END REPEAT;
END;

retry标签在块内的IF语句中有效。它对于CONTINUE处理程序无效,因此那里的引用是无效的,会导致错误:

ERROR 1308 (42000): LEAVE with no matching label: retry

为了避免在处理程序中引用外部标签,可以使用以下策略之一:

  • 要离开块,请使用EXIT处理程序。如果不需要块清理,BEGIN ... END处理程序主体可以为空:

    DECLARE EXIT HANDLER FOR SQLWARNING BEGIN END;
    

    否则,在处理程序主体中放置清理语句:

    DECLARE EXIT HANDLER FOR SQLWARNING
      BEGIN
        *block cleanup statements*
      END;
    
  • 要继续执行,请在CONTINUE处理程序中设置一个状态变量,该变量可以在封闭块中进行检查,以确定处理程序是否被调用。以下示例使用变量done来实现这一目的:

    CREATE PROCEDURE p ()
    BEGIN
      DECLARE i INT DEFAULT 3;
      DECLARE done INT DEFAULT FALSE;
      retry:
        REPEAT
          BEGIN
            DECLARE CONTINUE HANDLER FOR SQLWARNING
              BEGIN
                SET done = TRUE;
              END;
            IF done OR i < 0 THEN
              LEAVE retry;
            END IF;
            SET i = i - 1;
          END;
        UNTIL FALSE END REPEAT;
    END;
    

原文:dev.mysql.com/doc/refman/8.0/en/get-diagnostics.html

15.6.7.3 获取诊断信息语句

GET [CURRENT | STACKED] DIAGNOSTICS {
    *statement_information_item*
    [, *statement_information_item*] ...
  | CONDITION *condition_number*
    *condition_information_item*
    [, *condition_information_item*] ...
}

*statement_information_item*:
    *target* = *statement_information_item_name*

*condition_information_item*:
    *target* = *condition_information_item_name*

*statement_information_item_name*: {
    NUMBER
  | ROW_COUNT
}

*condition_information_item_name*: {
    CLASS_ORIGIN
  | SUBCLASS_ORIGIN
  | RETURNED_SQLSTATE
  | MESSAGE_TEXT
  | MYSQL_ERRNO
  | CONSTRAINT_CATALOG
  | CONSTRAINT_SCHEMA
  | CONSTRAINT_NAME
  | CATALOG_NAME
  | SCHEMA_NAME
  | TABLE_NAME
  | COLUMN_NAME
  | CURSOR_NAME
}

*condition_number*, *target*:
    (see following discussion)

SQL 语句生成填充诊断区域的诊断信息。获取诊断信息语句使应用程序能够检查这些信息。(您还可以使用显示警告显示错误来查看条件或错误。)

执行获取诊断信息不需要特殊权限。

关键字CURRENT表示从当前诊断区域检索信息。关键字STACKED表示从第二诊断区域检索信息,仅当当前上下文为条件处理程序时才可用。如果未给出任何关键字,则默认使用当前诊断区域。

获取诊断信息语句通常在存储程序内的处理程序中使用。这是 MySQL 的一个扩展,允许在处理程序上下文之外使用获取[当前]诊断信息来检查任何 SQL 语句的执行。例如,如果调用mysql客户端程序,则可以在提示符下输入这些语句:

mysql> DROP TABLE test.no_such_table;
ERROR 1051 (42S02): Unknown table 'test.no_such_table'
mysql> GET DIAGNOSTICS CONDITION 1
         @p1 = RETURNED_SQLSTATE, @p2 = MESSAGE_TEXT;
mysql> SELECT @p1, @p2;
+-------+------------------------------------+
| @p1   | @p2                                |
+-------+------------------------------------+
| 42S02 | Unknown table 'test.no_such_table' |
+-------+------------------------------------+

此扩展仅适用于当前诊断区域。它不适用于第二诊断区域,因为只有在当前上下文为条件处理程序时才允许使用获取堆叠诊断信息。如果不是这种情况,则会发生处理程序未激活时获取堆叠诊断信息错误。

有关诊断区域的描述,请参阅第 15.6.7.7 节,“MySQL 诊断区域”。简而言之,它包含两种信息:

  • 语句信息,如发生的条件数或受影响行数。

  • 条件信息,如错误代码和消息。如果语句引发多个条件,则诊断区域的此部分为每个条件区域。如果语句未引发任何条件,则诊断区域的此部分为空。

对于产生三个条件的语句,诊断区域包含如下语句和条件信息:

Statement information:
  row count
  ... other statement information items ...
Condition area list:
  Condition area 1:
    error code for condition 1
    error message for condition 1
    ... other condition information items ...
  Condition area 2:
    error code for condition 2:
    error message for condition 2
    ... other condition information items ...
  Condition area 3:
    error code for condition 3
    error message for condition 3
    ... other condition information items ...

获取诊断信息可以获取语句或条件信息,但不能在同一语句中同时获取两者:

  • 要获取语句信息,请将所需的语句项检索到目标变量中。此获取诊断信息实例将可用条件数和受影响行数分配给用户变量@p1@p2

    GET DIAGNOSTICS @p1 = NUMBER, @p2 = ROW_COUNT;
    
  • 要获取条件信息,请指定条件编号,并将所需的条件项检索到目标变量中。GET DIAGNOSTICS的这个实例将 SQLSTATE 值和错误消息分配给用户变量@p3@p4

    GET DIAGNOSTICS CONDITION 1
      @p3 = RETURNED_SQLSTATE, @p4 = MESSAGE_TEXT;
    

检索列表指定一个或多个*target* = *item_name*赋值,用逗号分隔。每个赋值命名一个目标变量,要么是statement_information_item_name,要么是condition_information_item_name标识符,取决于语句检索语句还是条件信息。

用于存储项目信息的有效target标识符可以是存储过程或函数参数,使用DECLARE声明的存储程序本地变量,或用户定义变量。

有效的condition_number标识符可以是存储过程或函数参数,使用DECLARE声明的存储程序本地变量,用户定义变量,系统变量或文字。如果条件编号不在具有信息的条件区域数量范围内,则会发出警告。在这种情况下,警告将添加到诊断区域而不清除它。

当发生条件时,MySQL 不会填充GET DIAGNOSTICS识别的所有条件项。例如:

mysql> GET DIAGNOSTICS CONDITION 1
         @p5 = SCHEMA_NAME, @p6 = TABLE_NAME;
mysql> SELECT @p5, @p6;
+------+------+
| @p5  | @p6  |
+------+------+
|      |      |
+------+------+

在标准 SQL 中,如果存在多个条件,则第一个条件与前一个 SQL 语句返回的SQLSTATE值相关。在 MySQL 中,这并不保证。要获取主要错误,不能这样做:

GET DIAGNOSTICS CONDITION 1 @errno = MYSQL_ERRNO;

相反,首先检索条件计数,然后使用它指定要检查的条件编号:

GET DIAGNOSTICS @cno = NUMBER;
GET DIAGNOSTICS CONDITION @cno @errno = MYSQL_ERRNO;

有关允许的语句和条件信息项以及在发生条件时哪些信息项被填充的信息,请参阅诊断区域信息项。

这是一个在存储过程上下文中使用GET DIAGNOSTICS和异常处理程序来评估插入操作结果的示例。如果插入成功,该过程使用GET DIAGNOSTICS获取受影响行数。这表明只要当前诊断区域未被清除,您可以多次使用GET DIAGNOSTICS来检索有关语句的信息。

CREATE PROCEDURE do_insert(value INT)
BEGIN
  -- Declare variables to hold diagnostics area information
  DECLARE code CHAR(5) DEFAULT '00000';
  DECLARE msg TEXT;
  DECLARE nrows INT;
  DECLARE result TEXT;
  -- Declare exception handler for failed insert
  DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    BEGIN
      GET DIAGNOSTICS CONDITION 1
        code = RETURNED_SQLSTATE, msg = MESSAGE_TEXT;
    END;

  -- Perform the insert
  INSERT INTO t1 (int_col) VALUES(value);
  -- Check whether the insert was successful
  IF code = '00000' THEN
    GET DIAGNOSTICS nrows = ROW_COUNT;
    SET result = CONCAT('insert succeeded, row count = ',nrows);
  ELSE
    SET result = CONCAT('insert failed, error = ',code,', message = ',msg);
  END IF;
  -- Say what happened
  SELECT result;
END;

假设t1.int_col是声明为NOT NULL的整数列。当调用该过程以插入非NULLNULL值时,该过程产生以下结果:

mysql> CALL do_insert(1);
+---------------------------------+
| result                          |
+---------------------------------+
| insert succeeded, row count = 1 |
+---------------------------------+

mysql> CALL do_insert(NULL);
+-------------------------------------------------------------------------+
| result                                                                  |
+-------------------------------------------------------------------------+
| insert failed, error = 23000, message = Column 'int_col' cannot be null |
+-------------------------------------------------------------------------+

当条件处理程序激活时,会发生对诊断区域堆栈的推送:

  • 第一个(当前)诊断区域变为第二个(堆叠)诊断区域,并创建一个新的当前诊断区域作为其副本。

  • GET [CURRENT] DIAGNOSTICSGET STACKED DIAGNOSTICS可以在处理程序内部使用,以访问当前诊断区域和堆叠诊断区域的内容。

  • 最初,两个诊断区域返回相同的结果,因此可以从当前诊断区域获取有关激活处理程序的条件的信息,只要在处理程序内不执行更改其当前诊断区域的语句。

  • 然而,在处理程序内执行的语句可以修改当前诊断区域,根据正常规则清除和设置其内容(参见诊断区域如何清除和填充)。

    获取有关激活处理程序条件的更可靠方法是使用堆叠的诊断区域,除了RESIGNAL之外,处理程序内执行的语句无法修改它。有关当前诊断区域何时设置和清除的信息,请参见第 15.6.7.7 节,“MySQL 诊断区域”。

下一个示例展示了如何在处理程序内部使用GET STACKED DIAGNOSTICS来获取有关已处理异常的信息,即使当前诊断区域已被处理程序语句修改。

在存储过程p()中,我们尝试向包含TEXT NOT NULL列的表中插入两个值。第一个值是非NULL字符串,第二个是NULL。该列禁止NULL值,因此第一个插入成功,但第二个导致异常。该过程包括一个异常处理程序,将尝试插入NULL映射为插入空字符串:

DROP TABLE IF EXISTS t1;
CREATE TABLE t1 (c1 TEXT NOT NULL);
DROP PROCEDURE IF EXISTS p;
delimiter //
CREATE PROCEDURE p ()
BEGIN
  -- Declare variables to hold diagnostics area information
  DECLARE errcount INT;
  DECLARE errno INT;
  DECLARE msg TEXT;
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    -- Here the current DA is nonempty because no prior statements
    -- executing within the handler have cleared it
    GET CURRENT DIAGNOSTICS CONDITION 1
      errno = MYSQL_ERRNO, msg = MESSAGE_TEXT;
    SELECT 'current DA before mapped insert' AS op, errno, msg;
    GET STACKED DIAGNOSTICS CONDITION 1
      errno = MYSQL_ERRNO, msg = MESSAGE_TEXT;
    SELECT 'stacked DA before mapped insert' AS op, errno, msg;

    -- Map attempted NULL insert to empty string insert
    INSERT INTO t1 (c1) VALUES('');

    -- Here the current DA should be empty (if the INSERT succeeded),
    -- so check whether there are conditions before attempting to
    -- obtain condition information
    GET CURRENT DIAGNOSTICS errcount = NUMBER;
    IF errcount = 0
    THEN
      SELECT 'mapped insert succeeded, current DA is empty' AS op;
    ELSE
      GET CURRENT DIAGNOSTICS CONDITION 1
        errno = MYSQL_ERRNO, msg = MESSAGE_TEXT;
      SELECT 'current DA after mapped insert' AS op, errno, msg;
    END IF ;
    GET STACKED DIAGNOSTICS CONDITION 1
      errno = MYSQL_ERRNO, msg = MESSAGE_TEXT;
    SELECT 'stacked DA after mapped insert' AS op, errno, msg;
  END;
  INSERT INTO t1 (c1) VALUES('string 1');
  INSERT INTO t1 (c1) VALUES(NULL);
END;
//
delimiter ;
CALL p();
SELECT * FROM t1;

当处理程序激活时,当前诊断区域的副本被推送到诊断区域堆栈。处理程序首先显示当前诊断区域和堆叠诊断区域的内容,最初两者都相同:

+---------------------------------+-------+----------------------------+
| op                              | errno | msg                        |
+---------------------------------+-------+----------------------------+
| current DA before mapped insert |  1048 | Column 'c1' cannot be null |
+---------------------------------+-------+----------------------------+

+---------------------------------+-------+----------------------------+
| op                              | errno | msg                        |
+---------------------------------+-------+----------------------------+
| stacked DA before mapped insert |  1048 | Column 'c1' cannot be null |
+---------------------------------+-------+----------------------------+

GET DIAGNOSTICS语句之后执行的语句可能会重置当前诊断区域。例如,处理程序将NULL插入映射为空字符串插入并显示结果。新插入成功并清除当前诊断区域,但堆叠的诊断区域保持不变,仍然包含激活处理程序的条件信息:

+----------------------------------------------+
| op                                           |
+----------------------------------------------+
| mapped insert succeeded, current DA is empty |
+----------------------------------------------+

+--------------------------------+-------+----------------------------+
| op                             | errno | msg                        |
+--------------------------------+-------+----------------------------+
| stacked DA after mapped insert |  1048 | Column 'c1' cannot be null |
+--------------------------------+-------+----------------------------+

当条件处理程序结束时,其当前诊断区域从堆栈中弹出,堆叠的诊断区域成为存储过程中的当前诊断区域。

执行完该过程后,表中包含两行。空行是由于尝试插入NULL而映射到空字符串插入导致的:

+----------+
| c1       |
+----------+
| string 1 |
|          |
+----------+

在上面的示例中,在条件处理程序中的前两个GET DIAGNOSTICS语句中,从当前和堆叠的诊断区域检索信息的返回值相同。如果在处理程序内部较早执行重置当前诊断区域的语句,则情况就不同了。假设p()被重写为将DECLARE语句放在处理程序定义内部而不是在其之前:

CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    -- Declare variables to hold diagnostics area information
    DECLARE errcount INT;
    DECLARE errno INT;
    DECLARE msg TEXT;
    GET CURRENT DIAGNOSTICS CONDITION 1
      errno = MYSQL_ERRNO, msg = MESSAGE_TEXT;
    SELECT 'current DA before mapped insert' AS op, errno, msg;
    GET STACKED DIAGNOSTICS CONDITION 1
      errno = MYSQL_ERRNO, msg = MESSAGE_TEXT;
    SELECT 'stacked DA before mapped insert' AS op, errno, msg;
...

在这种情况下,结果取决于版本:

  • 在 MySQL 5.7.2 之前,DECLARE不会更改当前诊断区域,因此前两个GET DIAGNOSTICS语句返回相同的结果,就像在p()的原始版本中一样。

    在 MySQL 5.7.2 中,已经做了工作以确保所有非诊断语句填充诊断区域,符合 SQL 标准。DECLARE是其中之一,因此在 5.7.2 及更高版本中,执行处理程序开头的DECLARE语句会清除当前诊断区域,并且GET DIAGNOSTICS语句会产生不同的结果:

    +---------------------------------+-------+------+
    | op                              | errno | msg  |
    +---------------------------------+-------+------+
    | current DA before mapped insert |  NULL | NULL |
    +---------------------------------+-------+------+
    
    +---------------------------------+-------+----------------------------+
    | op                              | errno | msg                        |
    +---------------------------------+-------+----------------------------+
    | stacked DA before mapped insert |  1048 | Column 'c1' cannot be null |
    +---------------------------------+-------+----------------------------+
    

在条件处理程序中避免此问题时,当试图获取激活处理程序的条件的信息时,请确保访问堆叠的诊断区域,而不是当前诊断区域。

原文:dev.mysql.com/doc/refman/8.0/en/resignal.html

15.6.7.4 RESIGNAL 语句

RESIGNAL [*condition_value*]
    [SET *signal_information_item*
    [, *signal_information_item*] ...]

*condition_value*: {
    SQLSTATE [VALUE] *sqlstate_value*
  | *condition_name*
}

*signal_information_item*:
    *condition_information_item_name* = *simple_value_specification*

*condition_information_item_name*: {
    CLASS_ORIGIN
  | SUBCLASS_ORIGIN
  | MESSAGE_TEXT
  | MYSQL_ERRNO
  | CONSTRAINT_CATALOG
  | CONSTRAINT_SCHEMA
  | CONSTRAINT_NAME
  | CATALOG_NAME
  | SCHEMA_NAME
  | TABLE_NAME
  | COLUMN_NAME
  | CURSOR_NAME
}

*condition_name*, *simple_value_specification*:
    (see following discussion)

RESIGNAL 传递在存储过程、函数、触发器或事件内部的复合语句中执行条件处理程序期间可用的错误条件信息。RESIGNAL 可能在传递信息之前更改部分或全部信息。RESIGNALSIGNAL 相关,但与 SIGNAL 不同,RESIGNAL 转发现有的条件信息,可能在修改后传递。

RESIGNAL 使得处理错误并返回错误信息成为可能。否则,在处理程序内执行 SQL 语句时,导致处理程序激活的信息将被销毁。RESIGNAL 还可以使一些过程变得更短,如果给定的处理程序可以处理部分情况,然后将条件“传递给上一级”到另一个处理程序。

执行 RESIGNAL 语句不需要特权。

所有形式的 RESIGNAL 需要当前上下文为条件处理程序。否则,RESIGNAL 是非法的,会出现 RESIGNAL when handler not active 错误。

要从诊断区域检索信息,请使用 GET DIAGNOSTICS 语句(参见 Section 15.6.7.3, “GET DIAGNOSTICS Statement”)。有关诊断区域的信息,请参阅 Section 15.6.7.7, “The MySQL Diagnostics Area”。

  • RESIGNAL 概述

  • 单独使用 RESIGNAL

  • 带有新信号信息的 RESIGNAL

  • 带有条件值和可选新信号信息的 RESIGNAL

  • RESIGNAL 需要条件处理程序上下文

RESIGNAL 概述

对于condition_valuesignal_information_itemRESIGNAL的定义和规则与SIGNAL相同。例如,condition_value可以是SQLSTATE值,该值可以指示错误、警告或“未找到”。有关更多信息,请参阅第 15.6.7.5 节,“SIGNAL Statement”。

RESIGNAL语句接受condition_valueSET子句,两者都是可选的。这导致了几种可能的用法:

  • 单独的RESIGNAL

    RESIGNAL;
    
  • RESIGNAL带有新的信号信息:

    RESIGNAL SET *signal_information_item* [, *signal_information_item*] ...;
    
  • 带有条件值和可能的新信号信息的RESIGNAL

    RESIGNAL *condition_value*
        [SET *signal_information_item* [, *signal_information_item*] ...];
    

这些用例都会导致诊断和条件区域的更改:

  • 诊断区包含一个或多个条件区域。

  • 条件区域包含条件信息项,例如SQLSTATE值、MYSQL_ERRNOMESSAGE_TEXT

有一个诊断区堆栈。当处理程序控制时,它会将诊断区推送到堆栈顶部,因此在处理程序执行期间有两个诊断区:

  • 第一个(当前)诊断区,最初是最后一个诊断区的副本,但会被第一个改变当前诊断区的处理程序中的第一个语句覆盖。

  • 最后一个(堆叠的)诊断区,其中包含在处理程序控制之前设置的条件区域。

诊断区中条件区域的最大数量由max_error_count系统变量的值确定。请参阅诊断区相关系统变量。

单独的 RESIGNAL

简单的RESIGNAL单独表示“不做任何更改地传递错误”。它恢复最后的诊断区并将其设置为当前诊断区。也就是说,它“弹出”诊断区堆栈。

在捕获条件的条件处理程序中,RESIGNAL单独的一个用法是执行其他操作,然后在不改变原始条件信息的情况下继续传递(即在进入处理程序之前存在的信息)。

例子:

DROP TABLE IF EXISTS xx;
delimiter //
CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    SET @error_count = @error_count + 1;
    IF @a = 0 THEN RESIGNAL; END IF;
  END;
  DROP TABLE xx;
END//
delimiter ;
SET @error_count = 0;
SET @a = 0;
CALL p();

假设DROP TABLE xx语句失败。诊断区堆栈如下:

DA 1\. ERROR 1051 (42S02): Unknown table 'xx'

然后执行进入EXIT处理程序。它开始通过将诊断区推送到堆栈顶部,现在看起来像这样:

DA 1\. ERROR 1051 (42S02): Unknown table 'xx'
DA 2\. ERROR 1051 (42S02): Unknown table 'xx'

此时,第一个(当前)和第二个(堆叠的)诊断区的内容相同。第一个诊断区可能会被在处理程序内随后执行的语句修改。

通常,过程语句会清除第一个诊断区。BEGIN是一个例外,它不清除,什么也不做。SET不是例外,它会清除,执行操作,并产生“成功”的结果。诊断区堆栈现在看��来像这样:

DA 1\. ERROR 0000 (00000): Successful operation
DA 2\. ERROR 1051 (42S02): Unknown table 'xx'

在这一点上,如果@a = 0RESIGNAL会弹出诊断区堆栈,现在看起来像这样:

DA 1\. ERROR 1051 (42S02): Unknown table 'xx'

这就是调用者看到的。

如果@a不等于 0,处理程序简单地结束,这意味着当前诊断区不再有用(已经被“处理”),因此可以丢弃它,导致堆叠的诊断区变成当前诊断区。诊断区堆栈看起来像这样:

DA 1\. ERROR 0000 (00000): Successful operation

细节使其看起来复杂,但最终结果非常有用:处理程序可以执行而不破坏导致处理程序激活的条件的信息。

带有新信号信息的 RESIGNAL

带有SET子句的RESIGNAL提供新的信号信息,因此该语句意味着“传递带有更改的错误”:

RESIGNAL SET *signal_information_item* [, *signal_information_item*] ...;

与单独使用RESIGNAL一样,其思想是弹出诊断区堆栈,以便原始信息被清除。与单独使用RESIGNAL不同的是,SET子句中指定的任何内容都会发生变化。

例子:

DROP TABLE IF EXISTS xx;
delimiter //
CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    SET @error_count = @error_count + 1;
    IF @a = 0 THEN RESIGNAL SET MYSQL_ERRNO = 5; END IF;
  END;
  DROP TABLE xx;
END//
delimiter ;
SET @error_count = 0;
SET @a = 0;
CALL p();

从之前的讨论中记得,单独使用RESIGNAL会导致诊断区堆栈如下:

DA 1\. ERROR 1051 (42S02): Unknown table 'xx'

RESIGNAL SET MYSQL_ERRNO = 5语句导致了这个堆栈,这是调用者看到的:

DA 1\. ERROR 5 (42S02): Unknown table 'xx'

换句话说,它改变了错误编号,而不会改变其他任何东西。

RESIGNAL语句可以更改任何或所有信号信息项,使诊断区的第一个条件区域看起来完全不同。

带有条件值和可选新信号信息的 RESIGNAL

带有条件值的RESIGNAL意味着“将一个条件推入当前诊断区。”如果存在SET子句,它还会改变错误信息。

RESIGNAL *condition_value*
    [SET *signal_information_item* [, *signal_information_item*] ...];

这种形式的RESIGNAL恢复了最后的诊断区并将其作为当前诊断区。也就是说,它“弹出”了诊断区堆栈,这与简单使用RESIGNAL的效果相同。但是,它还会根据条件值或信号信息更改诊断区。

例子:

DROP TABLE IF EXISTS xx;
delimiter //
CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    SET @error_count = @error_count + 1;
    IF @a = 0 THEN RESIGNAL SQLSTATE '45000' SET MYSQL_ERRNO=5; END IF;
  END;
  DROP TABLE xx;
END//
delimiter ;
SET @error_count = 0;
SET @a = 0;
SET @@max_error_count = 2;
CALL p();
SHOW ERRORS;

这与之前的例子类似,效果相同,只是如果发生RESIGNAL,则最终当前条件区域看起来不同。(条件增加而不是替换现有条件的原因是使用了条件值。)

RESIGNAL 语句包括一个条件值(SQLSTATE '45000'),因此它添加了一个新的条件区域,导致诊断区域堆栈如下所示:

DA 1\. (condition 2) ERROR 1051 (42S02): Unknown table 'xx'
      (condition 1) ERROR 5 (45000) Unknown table 'xx'

对于这个例子,CALL p()SHOW ERRORS 的结果是:

mysql> CALL p();
ERROR 5 (45000): Unknown table 'xx'
mysql> SHOW ERRORS;
+-------+------+----------------------------------+
| Level | Code | Message                          |
+-------+------+----------------------------------+
| Error | 1051 | Unknown table 'xx'               |
| Error |    5 | Unknown table 'xx'               |
+-------+------+----------------------------------+
RESIGNAL 需要条件处理程序上下文

所有形式的RESIGNAL都要求当前上下文是一个条件处理程序。否则,RESIGNAL是非法的,会出现RESIGNAL when handler not active错误。例如:

mysql> CREATE PROCEDURE p () RESIGNAL;
Query OK, 0 rows affected (0.00 sec)

mysql> CALL p();
ERROR 1645 (0K000): RESIGNAL when handler not active

这里是一个更加困难的例子:

delimiter //
CREATE FUNCTION f () RETURNS INT
BEGIN
  RESIGNAL;
  RETURN 5;
END//
CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @a=f();
  SIGNAL SQLSTATE '55555';
END//
delimiter ;
CALL p();

RESIGNAL 发生在存储函数 f() 中。虽然 f() 本身是在 EXIT 处理程序的上下文中调用的,但在 f() 中的执行有其自己的上下文,这不是处理程序上下文。因此,在 f() 中的 RESIGNAL 导致“处理程序未激活”错误。

原文:dev.mysql.com/doc/refman/8.0/en/signal.html

15.6.7.5 信号语句

SIGNAL *condition_value*
    [SET *signal_information_item*
    [, *signal_information_item*] ...]

*condition_value*: {
    SQLSTATE [VALUE] *sqlstate_value*
  | *condition_name*
}

*signal_information_item*:
    *condition_information_item_name* = *simple_value_specification*

*condition_information_item_name*: {
    CLASS_ORIGIN
  | SUBCLASS_ORIGIN
  | MESSAGE_TEXT
  | MYSQL_ERRNO
  | CONSTRAINT_CATALOG
  | CONSTRAINT_SCHEMA
  | CONSTRAINT_NAME
  | CATALOG_NAME
  | SCHEMA_NAME
  | TABLE_NAME
  | COLUMN_NAME
  | CURSOR_NAME
}

*condition_name*, *simple_value_specification*:
    (see following discussion)

SIGNAL是“返回”错误的方法。SIGNAL向处理程序、应用程序的外部部分或客户端提供错误信息。此外,它还可以控制错误的特性(错误编号、SQLSTATE值、消息)。没有SIGNAL,就必须诉诸解决方法,例如故意引用一个不存在的表来导致例程返回错误。

执行SIGNAL语句不需要特权。

要从诊断区域检索信息,请使用GET DIAGNOSTICS语句(参见第 15.6.7.3 节,“GET DIAGNOSTICS 语句”)。有关诊断区域的信息,请参阅第 15.6.7.7 节,“MySQL 诊断区域”。

  • 信号概述

  • 信号条件信息项

  • 信号对处理程序、游标和语句的影响

信号概述

SIGNAL语句中的condition_value表示要返回的错误值。它可以是一个SQLSTATE值(一个 5 个字符的字符串文字)或一个condition_name,它引用先前使用DECLARE ... CONDITION定义的命名条件(参见第 15.6.7.1 节,“DECLARE ... CONDITION 语句”)。

一个SQLSTATE值可以指示错误、警告或“未找到”。该值的前两个字符表示其错误类别,如信号条件信息项中所讨论的那样。一些信号值会导致语句终止;请参阅信号对处理程序、游标和语句的影响。

SIGNAL语句的SQLSTATE值不应以'00'开头,因为这样的值表示成功,不适用于发出错误信号。无论SQLSTATE值是直接在SIGNAL语句中指定还是在语句中引用的命名条件中引用,都是如此。如果该值无效,则会发生Bad SQLSTATE错误。

要发出通用的SQLSTATE值,请使用'45000',表示“未处理的用户定义异常”。

SIGNAL语句可选地包含一个SET子句,其中包含多个信号项,以condition_information_item_name = simple_value_specification分配的列表,用逗号分隔。

每个condition_information_item_nameSET子句中只能指定一次。否则,会出现Duplicate condition information item错误。

可以使用存储过程或函数参数、使用DECLARE声明的存储程序本地变量、用户定义变量、系统变量或文字指定有效的simple_value_specification标识符。字符文字可能包括一个_charset引导符。

有关可接受的condition_information_item_name值的信息,请参阅 Signal Condition Information Items。

以下过程根据其输入参数pval的值发出错误或警告:

CREATE PROCEDURE p (pval INT)
BEGIN
  DECLARE specialty CONDITION FOR SQLSTATE '45000';
  IF pval = 0 THEN
    SIGNAL SQLSTATE '01000';
  ELSEIF pval = 1 THEN
    SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'An error occurred';
  ELSEIF pval = 2 THEN
    SIGNAL specialty
      SET MESSAGE_TEXT = 'An error occurred';
  ELSE
    SIGNAL SQLSTATE '01000'
      SET MESSAGE_TEXT = 'A warning occurred', MYSQL_ERRNO = 1000;
    SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'An error occurred', MYSQL_ERRNO = 1001;
  END IF;
END;

如果pval为 0,p()会发出一个警告,因为以'01'开头的SQLSTATE值属于警告类别。警告不会终止该过程,并且在过程返回后可以使用SHOW WARNINGS查看。

如果pval为 1,p()会发出一个错误并设置MESSAGE_TEXT条件信息项。错误会终止该过程,并且文本将随错误信息一起返回。

如果pval为 2,则会发出相同的错误,尽管在这种情况下使用命名条件指定了SQLSTATE值。

如果pval为其他任何值,p()首先发出一个警告并设置消息文本和错误编号条件信息项。此警告不会终止该过程,因此执行会继续,然后p()会发出一个错误。错误会终止该过程。警告设置的消息文本和错误编号将被错误设置的值替换,这些值将与错误信息一起返回。

SIGNAL通常在存储程序中使用,但是 MySQL 扩展允许在处理程序上下文之外使用。例如,如果调用mysql客户端程序,则可以在提示符下输入以下任何语句:

SIGNAL SQLSTATE '77777';

CREATE TRIGGER t_bi BEFORE INSERT ON t
  FOR EACH ROW SIGNAL SQLSTATE '77777';

CREATE EVENT e ON SCHEDULE EVERY 1 SECOND
  DO SIGNAL SQLSTATE '77777';

SIGNAL根据以下规则执行:

如果SIGNAL语句指示特定的SQLSTATE值,则该值用于发出指定的条件。例如:

CREATE PROCEDURE p (divisor INT)
BEGIN
  IF divisor = 0 THEN
    SIGNAL SQLSTATE '22012';
  END IF;
END;

如果 SIGNAL 语句使用了一个命名条件,该条件必须在适用于 SIGNAL 语句的某个范围内声明,并且必须使用 SQLSTATE 值而不是 MySQL 错误编号进行定义。示例:

CREATE PROCEDURE p (divisor INT)
BEGIN
  DECLARE divide_by_zero CONDITION FOR SQLSTATE '22012';
  IF divisor = 0 THEN
    SIGNAL divide_by_zero;
  END IF;
END;

如果在 SIGNAL 语句的范围内不存在命名条件,则会发生 Undefined CONDITION 错误。

如果 SIGNAL 引用了一个使用 MySQL 错误编号而不是 SQLSTATE 值定义的命名条件,则会发生 SIGNAL/RESIGNAL 只能使用使用 SQLSTATE 定义的 CONDITION 错误。以下语句会导致该错误,因为命名条件与 MySQL 错误编号相关联:

DECLARE no_such_table CONDITION FOR 1051;
SIGNAL no_such_table;

如果在不同范围内多次声明具有相同名称的条件,则具有最局部范围的声明适用。考虑以下过程:

CREATE PROCEDURE p (divisor INT)
BEGIN
  DECLARE my_error CONDITION FOR SQLSTATE '45000';
  IF divisor = 0 THEN
    BEGIN
      DECLARE my_error CONDITION FOR SQLSTATE '22012';
      SIGNAL my_error;
    END;
  END IF;
  SIGNAL my_error;
END;

如果 divisor 为 0,则执行第一个 SIGNAL 语句。最内层的 my_error 条件声明适用,引发 SQLSTATE '22012'

如果 divisor 不为 0,则执行第二个 SIGNAL 语句。最外层的 my_error 条件声明适用,引发 SQLSTATE '45000'

有关服务器在发生条件时选择处理程序的信息,请参阅 第 15.6.7.6 节,“处理程序的作用域规则”。

异常处理程序内部可以引发信号:

CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    SIGNAL SQLSTATE VALUE '99999'
      SET MESSAGE_TEXT = 'An error occurred';
  END;
  DROP TABLE no_such_table;
END;

CALL p() 到达 DROP TABLE 语句。没有名为 no_such_table 的表,因此激活了错误处理程序。错误处理程序销毁原始错误(“没有这样的表”),并生成一个具有 SQLSTATE '99999' 和消息 An error occurred 的新错误。

信号条件信息项

以下表列出了可以在 SIGNAL(或 RESIGNAL)语句中设置的诊断区域条件信息项的名称。所有项目都是标准 SQL,除了 MYSQL_ERRNO,它是 MySQL 的扩展。有关这些项目的更多信息,请参阅 第 15.6.7.7 节,“MySQL 诊断区域”。

Item Name             Definition
---------             ----------
CLASS_ORIGIN          VARCHAR(64)
SUBCLASS_ORIGIN       VARCHAR(64)
CONSTRAINT_CATALOG    VARCHAR(64)
CONSTRAINT_SCHEMA     VARCHAR(64)
CONSTRAINT_NAME       VARCHAR(64)
CATALOG_NAME          VARCHAR(64)
SCHEMA_NAME           VARCHAR(64)
TABLE_NAME            VARCHAR(64)
COLUMN_NAME           VARCHAR(64)
CURSOR_NAME           VARCHAR(64)
MESSAGE_TEXT          VARCHAR(128)
MYSQL_ERRNO           SMALLINT UNSIGNED

字符项的字符集为 UTF-8。

SIGNAL 语句中将 NULL 赋给条件信息项是非法的。

SIGNAL 语句总是指定一个 SQLSTATE 值,可以直接指定,也可以间接引用具有 SQLSTATE 值的命名条件。SQLSTATE 值的前两个字符是其类别,类别确定条件信息项的默认值:

  • 类 = '00'(成功)

    非法。以'00'开头的SQLSTATE值表示成功,并且对于SIGNAL无效。

  • 类别 = '01'(警告)

    MESSAGE_TEXT = 'Unhandled user-defined warning condition';
    MYSQL_ERRNO = ER_SIGNAL_WARN
    
  • 类别 = '02'(未找到)

    MESSAGE_TEXT = 'Unhandled user-defined not found condition';
    MYSQL_ERRNO = ER_SIGNAL_NOT_FOUND
    
  • 类别 > '02'(异常)

    MESSAGE_TEXT = 'Unhandled user-defined exception condition';
    MYSQL_ERRNO = ER_SIGNAL_EXCEPTION
    

对于合法类别,其他条件信息项设置如下:

CLASS_ORIGIN = SUBCLASS_ORIGIN = '';
CONSTRAINT_CATALOG = CONSTRAINT_SCHEMA = CONSTRAINT_NAME = '';
CATALOG_NAME = SCHEMA_NAME = TABLE_NAME = COLUMN_NAME = '';
CURSOR_NAME = '';

SIGNAL执行后可访问的错误值是由SIGNAL语句引发的SQLSTATE值以及MESSAGE_TEXTMYSQL_ERRNO项。这些值可从 C API 中获取:

  • mysql_sqlstate()返回SQLSTATE值。

  • mysql_errno()返回MYSQL_ERRNO值。

  • mysql_error()返回MESSAGE_TEXT值。

在 SQL 级别,SHOW WARNINGSSHOW ERRORS的输出指示CodeMessage列中的MYSQL_ERRNOMESSAGE_TEXT值。

要从诊断区域检索信息,请使用GET DIAGNOSTICS语句(参见 Section 15.6.7.3, “GET DIAGNOSTICS Statement”)。有关诊断区域的信息,请参阅 Section 15.6.7.7, “The MySQL Diagnostics Area”。

信号对处理程序、游标和语句的影响

信号对语句执行的影响取决于信号类别。类别确定错误的严重程度。MySQL 忽略sql_mode系统变量的值;特别是,严格的 SQL 模式无关紧要。MySQL 还忽略IGNORESIGNAL的目的是明确引发用户生成的错误,因此信号永远不会被忽略。

在以下描述中,“未处理”表示未使用DECLARE ... HANDLER为信号的SQLSTATE值定义处理程序。

  • 类别 = '00'(成功)

    非法。以'00'开头的SQLSTATE值表示成功,并且对于SIGNAL无效。

  • 类别 = '01'(警告)

    warning_count系统变量的值增加。SHOW WARNINGS显示信号。SQLWARNING处理程序捕获信号。

    由于导致函数返回的RETURN语句清除了诊断区域,因此无法从存储函数中返回警告。该语句清除了可能存在的任何警告(并将warning_count重置为 0)。

  • 类别 = '02'(未找到)

    NOT FOUND处理程序捕获信号。对游标没有影响。如果在存储函数中未处理信号,则语句结束。

  • 类别 > '02'(异常)

    SQLEXCEPTION处理程序捕获信号。如果在存储函数中未处理信号,则语句结束。

  • 类别 = '40'

    被视为普通异常。

原文:dev.mysql.com/doc/refman/8.0/en/handler-scope.html

15.6.7.6 处理程序的范围规则

存储过程可以包括在程序内发生某些条件时调用的处理程序。每个处理程序的适用性取决于其在程序定义中的位置以及它处理的条件或条件:

  • BEGIN ... END块中声明的处理程序仅对在块中处理程序声明后的 SQL 语句有效。如果处理程序本身引发条件,则它无法处理该条件,也不能处理块中声明的任何其他处理程序。在下面的示例中,处理程序H1H2适用于stmt1stmt2语句引发的条件。但是H1H2对于在H1H2主体中引发的条件不适用。

    BEGIN -- outer block
      DECLARE EXIT HANDLER FOR ...;  -- handler H1
      DECLARE EXIT HANDLER FOR ...;  -- handler H2
      *stmt1*;
      *stmt2*;
    END;
    
  • 处理程序仅在声明它的块中有效,并且不能用于发生在该块外部的条件。在下面的示例中,处理程序H1仅在内部块中的stmt1中有效,而不适用于外部块中的stmt2

    BEGIN -- outer block
      BEGIN -- inner block
        DECLARE EXIT HANDLER FOR ...;  -- handler H1
        *stmt1*;
      END;
      *stmt2*;
    END;
    
  • 处理程序可以是特定的或一般的。特定处理程序是针对 MySQL 错误代码、SQLSTATE值或条件名称的。一般处理程序是针对SQLWARNINGSQLEXCEPTIONNOT FOUND类中的条件。条件特异性与条件优先级有关,如后面所述。

多个处理程序可以在不同的范围和具有不同的特异性中声明。例如,在外部块中可能有一个特定的 MySQL 错误代码处理程序,而在内部块中可能有一个一般的SQLWARNING处理程序。或者在同一块中可能有一个特定的 MySQL 错误代码处理程序和一般的SQLWARNING类处理程序。

处理程序是否被激活不仅取决于其自身的范围和条件值,还取决于其他处理程序的存在。当存储过程中发生条件时,服务器会在当前范围(当前BEGIN ... END块)中搜索适用的处理程序。如果没有适用的处理程序,则搜索会继续向外进行,直到找到每个连续包含范围(块)中的处理程序。当服务器在给定范围找到一个或多个适用的处理程序时,它会根据条件优先级在它们之间进行选择:

  • 一个 MySQL 错误代码处理程序优先于一个SQLSTATE值处理程序。

  • 一个SQLSTATE值处理程序优先于一般的SQLWARNINGSQLEXCEPTIONNOT FOUND处理程序。

  • 一个SQLEXCEPTION处理程序优先于一个SQLWARNING处理程序。

  • 可能存在几个具有相同优先级的适用处理程序。例如,一个语句可能生成多个具有不同错误代码的警告,对于每个警告都存在一个特定错误的处理程序。在这种情况下,服务器激活哪个处理程序的选择是不确定的,并且可能根据条件发生的情况而变化。

处理程序选择规则的一个含义是,如果不同作用域中存在多个适用的处理程序,则具有最局部作用域的处理程序优先于外部作用域中的处理程序,甚至优先于更具体条件的处理程序。

如果在条件发生时没有适当的处理程序,则采取的操作取决于条件的类别:

  • 对于SQLEXCEPTION条件,存储程序在引发条件的语句处终止,就好像有一个EXIT处理程序。如果程序是由另一个存储程序调用的,则调用程序使用其自己的处理程序选择规则处理条件。

  • 对于SQLWARNING条件,程序继续执行,就好像有一个CONTINUE处理程序。

  • 对于NOT FOUND条件,如果条件是正常引发的,则操作是CONTINUE。如果是由SIGNALRESIGNAL引发的,则操作是EXIT

以下示例演示了 MySQL 如何应用处理程序选择规则。

这个过程包含两个处理程序,一个用于特定的SQLSTATE值('42S02'),用于尝试删除不存在表时发生的情况,另一个用于一般的SQLEXCEPTION类:

CREATE PROCEDURE p1()
BEGIN
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02'
    SELECT 'SQLSTATE handler was activated' AS msg;
  DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    SELECT 'SQLEXCEPTION handler was activated' AS msg;

  DROP TABLE test.t;
END;

两个处理程序都在同一个块中声明并具有相同的作用域。然而,SQLSTATE处理程序优先于SQLEXCEPTION处理程序,因此如果表t不存在,则DROP TABLE语句引发一个激活SQLSTATE处理程序的条件:

mysql> CALL p1();
+--------------------------------+
| msg                            |
+--------------------------------+
| SQLSTATE handler was activated |
+--------------------------------+

这个过程包含相同的两个处理程序。但这次,DROP TABLE语句和SQLEXCEPTION处理程序在相对于SQLSTATE处理程序的内部块中:

CREATE PROCEDURE p2()
BEGIN -- outer block
    DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02'
      SELECT 'SQLSTATE handler was activated' AS msg;
  BEGIN -- inner block
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
      SELECT 'SQLEXCEPTION handler was activated' AS msg;

    DROP TABLE test.t; -- occurs within inner block
  END;
END;

在这种情况下,更接近条件发生位置的处理程序优先。即使SQLEXCEPTION处理程序比SQLSTATE处理程序更一般,SQLEXCEPTION处理程序也会被激活:

mysql> CALL p2();
+------------------------------------+
| msg                                |
+------------------------------------+
| SQLEXCEPTION handler was activated |
+------------------------------------+

在这个过程中,处理程序之一在DROP TABLE语句的作用域内部声明:

CREATE PROCEDURE p3()
BEGIN -- outer block
  DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    SELECT 'SQLEXCEPTION handler was activated' AS msg;
  BEGIN -- inner block
    DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02'
      SELECT 'SQLSTATE handler was activated' AS msg;
  END;

  DROP TABLE test.t; -- occurs within outer block
END;

只有SQLEXCEPTION处理程序适用,因为另一个处理程序不适用于DROP TABLE引发的条件:

mysql> CALL p3();
+------------------------------------+
| msg                                |
+------------------------------------+
| SQLEXCEPTION handler was activated |
+------------------------------------+

在这个过程中,两个处理程序都在DROP TABLE语句的作用域内部声明:

CREATE PROCEDURE p4()
BEGIN -- outer block
  BEGIN -- inner block
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
      SELECT 'SQLEXCEPTION handler was activated' AS msg;
    DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02'
      SELECT 'SQLSTATE handler was activated' AS msg;
  END;

  DROP TABLE test.t; -- occurs within outer block
END;

由于它们不在DROP TABLE的范围内,因此都不适用。语句引发的条件未被处理,导致过程以错误终止:

mysql> CALL p4();
ERROR 1051 (42S02): Unknown table 'test.t'

原文:dev.mysql.com/doc/refman/8.0/en/diagnostics-area.html

15.6.7.7 MySQL 诊断区

SQL 语句生成填充诊断区的诊断信息。标准 SQL 具有诊断区堆栈,每个嵌套执行上下文都包含一个诊断区。标准 SQL 还支持GET STACKED DIAGNOSTICS语法,用于在条件处理程序执行期间引用第二个诊断区。

以下讨论描述了 MySQL 中诊断区的结构,MySQL 识别的信息项,语句如何清除和设置诊断区,以及诊断区如何推送到堆栈并从堆栈中弹出。

  • 诊断区结构

  • 诊断区信息项

  • 诊断区如何清除和填充

  • 诊断区堆栈的工作原理

  • 与诊断区相关的系统变量

诊断区结构

诊断区包含两种信息:

  • 语句信息,例如发生的条件数量或受影响行数。

  • 条件信息,例如错误代码和消息。如果语句引发多个条件,则诊断区的此部分为每个条件区域都有一个条件区域。如果语句未引发任何条件,则诊断区的此部分为空。

对于生成三个条件的语句,诊断区包含如下语句和条件信息:

Statement information:
  row count
  ... other statement information items ...
Condition area list:
  Condition area 1:
    error code for condition 1
    error message for condition 1
    ... other condition information items ...
  Condition area 2:
    error code for condition 2:
    error message for condition 2
    ... other condition information items ...
  Condition area 3:
    error code for condition 3
    error message for condition 3
    ... other condition information items ...
诊断区信息项

诊断区包含语句和条件信息项。数值项为整数。字符项的字符集为 UTF-8。没有任何项可以是NULL。如果语句未设置填充诊断区的语句或条件项,则其值为 0 或空字符串,取决于项的数据类型。

诊断区的语句信息部分包含以下内容:

  • NUMBER: 一个整数,表示具有信息的条件区域数量。

  • ROW_COUNT: 一个整数,表示语句影响的行数。ROW_COUNTROW_COUNT()函数的值相同(参见第 14.15 节,“信息函数”)。

诊断区域的条件信息部分包含每个条件的条件区域。条件区域从 1 到NUMBER语句条件项的值编号。如果NUMBER为 0,则没有条件区域。

每个条件区域包含以下列表中的项目。所有项目都是标准 SQL,除了MYSQL_ERRNO,它是 MySQL 的扩展。这些定义适用于除信号(即由SIGNALRESIGNAL语句生成的条件之外的条件。对于非信号条件,MySQL 仅填充未描述为始终为空的那些条件项。信号对条件区域的影响稍后描述。

  • CLASS_ORIGIN:包含RETURNED_SQLSTATE值的类的字符串。如果RETURNED_SQLSTATE值以 SQL 标准文档 ISO 9075-2(第 24.1 节,SQLSTATE)中定义的类值开头,则CLASS_ORIGIN'ISO 9075'。否则,CLASS_ORIGIN'MySQL'

  • SUBCLASS_ORIGIN:包含RETURNED_SQLSTATE值的子类的字符串。如果CLASS_ORIGIN'ISO 9075'RETURNED_SQLSTATE'000'结尾,则SUBCLASS_ORIGIN'ISO 9075'。否则,SUBCLASS_ORIGIN'MySQL'

  • RETURNED_SQLSTATE:指示条件的SQLSTATE值的字符串。

  • MESSAGE_TEXT:指示条件的错误消息的字符串。

  • MYSQL_ERRNO:指示条件的 MySQL 错误代码的整数。

  • CONSTRAINT_CATALOGCONSTRAINT_SCHEMACONSTRAINT_NAME:指示违反约束的目录、模式和名称的字符串。它们始终为空。

  • CATALOG_NAMESCHEMA_NAMETABLE_NAMECOLUMN_NAME:指示与条件相关的目录、模式、表和列的字符串。它们始终为空。

  • CURSOR_NAME:指示游标名称的字符串。这始终为空。

有关特定错误的RETURNED_SQLSTATEMESSAGE_TEXTMYSQL_ERRNO值,请参阅服务器错误消息参考。

如果SIGNAL(或RESIGNAL)语句填充诊断区域,则其SET子句可以为除RETURNED_SQLSTATE之外的任何条件信息项分配合法的数据类型值。SIGNAL还设置RETURNED_SQLSTATE值,但不是直接在其SET子句中。该值来自SIGNAL语句的SQLSTATE参数。

SIGNAL还设置语句信息项。它将NUMBER设置为 1。对于错误,它将ROW_COUNT设置为−1,否则为 0。

诊断区域如何清除和填充

非诊断性 SQL 语句会自动填充诊断区域,并且其内容可以通过SIGNALRESIGNAL语句明确设置。可以使用GET DIAGNOSTICS提取特定项来检查诊断区域,或者使用SHOW WARNINGSSHOW ERRORS来查看条件或错误。

SQL 语句如下清除和设置诊断区域:

  • 当服务器开始执行解析后的语句时,它会清除非诊断性语句的诊断区域。诊断性语句不会清除诊断区域。这些语句是诊断性的:

    • GET DIAGNOSTICS

    • SHOW ERRORS

    • SHOW WARNINGS

  • 如果一个语句引发条件,那么诊断区域将清除属于先前语句的条件。唯一的例外是由GET DIAGNOSTICSRESIGNAL引发的条件会被添加到诊断区域而不清除它。

因此,即使一个语句在开始执行时通常不清除诊断区域,但如果该语句引发条件,则会清除它。

以下示例展示了各种语句对诊断区域的影响,使用SHOW WARNINGS显示存储在其中的条件信息。

这个DROP TABLE语句在条件发生时清除诊断区域并填充它:

mysql> DROP TABLE IF EXISTS test.no_such_table;
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql> SHOW WARNINGS;
+-------+------+------------------------------------+
| Level | Code | Message                            |
+-------+------+------------------------------------+
| Note  | 1051 | Unknown table 'test.no_such_table' |
+-------+------+------------------------------------+
1 row in set (0.00 sec)

这个SET语句生成一个错误,因此它会清除并填充诊断区域:

mysql> SET @x = @@x;
ERROR 1193 (HY000): Unknown system variable 'x' 
mysql> SHOW WARNINGS;
+-------+------+-----------------------------+
| Level | Code | Message                     |
+-------+------+-----------------------------+
| Error | 1193 | Unknown system variable 'x' |
+-------+------+-----------------------------+
1 row in set (0.00 sec)

先前的SET语句产生了一个条件,因此在这一点上,1 是唯一有效的GET DIAGNOSTICS条件号。以下语句使用条件号为 2,这会产生一个警告,该警告被添加到诊断区域而不清除它:

mysql> GET DIAGNOSTICS CONDITION 2 @p = MESSAGE_TEXT;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> SHOW WARNINGS;
+-------+------+------------------------------+
| Level | Code | Message                      |
+-------+------+------------------------------+
| Error | 1193 | Unknown system variable 'xx' |
| Error | 1753 | Invalid condition number     |
+-------+------+------------------------------+
2 rows in set (0.00 sec)

现在诊断区域中有两个条件,因此相同的GET DIAGNOSTICS语句成功执行:

mysql> GET DIAGNOSTICS CONDITION 2 @p = MESSAGE_TEXT;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @p;
+--------------------------+
| @p                       |
+--------------------------+
| Invalid condition number |
+--------------------------+
1 row in set (0.01 sec)
诊断区域栈的工作原理

当诊断区域栈发生推送时,第一个(当前的)诊断区域变为第二个(堆叠的)诊断区域,并创建一个新的当前诊断区域作为其副本。诊断区域在以下情况下被推送到栈中并从栈中弹出:

  • 执行存储程序

    程序执行前发生推送,执行后发生弹出。如果存储程序在处理程序执行时结束,则可能有多个要弹出的诊断区;这是由于没有适当处理程序的异常或处理程序中的返回引起的。

    弹出的诊断区中的任何警告或错误条件都会添加到当前诊断区中,但对于触发器,只会添加错误。当存储程序结束时,调用者会在其当前诊断区中看到这些条件。

  • 在存储程序中执行条件处理程序

    当由于条件处理程序激活而发生推送时,堆栈诊断区是在推送之前存储程序中的当前区域。新的当前诊断区现在是处理程序的当前诊断区。获取[当前]诊断获取堆叠诊断可以在处理程序中使用,以访问当前(处理程序)和堆叠(存储程序)诊断区的内容。最初,它们返回相同的结果,但在处理程序中执行的语句会修改当前诊断区,根据正常规则清除和设置其内容(参见诊断区如何清除和填充)。堆叠诊断区不能被处理程序中执行的语句修改,除非使用重新发出

    如果处理程序成功执行,则当前(处理程序)诊断区将被弹出,堆叠(存储程序)诊断区再次成为当前诊断区。在处理程序执行期间添加到处理程序诊断区的条件将被添加到当前诊断区。

  • 执行重新发出

    重新发出语句传递在存储程序内部复合语句中执行条件处理程序期间可用的错误条件信息。重新发出可能在传递之前更改一些或所有信息,根据第 15.6.7.4 节,“重新发出语句”中描述的方式修改诊断堆栈。

与诊断区相关的系统变量

某些系统变量控制或与诊断区的某些方面相关:

  • max_error_count 控制诊断区域中条件区域的数量。如果发生的条件超过这个数量,MySQL 会悄悄地丢弃多余条件的信息。(通过 RESIGNAL 添加的条件始终会被添加,旧条件会根据需要被丢弃以腾出空间。)

  • warning_count 表示发生的条件数量。这包括错误、警告和注释。通常情况下,NUMBERwarning_count 是相同的。然而,当生成的条件数量超过 max_error_count 时,warning_count 的值会继续上升,而 NUMBER 保持在 max_error_count 上限,因为诊断区域中不会存储额外的条件。

  • error_count 表示发生的错误数量。这个值包括“未找到”和异常条件,但不包括警告和注释。与 warning_count 类似,它的值可以超过 max_error_count

  • 如果 sql_notes 系统变量设置为 0,则不会存储注释,也不会增加 warning_count

例如:如果 max_error_count 是 10,诊断区域最多可以包含 10 个条件区域。假设一条语句引发了 20 个条件,其中有 12 个错误。在这种情况下,诊断区域包含前 10 个条件,NUMBER 是 10,warning_count 是 20,error_count 是 12。

max_error_count 的更改在下一次尝试修改诊断区域时才会生效。如果诊断区域包含 10 个条件区域,而 max_error_count 设置为 5,这对诊断区域的大小或内容没有立即影响。

原文:dev.mysql.com/doc/refman/8.0/en/conditions-and-parameters.html

15.6.7.8 条件处理和 OUT 或 INOUT 参数

如果存储过程以未处理的异常退出,则OUTINOUT参数的修改值不会传播回调用者。

如果异常由包含RESIGNAL语句的CONTINUEEXIT处理程序处理,RESIGNAL语句的执行会弹出诊断区域栈,从而发出异常信号(即,在进入处理程序之前存在的信息)。如果异常是错误,则OUTINOUT参数的值不会传播回调用者。

15.6.8 条件处理的限制

原文:dev.mysql.com/doc/refman/8.0/en/condition-handling-restrictions.html

SIGNALRESIGNALGET DIAGNOSTICS不能作为预处理语句。例如,以下语句是无效的:

PREPARE stmt1 FROM 'SIGNAL SQLSTATE "02000"';

在类'04'中的SQLSTATE值不会被特殊处理。它们与其他异常一样处理。

在标准 SQL 中,第一个条件与前一个 SQL 语句返回的SQLSTATE值相关。在 MySQL 中,这并不保证,所以要获取主要错误,你不能这样做:

GET DIAGNOSTICS CONDITION 1 @errno = MYSQL_ERRNO;

相反,应该这样做:

GET DIAGNOSTICS @cno = NUMBER;
GET DIAGNOSTICS CONDITION @cno @errno = MYSQL_ERRNO;

15.7 数据库管理语句

原文:dev.mysql.com/doc/refman/8.0/en/sql-server-administration-statements.html

15.7.1 账户管理语句

15.7.2 资源组管理语句

15.7.3 表维护语句

15.7.4 组件、插件和可加载函数语句

15.7.5 CLONE 语句

15.7.6 SET 语句

15.7.7 SHOW 语句

15.7.8 其他管理语句

15.7.1 账户管理语句

原文:dev.mysql.com/doc/refman/8.0/en/account-management-statements.html

15.7.1.1 修改用户语句

15.7.1.2 创建角色语句

15.7.1.3 创建用户语句

15.7.1.4 删除角色语句

15.7.1.5 删除用户语句

15.7.1.6 授权语句

15.7.1.7 重命名用户语句

15.7.1.8 撤销语句

15.7.1.9 设置默认角色语句

15.7.1.10 设置密码语句

15.7.1.11 设置角色语句

MySQL 账户信息存储在mysql系统模式的表中。这个数据库和访问控制系统在第七章,MySQL 服务器管理中有详细讨论,您应该查阅以获取更多细节。

重要

一些 MySQL 版本对授权表进行更改以添加新的权限或功能。为了确保您能够利用任何新功能,每次升级 MySQL 时都要将授权表更新到当前结构。请参阅第三章,升级 MySQL

read_only系统变量启用时,账户管理语句需要CONNECTION_ADMIN权限(或已弃用的SUPER权限),除了其他所需权限。这是因为它们修改了mysql系统模式中的表。

账户管理语句是原子性的并且具有崩溃安全性。更多信息请参见第 15.1.1 节,“原子数据定义语句支持”。

原文:dev.mysql.com/doc/refman/8.0/en/alter-user.html

15.7.1.1 ALTER USER Statement

ALTER USER [IF EXISTS]
    *user* [*auth_option*] [, *user* [*auth_option*]] ...
    [REQUIRE {NONE | *tls_option* [[AND] *tls_option*] ...}]
    [WITH *resource_option* [*resource_option*] ...]
    [*password_option* | *lock_option*] ...
    [COMMENT '*comment_string*' | ATTRIBUTE '*json_object*']

ALTER USER [IF EXISTS]
    USER() *user_func_auth_option*

ALTER USER [IF EXISTS]
    *user* [*registration_option*]

ALTER USER [IF EXISTS]
    USER() [*registration_option*]

ALTER USER [IF EXISTS]
    *user* DEFAULT ROLE
    {NONE | ALL | *role* [, *role* ] ...}

*user*:
    (see Section 8.2.4, “Specifying Account Names”)

*auth_option*: {
    IDENTIFIED BY '*auth_string*'
        [REPLACE '*current_auth_string*']
        [RETAIN CURRENT PASSWORD]
  | IDENTIFIED BY RANDOM PASSWORD
        [REPLACE '*current_auth_string*']
        [RETAIN CURRENT PASSWORD]
  | IDENTIFIED WITH *auth_plugin*
  | IDENTIFIED WITH *auth_plugin* BY '*auth_string*'
        [REPLACE '*current_auth_string*']
        [RETAIN CURRENT PASSWORD]
  | IDENTIFIED WITH *auth_plugin* BY RANDOM PASSWORD
        [REPLACE '*current_auth_string*']
        [RETAIN CURRENT PASSWORD]
  | IDENTIFIED WITH *auth_plugin* AS '*auth_string*'
  | DISCARD OLD PASSWORD
  | ADD *factor* *factor_auth_option* [ADD *factor* *factor_auth_option*]
  | MODIFY *factor* *factor_auth_option* [MODIFY *factor* *factor_auth_option*]
  | DROP *factor* [DROP *factor*]
}

*user_func_auth_option*: {
    IDENTIFIED BY '*auth_string*'
        [REPLACE '*current_auth_string*']
        [RETAIN CURRENT PASSWORD]
  | DISCARD OLD PASSWORD
}

*factor_auth_option*: {
    IDENTIFIED BY '*auth_string*'
  | IDENTIFIED BY RANDOM PASSWORD
  | IDENTIFIED WITH *auth_plugin* BY '*auth_string*'
  | IDENTIFIED WITH *auth_plugin* BY RANDOM PASSWORD
  | IDENTIFIED WITH *auth_plugin* AS '*auth_string*'
}

*registration_option*: {
    *factor* INITIATE REGISTRATION
  | *factor* FINISH REGISTRATION SET CHALLENGE_RESPONSE AS '*auth_string*'
  | *factor* UNREGISTER
}

*factor*: {2 | 3} FACTOR

*tls_option*: {
   SSL
 | X509
 | CIPHER '*cipher*'
 | ISSUER '*issuer*'
 | SUBJECT '*subject*'
}

*resource_option*: {
    MAX_QUERIES_PER_HOUR *count*
  | MAX_UPDATES_PER_HOUR *count*
  | MAX_CONNECTIONS_PER_HOUR *count*
  | MAX_USER_CONNECTIONS *count*
}

*password_option*: {
    PASSWORD EXPIRE [DEFAULT | NEVER | INTERVAL *N* DAY]
  | PASSWORD HISTORY {DEFAULT | *N*}
  | PASSWORD REUSE INTERVAL {DEFAULT | *N* DAY}
  | PASSWORD REQUIRE CURRENT [DEFAULT | OPTIONAL]
  | FAILED_LOGIN_ATTEMPTS *N*
  | PASSWORD_LOCK_TIME {*N* | UNBOUNDED}
}

*lock_option*: {
    ACCOUNT LOCK
  | ACCOUNT UNLOCK
}

ALTER USER语句修改 MySQL 账户。它允许对现有账户进行身份验证、角色、SSL/TLS、资源限制、密码管理、注释和属性属性的修改。它还可以用于锁定和解锁账户。

在大多数情况下,ALTER USER需要全局CREATE USER权限,或者mysql系统模式的UPDATE权限。例外情况包括:

  • 任何使用非匿名账户连接到服务器的客户端都可以更改该账户的密码。(特别是,您可以更改自己的密码。)要查看服务器对您进行身份验证的账户,请调用CURRENT_USER()函数:

    SELECT CURRENT_USER();
    
  • 对于DEFAULT ROLE语法,ALTER USER需要这些权限:

    • 为另一个用户设置默认角色需要全局CREATE USER权限,或者mysql.default_roles系统表的UPDATE权限。

    • 为自己设置默认角色不需要特殊权限,只要您想要的默认角色已经授予您。

  • 修改次要密码的语句需要这些权限:

    • 使用RETAIN CURRENT PASSWORDDISCARD OLD PASSWORD子句需要APPLICATION_PASSWORD_ADMIN权限,用于适用于您自己账户的ALTER USER语句。该权限用于操作您自己的次要密码,因为大多数用户只需要一个密码。

    • 如果要允许一个账户操作所有账户的次要密码,则需要CREATE USER权限,而不是APPLICATION_PASSWORD_ADMIN

当启用read_only系统变量时,ALTER USER还需要CONNECTION_ADMIN权限(或已弃用的SUPER权限)。

从 MySQL 8.0.27 开始,这些额外的权限考虑因素适用:

  • authentication_policy系统变量对ALTER USER语句中与身份验证相关的子句的使用施加了一定的约束;有关详细信息,请参阅该变量的描述。如果具有AUTHENTICATION_POLICY_ADMIN权限,则不适用这些约束。

  • 要修改使用无密码身份验证的帐户,必须具有PASSWORDLESS_USER_ADMIN权限。

默认情况下,如果尝试修改不存在的用户,则会发生错误。如果提供了IF EXISTS子句,则该语句会对每个不存在的命名用户产生警告,而不是错误。

重要提示

在某些情况下,ALTER USER可能会记录在服务器日志中或客户端的历史文件中,例如~/.mysql_history,这意味着明文密码可能会被任何具有读取权限的人读取。有关服务器日志中发生这种情况的条件以及如何控制它的信息,请参阅第 8.1.2.3 节,“密码和日志记录”。有关客户端日志记录的类似信息,请参阅第 6.5.1.3 节,“mysql 客户端日志记录”。

ALTER USER语句有几个方面,分别在以下主题下描述:

  • 修改用户概述

  • 修改用户身份验证选项

  • 修改用户多因素身份验证选项

  • 修改用户注册选项

  • 修改用户角色选项

  • 修改用户 SSL/TLS 选项

  • 修改用户资源限制选项

  • 修改用户密码管理选项

  • 修改用户注释和属性选项

  • 修改用户账户锁定选项

  • 修改用户二进制日志记录

修改用户概述

对于每个受影响的帐户,ALTER USER修改mysql.user系统表中对应行,以反映语句中指定的属性。未指定的属性保留其当前值。

每个帐户名称使用第 8.2.4 节“指定帐户名称”中描述的格式。如果省略帐户名称的主机名部分,默认为'%'。还可以指定CURRENT_USERCURRENT_USER()来引用与当前会话关联的帐户。

仅在一个情况下,可以使用USER()函数指定帐户:

ALTER USER USER() IDENTIFIED BY '*auth_string*';

这种语法使您可以在不明确命名您的帐户的情况下更改自己的密码。(该语法还支持 ALTER USER 身份验证选项中描述的REPLACERETAIN CURRENT PASSWORDDISCARD OLD PASSWORD子句。)

对于允许auth_option值跟随user值的ALTER USER语法,auth_option通过指定帐户身份验证插件、凭据(例如密码)或两者来指示帐户如何进行身份验证。每个auth_option值仅适用于紧随其前的帐户。

根据user规范,该语句可能包括 SSL/TLS、资源限制、密码管理和锁定属性的选项。所有这些选项都是语句的全局选项,并适用于语句中命名的所有帐户。

示例:更改帐户的密码并将其过期。结果是,用户必须使用指定的密码连接,并在下次连接时选择一个新密码:

ALTER USER 'jeffrey'@'localhost'
  IDENTIFIED BY '*new_password*' PASSWORD EXPIRE;

示例:修改帐户以使用caching_sha2_password身份验证插件和给定密码。要求每 180 天选择一个新密码,并启用失败登录跟踪,以便连续三次输入错误密码导致临时锁定帐户两天:

ALTER USER 'jeffrey'@'localhost'
  IDENTIFIED WITH caching_sha2_password BY '*new_password*'
  PASSWORD EXPIRE INTERVAL 180 DAY
  FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 2;

示例:锁定或解锁帐户:

ALTER USER 'jeffrey'@'localhost' ACCOUNT LOCK;
ALTER USER 'jeffrey'@'localhost' ACCOUNT UNLOCK;

示例:要求帐户使用 SSL 连接,并在每小时建立 20 个连接的限制:

ALTER USER 'jeffrey'@'localhost'
  REQUIRE SSL WITH MAX_CONNECTIONS_PER_HOUR 20;

示例:更改多个帐户,指定一些每个帐户的属性和一些全局属性:

ALTER USER
  'jeffrey'@'localhost'
    IDENTIFIED BY '*jeffrey_new_password*',
  'jeanne'@'localhost',
  'josh'@'localhost'
    IDENTIFIED BY '*josh_new_password*'
    REPLACE '*josh_current_password*'
    RETAIN CURRENT PASSWORD
  REQUIRE SSL WITH MAX_USER_CONNECTIONS 2
  PASSWORD HISTORY 5;

jeffrey后面的IDENTIFIED BY值仅适用于其紧接的帐户,因此它仅为jeffrey更改密码为'*jeffrey_new_password*'。对于jeanne,没有每个帐户的值(因此密码保持不变)。对于joshIDENTIFIED BY建立了一个新密码('*josh_new_password*'),指定REPLACE以验证发出ALTER USER语句的用户知道当前密码('*josh_current_password*'),并且当前密码也保留为帐户的次要密码。(因此,josh可以使用主密码或次要密码连接。)

剩余属性全局适用于语句中命名的所有帐户,因此对于两个帐户:

  • 连接需要使用 SSL。

  • 该帐户最多可用于两个同时连接。

  • 密码更改不能重复使用最近的五个密码。

示例:丢弃josh的次要密码,只留下主密码:

ALTER USER 'josh'@'localhost' DISCARD OLD PASSWORD;

在缺少特定类型选项的情况下,帐户在这方面保持不变。例如,没有锁定选项,帐户的锁定状态不会改变。

ALTER USER 认证选项

一个帐户名后面可以跟着一个auth_option认证选项,指定帐户认证插件、凭据,或两者兼有。它还可以包括一个密码验证子句,指定要替换的帐户当前密码,并管理帐户是否有次要密码。

注意

仅适用于使用将凭据存储在 MySQL 内部的认证插件的帐户的随机密码生成、密码验证和次要密码子句。对于使用针对 MySQL 外部凭据系统执行身份验证的插件的帐户,密码管理也必须在该系统外部处理。有关内部凭据存储的更多信息,请参见第 8.2.15 节,“密码管理”。

  • auth_plugin指定认证插件。插件名称可以是带引号的字符串文字,也可以是未带引号的名称。插件名称存储在mysql.user系统表的plugin列中。

    对于不指定认证插件的auth_option语法,服务器分配默认插件,如默认认证插件中所述确定。有关每个插件的描述,请参见第 8.4.1 节,“认证插件”。

  • 存储在内部的凭据存储在 mysql.user 系统表中。'*auth_string*' 值或 RANDOM PASSWORD 指定账户凭据,分别作为明文(未加密)字符串或以与账户关联的认证插件期望的格式进行哈希处理的形式:

    • 对于使用 BY '*auth_string*' 语法的情况,该字符串是明文的,并且传递给认证插件进行可能的哈希处理。插件返回的结果存储在 mysql.user 表中。插件可以按照指定的值使用该值,这种情况下不会进行哈希处理。

    • 对于使用 BY RANDOM PASSWORD 语法的情况,MySQL 生成一个随机密码并作为明文传递给认证插件进行可能的哈希处理。插件返回的结果存储在 mysql.user 表中。插件可以按照指定的值使用该值,这种情况下不会进行哈希处理。

      随机生成的密码从 MySQL 8.0.18 开始可用,并具有 随机密码生成 中描述的特性。

    • 对于使用 AS '*auth_string*' 语法的情况,该字符串被假定已经是认证插件所需的格式,并且原样存储在 mysql.user 表中。如果插件需要哈希值,则该值必须已经以适合插件的格式进行哈希处理;否则,插件无法使用该值,也无法正确验证客户端连接。

      从 MySQL 8.0.17 开始,哈希字符串可以是字符串文字或十六进制值。后者对应于当启用 print_identified_with_as_hex 系统变量时,包含不可打印字符的密码哈希的 SHOW CREATE USER 显示的值类型。

    • 如果认证插件不对认证字符串进行哈希处理,则 BY '*auth_string*'AS '*auth_string*' 子句具有相同的效果:认证字符串原样存储在 mysql.user 系统表中。

  • REPLACE '*current_auth_string*' 子句执行密码验证,并从 MySQL 8.0.13 开始可用。如果给出:

    • REPLACE 指定要替换的账户当前密码,作为明文(未加密)字符串。

    • 如果账户的密码更改需要指定当前密码以验证尝试进行更改的用户实际上知道当前密码,则必须给出该子句。

    • 如果账户的密码更改可能但不一定需要指定当前密码,则该子句是可选的。

    • 如果给出了该子句但与当前密码不匹配,则语句将失败,即使该子句是可选的。

    • 只有在更改当前用户的账户密码时才能指定 REPLACE

    有关通过指定当前密码进行密码验证的更多信息,请参见第 8.2.15 节,“密码管理”。

  • RETAIN CURRENT PASSWORDDISCARD OLD PASSWORD子句实现了双密码功能,并自 MySQL 8.0.14 起可用。两者都是可选的,但如果给出,则具有以下效果:

    • RETAIN CURRENT PASSWORD保留账户当前密码作为其次要密码,替换任何现有的次要密码。新密码成为主密码,但客户端可以使用账户使用主密码或次要密码连接到服务器。(例外情况:如果ALTER USER语句指定的新密码为空,则次要密码也变为空,即使给出了RETAIN CURRENT PASSWORD。)

    • 如果为具有空主密码的账户指定了RETAIN CURRENT PASSWORD,则该语句将失败。

    • 如果一个账户有次要密码,并且您更改其主密码而没有指定RETAIN CURRENT PASSWORD,则次要密码保持不变。

    • 如果更改分配给账户的身份验证插件,则次要密码将被丢弃。如果更改身份验证插件并且还指定了RETAIN CURRENT PASSWORD,则该语句将失败。

    • DISCARD OLD PASSWORD丢弃次要密码(如果存在)。账户仅保留其主密码,客户端只能使用主密码连接到服务器。

    有关双密码使用的更多信息,请参见第 8.2.15 节,“密码管理”。

ALTER USER允许这些auth_option语法:

  • IDENTIFIED BY '*auth_string*' [REPLACE '*current_auth_string*'] [RETAIN CURRENT PASSWORD]

    将账户身份验证插件设置为默认插件,将明文'*auth_string*'值传递给插件进行可能的哈希处理,并将结果存储在mysql.user系统表中的账户行中。

    如果指定了REPLACE子句,则指定了当前账户密码,如本节前文所述。

    如果给出了RETAIN CURRENT PASSWORD子句,则导致保留账户当前密码作为其次要密码,如本节前文所述。

  • IDENTIFIED BY RANDOM PASSWORD [REPLACE '*current_auth_string*'] [RETAIN CURRENT PASSWORD]

    将账户认证插件设置为默认插件,生成一个随机密码,将明文密码值传递给插件进行可能的哈希处理,并将结果存储在mysql.user系统表中的账户行中。该语句还会将明文密码作为结果集返回,以便用户或应用程序执行该语句时使用。有关结果集和随机生成密码特性的详细信息,请参见随机密码生成。

    REPLACE子句,如果给定,指定账户当前密码,如本节前文所述。

    RETAIN CURRENT PASSWORD子句,如果给定,会导致账户当前密码保留为其次要密码,如本节前文所述。

  • IDENTIFIED WITH *auth_plugin*

    将账户认证插件设置为auth_plugin,将凭据清空为空字符串(凭据与旧认证插件相关联,而不是新的插件),并将结果存储在mysql.user系统表中的账户行中。

    此外,密码被标记为过期。用户在下次连接时必须选择新密码。

  • IDENTIFIED WITH *auth_plugin* BY '*auth_string*' [REPLACE '*current_auth_string*'] [RETAIN CURRENT PASSWORD]

    将账户认证插件设置为auth_plugin,将明文'*auth_string*'值传递给插件进行可能的哈希处理,并将结果存储在mysql.user系统表中的账户行中。

    REPLACE子句,如果给定,指定账户当前密码,如本节前文所述。

    RETAIN CURRENT PASSWORD子句,如果给定,会导致账户当前密码保留为其次要密码,如本节前文所述。

  • IDENTIFIED WITH *auth_plugin* BY RANDOM PASSWORD [REPLACE '*current_auth_string*'] [RETAIN CURRENT PASSWORD]

    将账户认证插件设置为auth_plugin,生成一个随机密码,将明文密码值传递给插件进行可能的哈希处理,并将结果存储在mysql.user系统表中的账户行中。该语句还会将明文密码作为结果集返回,以便用户或应用程序执行该语句时使用。有关结果集和随机生成密码特性的详细信息,请参见随机密码生成。

    REPLACE子句,如果给定,指定账户当前密码,如本节前文所述。

    RETAIN CURRENT PASSWORD子句,如果给定,会导致账户当前密码保留为其次要密码,如本节前文所述。

  • IDENTIFIED WITH *auth_plugin* AS '*auth_string*'

    将账户认证插件设置为auth_plugin,并将'*auth_string*'值原样存储在mysql.user账户行中。如果插件需要哈希字符串,则假定字符串已经以插件所需的格式进行了哈希处理。

  • DISCARD OLD PASSWORD

    丢弃账户的次要密码,如果存在的话,如本节前面描述的。

示例:将密码指定为明文;使用默认插件:

ALTER USER 'jeffrey'@'localhost'
  IDENTIFIED BY '*password*';

示例:指定认证插件,以及一个明文密码值:

ALTER USER 'jeffrey'@'localhost'
  IDENTIFIED WITH mysql_native_password
             BY '*password*';

示例:与前面的示例类似,但另外指定当前密码作为明文值,以满足用户更改时需要知道该密码的任何账户要求:

ALTER USER 'jeffrey'@'localhost'
  IDENTIFIED WITH mysql_native_password
             BY '*password*'
             REPLACE '*current_password*';

除非当前用户是jeffrey,否则上述语句将失败,因为REPLACE仅允许更改当前用户的密码。

示例:建立一个新的主密码,并保留现有密码作为次要密码:

ALTER USER 'jeffrey'@'localhost'
  IDENTIFIED BY '*new_password*'
  RETAIN CURRENT PASSWORD;

示例:丢弃次要密码,仅保留账户的主密码:

ALTER USER 'jeffery'@'localhost' DISCARD OLD PASSWORD;

示例:指定认证插件,以及一个哈希密码值:

ALTER USER 'jeffrey'@'localhost'
  IDENTIFIED WITH mysql_native_password
             AS '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4';

有关设置密码和认证插件的更多信息,请参见第 8.2.14 节,“分配账户密码”和第 8.2.17 节,“可插拔认证”。

更改用户多因素认证选项

截至 MySQL 8.0.27,ALTER USER具有ADDMODIFYDROP子句,允许添加、修改或删除认证因素。在每种情况下,子句指定要对一个认证因素执行的操作,以及可选地对另一个认证因素执行的操作。对于每个操作,factor项指定了FACTOR关键字,前面跟着数字 2 或 3,以指示操作是应用于第二个还是第三个认证因素。(在此上下文中不允许使用 1。要对第一个认证因素执行操作,请使用 ALTER USER Authentication Options 中描述的语法。)

ALTER USER多因素认证子句的约束由authentication_policy系统变量定义。例如,authentication_policy设置控制账户可以拥有的认证因素数量,以及对于每个因素,允许使用哪些认证方法。请参阅配置多因素认证策略。

ALTER USER在单个语句中添加、修改或删除第二个和第三个因素时,操作是按顺序执行的,但如果序列中的任何操作失败,则整个ALTER USER语句将失败。

对于ADD,每个命名因素都不得已存在,否则无法添加。对于MODIFYDROP,每个命名因素必须存在才能被修改或删除。如果定义了第二个和第三个因素,删除第二个因素会导致第三个因素取代它成为第二个因素。

此语句删除认证因素 2 和 3,从而将帐户从 3FA 转换为 1FA:

ALTER USER '*user*' DROP 2 FACTOR 3 FACTOR;

有关其他ADDMODIFYDROP示例,请参阅开始使用多因素认证。

有关确定未命名插件的认证子句的默认认证插件的特定规则的信息,请参阅默认认证插件。

ALTER USER 注册选项

截至 MySQL 8.0.27,ALTER USER具有允许注册和注销 FIDO 设备的子句。有关更多信息,请参阅使用 FIDO 认证,FIDO 设备注销以及mysql客户端--fido-register-factor选项描述。

mysql客户端--fido-register-factor选项,用于 FIDO 设备注册,会导致mysql客户端生成并执行INITIATE REGISTRATIONFINISH REGISTRATION语句。这些语句不适用于手动执行。

ALTER USER 角色选项

ALTER USER ... DEFAULT ROLE定义了用户连接到服务器并进行身份验证时激活的角色,或者用户在会话期间执行SET ROLE DEFAULT语句时激活的角色。

ALTER USER ... DEFAULT ROLESET DEFAULT ROLE 的替代语法(参见 第 15.7.1.9 节,“SET DEFAULT ROLE Statement”)。然而,ALTER USER 只能为单个用户设置默认值,而 SET DEFAULT ROLE 可以为多个用户设置默认值。另一方面,您可以将 CURRENT_USER 指定为 ALTER USER 语句的用户名,而对于 SET DEFAULT ROLE 则不行。

每个用户账户名称使用先前描述的格式。

每个角色名称使用 第 8.2.5 节,“指定角色名称” 中描述的格式。例如:

ALTER USER 'joe'@'10.0.0.1' DEFAULT ROLE administrator, developer;

如果省略角色���称的主机名部分,则默认为 '%'

DEFAULT ROLE 关键字后的子句允许这些值:

  • NONE:将默认设置为 NONE(无角色)。

  • ALL:将默认设置为授予该账户的所有角色。

  • *role* [, *role* ] ...:将默认设置为指定的角色,这些角色必须在执行 ALTER USER ... DEFAULT ROLE 时存在并授予该账户。

修改用户 SSL/TLS 选项

MySQL 可以检查 X.509 证书属性,除了基于用户名和凭据的常规身份验证外。有关在 MySQL 中使用 SSL/TLS 的背景信息,请参见 第 8.3 节,“使用加密连接”。

要为 MySQL 账户指定 SSL/TLS 相关选项,请使用包含一个或多个 tls_option 值的 REQUIRE 子句。

REQUIRE 选项的顺序无关紧要,但不能指定两次选项。在 REQUIRE 选项之间的 AND 关键字是可选的。

ALTER USER 允许这些 tls_option 值:

  • NONE

    表明由该语句指定的所有账户没有 SSL 或 X.509 要求。如果用户名和密码有效,则允许非加密连接。如果客户端具有正确的证书和密钥文件,则客户端可以选择使用加密连接。

    ALTER USER 'jeffrey'@'localhost' REQUIRE NONE;
    

    客户端默认尝试建立安全连接。对于具有 REQUIRE NONE 的客户端,如果无法建立安全连接,则连接尝试会回退到非加密连接。要求加密连接,客户端只需指定 --ssl-mode=REQUIRED 选项;如果无法建立安全连接,则连接尝试失败。

  • SSL

    告诉服务器只允许通过加密连接访问由该语句指定的所有账户。

    ALTER USER 'jeffrey'@'localhost' REQUIRE SSL;
    

    客户端默认尝试建立安全连接。对于具有REQUIRE SSL的帐户,如果无法建立安全连接,则连接尝试失败。

  • X509

    对于所有由该语句命名的帐户,要求客户端提供有效证书,但确切的证书、颁发者和主题并不重要。唯一的要求是应该能够使用其中一个 CA 证书验证其签名。使用 X.509 证书始终意味着加密,因此在这种情况下SSL选项是不必要的。

    ALTER USER 'jeffrey'@'localhost' REQUIRE X509;
    

    对于具有REQUIRE X509的帐户,客户端必须指定--ssl-key--ssl-cert选项进行连接。(建议但不是必须还指定--ssl-ca,以便验证服务器提供的公共证书。)对于ISSUERSUBJECT也是如此,因为这些REQUIRE选项暗示了X509的要求。

  • 颁发者 '*issuer*'

    对于所有由该语句命名的帐户,要求客户端提供由 CA'*issuer*'颁发的有效 X.509 证书。如果客户端提供的证书有效但颁发者不同,服务器将拒绝连接。使用 X.509 证书始终意味着加密,因此在这种情况下SSL选项是不必要的。

    ALTER USER 'jeffrey'@'localhost'
      REQUIRE ISSUER '/C=SE/ST=Stockholm/L=Stockholm/
        O=MySQL/CN=CA/emailAddress=ca@example.com';
    

    因为ISSUER暗示了X509的要求,客户端必须指定--ssl-key--ssl-cert选项进行连接。(建议但不是必须还指定--ssl-ca,以便验证服务器提供的公共证书。)

  • 主题 '*subject*'

    对于所有由该语句命名的帐户,要求客户端提供包含主题subject的有效 X.509 证书。如果客户端提供的证书有效但主题不同,服务器将拒绝连接。使用 X.509 证书始终意味着加密,因此在这种情况下SSL选项是不必要的。

    ALTER USER 'jeffrey'@'localhost'
      REQUIRE SUBJECT '/C=SE/ST=Stockholm/L=Stockholm/
        O=MySQL demo client certificate/
        CN=client/emailAddress=client@example.com';
    

    MySQL 对'*subject*'值与证书中的值进行简单的字符串比较,因此大小写和组件顺序必须与证书中的完全相同。

    因为SUBJECT暗示了X509的要求,客户端必须指定--ssl-key--ssl-cert选项进行连接。(建议但不是必须还指定--ssl-ca,以便验证服务器提供的公共证书。)

  • 密码 '*cipher*'

    对于语句命名的所有帐户,需要特定的密码方法来加密连接。需要此选项以确保使用足够强度的密码和密钥长度的密码和密钥。如果使用旧算法和短加密密钥的算法,则加密可能较弱。

    ALTER USER 'jeffrey'@'localhost'
      REQUIRE CIPHER 'EDH-RSA-DES-CBC3-SHA';
    

SUBJECTISSUERCIPHER选项可以在REQUIRE子句中组合使用:

ALTER USER 'jeffrey'@'localhost'
  REQUIRE SUBJECT '/C=SE/ST=Stockholm/L=Stockholm/
    O=MySQL demo client certificate/
    CN=client/emailAddress=client@example.com'
  AND ISSUER '/C=SE/ST=Stockholm/L=Stockholm/
    O=MySQL/CN=CA/emailAddress=ca@example.com'
  AND CIPHER 'EDH-RSA-DES-CBC3-SHA';
ALTER USER 资源限制选项

可以对帐户使用服务器资源的限制进行限制,如第 8.2.21 节“设置帐户资源限制”中所讨论的。为此,请使用指定一个或多个resource_option值的WITH子句。

WITH选项的顺序无关紧要,除非给定资源限制多次指定,否则最后一次实例优先。

ALTER USER允许使用以下resource_option值:

  • MAX_QUERIES_PER_HOUR *count*MAX_UPDATES_PER_HOUR *count*MAX_CONNECTIONS_PER_HOUR *count*

    对于语句命名的所有帐户,这些选项限制了在任何给定的一小时内每个帐户对服务器执行多少查询、更新和连接。如果count0(默认值),这意味着该帐户没有限制。

  • MAX_USER_CONNECTIONS *count*

    对于语句命名的所有帐户,限制每个帐户对服务器的最大同时连接数。非零count明确指定了帐户的限制。如果count0(默认值),服务器将根据max_user_connections系统变量的全局值确定帐户的同时连接数。如果max_user_connections也为零,则该帐户没有限制。

示例:

ALTER USER 'jeffrey'@'localhost'
  WITH MAX_QUERIES_PER_HOUR 500 MAX_UPDATES_PER_HOUR 100;
ALTER USER 密码管理选项

ALTER USER支持几个password_option值用于密码管理:

  • 密码过期选项:您可以手动使帐户密码过期并建立其密码过期策略。策略选项不会使密码过期。相反,它们确定服务器如何根据密码年龄自动使帐户密码过期,该年龄是从最近更改帐户密码的日期和时间评估的。

  • 密码重用选项:您可以基于密码更改次数、经过的时间或两者限制密码重用。

  • 密码验证必需选项:您可以指示更改帐户密码的尝试是否必须指定当前密码,以验证试图进行更改的用户实际上知道当前密码。

  • 不正确密码的失败登录跟踪选项:您可以导致服务器跟踪失败的登录尝试,并临时锁定给出太多连续不正确密码的帐户。可配置失败次数和锁定时间。

本节描述了密码管理选项的语法。有关建立密码管理策略的信息,请参见第 8.2.15 节“密码管理”。

如果指定了给定类型的多个密码管理选项,则最后一个优先。例如,PASSWORD EXPIRE DEFAULT PASSWORD EXPIRE NEVERPASSWORD EXPIRE NEVER相同。

注意

除了与失败登录跟踪相关的选项外,密码管理选项仅适用于使用将凭据存储在 MySQL 内部的身份验证插件的帐户。对于使用针对 MySQL 外部凭据系统执行身份验证的插件的帐户,密码管理也必须在该系统外部处理。有关内部凭据存储的更多信息,请参见第 8.2.15 节“密码管理”。

如果客户端的帐户密码已手动过期或密码年龄被认为大于其允许的生命周期,根据自动过期策略,客户端的密码已过期。在这种情况下,服务器要么断开客户端的连接,要么限制其允许的操作(请参见第 8.2.16 节“服务器处理过期密码”)。受限客户端执行的操作会导致错误,直到用户建立新的帐户密码。

注意

尽管可以通过将过期密码重置为当前值来“重置”过期密码,但作为良好政策的一部分,最好选择不同的密码。 DBA 可以通过建立适当的密码重用策略来强制执行不重用。请参见密码重用策略。

ALTER USER允许这些password_option值来控制密码过期:

  • PASSWORD EXPIRE

    立即标记由语句命名的所有帐户的密码过期。

    ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE;
    
  • PASSWORD EXPIRE DEFAULT

    将由语句命名的所有帐户设置为适用全局过期策略,由default_password_lifetime系统变量指定。

    ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE DEFAULT;
    
  • PASSWORD EXPIRE NEVER

    此过期选项覆盖了语句命名的所有帐户的全局策略。对于每个帐户,它禁用密码过期,使密码永不过期。

    ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE NEVER;
    
  • PASSWORD EXPIRE INTERVAL *N* DAY

    此过期选项覆盖了声明中所有命名的账户的全局策略。对于每个账户,它将密码寿命设置为N天。以下声明要求每 180 天更改一次密码:

    ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE INTERVAL 180 DAY;
    

ALTER USER 允许这些password_option值来控制基于所需最小密码更改次数的先前密码重用:

  • PASSWORD HISTORY DEFAULT

    设置所有由声明命名的账户,以便全局关于密码历史长度的策略适用,以禁止在由password_history系统变量指定的更改次数之前重用密码。

    ALTER USER 'jeffrey'@'localhost' PASSWORD HISTORY DEFAULT;
    
  • PASSWORD HISTORY *N*

    此历史长度选项覆盖了声明中所有命名的账户的全局策略。对于每个账户,它将密码历史长度设置为N个密码,以禁止重新使用最近选择的N个密码中的任何一个。以下声明禁止重新使用之前的 6 个密码:

    ALTER USER 'jeffrey'@'localhost' PASSWORD HISTORY 6;
    

ALTER USER 允许这些password_option值来控制基于经过的时间重新使用先前密码的情况:

  • PASSWORD REUSE INTERVAL DEFAULT

    设置所有由账户命名的声明,以便全局关于经过时间的策略适用,以禁止重用比由password_reuse_interval系统变量指定的天数新的密码。

    ALTER USER 'jeffrey'@'localhost' PASSWORD REUSE INTERVAL DEFAULT;
    
  • PASSWORD REUSE INTERVAL *N* DAY

    此经过时间选项覆盖了声明中所有命名的账户的全局策略。对于每个账户,它将密码重用间隔设置为N天,以禁止重用比该天数新的密码。以下声明禁止密码在 360 天内重用:

    ALTER USER 'jeffrey'@'localhost' PASSWORD REUSE INTERVAL 360 DAY;
    

ALTER USER 允许这些password_option值来控制是否尝试更改账户密码必须指定当前密码,以验证尝试进行更改的用户实际上知道当前密码:

  • PASSWORD REQUIRE CURRENT

    此验证选项覆盖了声明中所有命名的账户的全局策略。对于每个账户,它要求密码更改必须指定当前密码。

    ALTER USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT;
    
  • PASSWORD REQUIRE CURRENT OPTIONAL

    此验证选项覆盖了声明中所有命名的账户的全局策略。对于每个账户,它不要求密码更改必须指定当前密码。(当前密码可以给出,但不是必须的。)

    ALTER USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT OPTIONAL;
    
  • PASSWORD REQUIRE CURRENT DEFAULT

    设置所有由账户命名的声明,以便全局关于密码验证的策略适用,如password_require_current系统变量所指定的那样。

    ALTER USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT DEFAULT;
    

从 MySQL 8.0.19 开始,ALTER USER 允许使用以下 password_option 值来控制失败登录跟踪:

  • FAILED_LOGIN_ATTEMPTS *N*

    是否跟踪指定错误密码的账户登录尝试。N 必须是从 0 到 32767 的数字。值为 0 禁用失败登录跟踪。大于 0 的值表示多少连续密码失败会导致临时账户锁定(如果 PASSWORD_LOCK_TIME 也非零)。

  • PASSWORD_LOCK_TIME {*N* | UNBOUNDED}

    在太多连续登录尝试提供错误密码后锁定账户多长时间。N 必须是从 0 到 32767 的数字,或 UNBOUNDED。值为 0 禁用临时账户锁定。大于 0 的值表示锁定账户的天数。值为 UNBOUNDED 导致账户锁定持续时间无限;一旦锁定,账户将保持锁定状态直到解锁。有关解锁发生的条件,请参阅 Failed-Login Tracking and Temporary Account Locking。

要实现失败登录跟踪和临时锁定,一个账户的 FAILED_LOGIN_ATTEMPTSPASSWORD_LOCK_TIME 选项都必须非零。以下语句修改一个账户,使其在连续四次密码失败后保持锁定两天:

ALTER USER 'jeffrey'@'localhost'
  FAILED_LOGIN_ATTEMPTS 4 PASSWORD_LOCK_TIME 2;
修改用户评论和属性选项

MySQL 8.0.21 及更高版本支持用户评论和用户属性,如 Section 15.7.1.3, “CREATE USER Statement” 中所述。这些可以通过 ALTER USER 使用 COMMENTATTRIBUTE 选项进行修改。不能在同一 ALTER USER 语句中同时指定这两个选项;尝试这样做会导致语法错误。

用户评论和用户属性存储在信息模式 USER_ATTRIBUTES 表中作为 JSON 对象;用户评论存储为此表的 ATTRIBUTE 列中 comment 键的值,如后面的讨论所示。COMMENT 文本可以是任意带引号的文本,并替换任何现有用户评论。ATTRIBUTE 值必须是 JSON 对象的有效字符串表示。这与任何现有用户属性合并,就好像在现有用户属性和新用户属性上使用了 JSON_MERGE_PATCH() 函数;对于重新使用的任何键,新值会覆盖旧值,如下所示:

mysql> SELECT * FROM INFORMATION_SCHEMA.USER_ATTRIBUTES
 ->     WHERE USER='bill' AND HOST='localhost';
+------+-----------+----------------+
| USER | HOST      | ATTRIBUTE      |
+------+-----------+----------------+
| bill | localhost | {"foo": "bar"} |
+------+-----------+----------------+
1 row in set (0.11 sec)

mysql> ALTER USER 'bill'@'localhost' ATTRIBUTE '{"baz": "faz", "foo": "moo"}';
Query OK, 0 rows affected (0.22 sec)

mysql> SELECT * FROM INFORMATION_SCHEMA.USER_ATTRIBUTES
 ->     WHERE USER='bill' AND HOST='localhost';
+------+-----------+------------------------------+
| USER | HOST      | ATTRIBUTE                    |
+------+-----------+------------------------------+
| bill | localhost | {"baz": "faz", "foo": "moo"} |
+------+-----------+------------------------------+
1 row in set (0.00 sec)

要从用户属性中删除键及其值,将键设置为 JSON null(必须小写且不带引号),如下所示:

mysql> ALTER USER 'bill'@'localhost' ATTRIBUTE '{"foo": null}';
Query OK, 0 rows affected (0.08 sec)

mysql> SELECT * FROM INFORMATION_SCHEMA.USER_ATTRIBUTES
 ->     WHERE USER='bill' AND HOST='localhost';
+------+-----------+----------------+
| USER | HOST      | ATTRIBUTE      |
+------+-----------+----------------+
| bill | localhost | {"baz": "faz"} |
+------+-----------+----------------+
1 row in set (0.00 sec)

要将现有用户的注释设置为空字符串,请使用ALTER USER ... COMMENT ''。这将在USER_ATTRIBUTES表中留下一个空的comment值;要完全删除用户注释,请使用ALTER USER ... ATTRIBUTE ...,将列键的值设置为 JSON null(小写,不带引号)。下面是一系列 SQL 语句的示例:

mysql> ALTER USER 'bill'@'localhost' COMMENT 'Something about Bill';
Query OK, 0 rows affected (0.06 sec)

mysql> SELECT * FROM INFORMATION_SCHEMA.USER_ATTRIBUTES
 ->     WHERE USER='bill' AND HOST='localhost';
+------+-----------+---------------------------------------------------+
| USER | HOST      | ATTRIBUTE                                         |
+------+-----------+---------------------------------------------------+
| bill | localhost | {"baz": "faz", "comment": "Something about Bill"} |
+------+-----------+---------------------------------------------------+
1 row in set (0.00 sec)

mysql> ALTER USER 'bill'@'localhost' COMMENT '';
Query OK, 0 rows affected (0.09 sec)

mysql> SELECT * FROM INFORMATION_SCHEMA.USER_ATTRIBUTES
 ->     WHERE USER='bill' AND HOST='localhost';
+------+-----------+-------------------------------+
| USER | HOST      | ATTRIBUTE                     |
+------+-----------+-------------------------------+
| bill | localhost | {"baz": "faz", "comment": ""} |
+------+-----------+-------------------------------+
1 row in set (0.00 sec)

mysql> ALTER USER 'bill'@'localhost' ATTRIBUTE '{"comment": null}';
Query OK, 0 rows affected (0.07 sec)

mysql> SELECT * FROM INFORMATION_SCHEMA.USER_ATTRIBUTES
 ->     WHERE USER='bill' AND HOST='localhost';
+------+-----------+----------------+
| USER | HOST      | ATTRIBUTE      |
+------+-----------+----------------+
| bill | localhost | {"baz": "faz"} |
+------+-----------+----------------+
1 row in set (0.00 sec)
ALTER USER 帐户锁定选项

MySQL 支持使用ACCOUNT LOCKACCOUNT UNLOCK选项进行帐户锁定和解锁,这些选项指定帐户的锁定状态。有关更多讨论,请参阅第 8.2.20 节,“帐户锁定”。

如果指定了多个帐户锁定选项,则最后一个优先。

ALTER USER ... ACCOUNT UNLOCK 解锁由语句指定的任何因登录失败次数过多而暂时锁定的帐户。请参阅第 8.2.15 节,“密码管理”。

ALTER USER 二进制日志记录

ALTER USER 如果成功,则写入二进制日志,但如果失败则不会;在这种情况下,将发生回滚,不会进行任何更改。写入二进制日志的语句包括所有命名用户。如果给出了IF EXISTS子句,则甚至包括那些不存在且未被更改的用户。

如果原始语句更改了用户的凭据,则写入二进制日志的语句指定了该用户的适用身份验证插件,��定如下:

  • 如果原始语句中指定了插件的名称,则插件的名称。

  • 否则,与用户帐户关联的插件(如果用户存在),或者默认身份验证插件(如果用户不存在)。(如果写入二进制日志的语句必须为用户指定特定的身份验证插件,请在原始语句中包含它。)

如果服务器为写入二进制日志的语句中的任何用户添加默认身份验证插件,则会向错误日志中写入警告,列出这些用户。

如果原始语句指定了FAILED_LOGIN_ATTEMPTSPASSWORD_LOCK_TIME选项,则写入二进制日志的语句包括该选项。

具有支持多因素身份验证(MFA)的子句的ALTER USER语句被写入二进制日志,但不包括ALTER USER *user factor* INITIATE REGISTRATION语句。

  • ALTER USER *user factor* FINISH REGISTRATION SET CHALLENGE_RESPONSE AS '*auth_string*' 语句被写入二进制日志为ALTER USER *user* MODIFY *factor* IDENTIFIED WITH authentication_fido AS *fido_hash_string*;

  • 在复制环境中,复制用户需要PASSWORDLESS_USER_ADMIN权限来执行对使用authentication_fido插件配置为无密码身份验证的帐户进行ALTER USER ... MODIFY操作。

原文:dev.mysql.com/doc/refman/8.0/en/create-role.html

15.7.1.2 CREATE ROLE Statement

CREATE ROLE [IF NOT EXISTS] *role* [, *role* ] ...

CREATE ROLE 创建一个或多个角色,这些角色是命名的权限集合。要使用此语句,您必须具有全局CREATE ROLECREATE USER权限。当启用read_only系统变量时,CREATE ROLE 还需要CONNECTION_ADMIN权限(或已弃用的SUPER权限)。

创建角色时,角色被锁定,没有密码,并分配默认的身份验证插件。(这些角色属性可以由具有全局CREATE USER权限的用户稍后使用ALTER USER语句更改。)

CREATE ROLE 对所有命名角色要么成功,要么回滚并且不起作用,如果发生任何错误。默认情况下,如果尝试创建已经存在的角色,则会发生错误。如果给出了IF NOT EXISTS子句,则该语句对每个已经存在的命名角色产生警告,而不是错误。

如果成功,该语句将写入二进制日志,但如果失败则不会写入;在这种情况下,将发生回滚,不会进行任何更改。写入二进制日志的语句包括所有命名角色。如果给出了IF NOT EXISTS子句,则即使已经存在且未被创建的角色也会包括在内。

每个角色名称都使用第 8.2.5 节,“指定角色名称”中描述的格式。例如:

CREATE ROLE 'admin', 'developer';
CREATE ROLE 'webapp'@'localhost';

角色名称的主机名部分,如果省略,默认为'%'

有关角色使用示例,请参见第 8.2.10 节,“使用角色”。

原文:dev.mysql.com/doc/refman/8.0/en/create-user.html

15.7.1.3 CREATE USER Statement

CREATE USER [IF NOT EXISTS]
    *user* [*auth_option*] [, *user* [*auth_option*]] ...
    DEFAULT ROLE *role* [, *role* ] ...
    [REQUIRE {NONE | *tls_option* [[AND] *tls_option*] ...}]
    [WITH *resource_option* [*resource_option*] ...]
    [*password_option* | *lock_option*] ...
    [COMMENT '*comment_string*' | ATTRIBUTE '*json_object*']

*user*:
    (see Section 8.2.4, “Specifying Account Names”)

*auth_option*: {
    IDENTIFIED BY '*auth_string*' [AND *2fa_auth_option*]
  | IDENTIFIED BY RANDOM PASSWORD [AND *2fa_auth_option*]
  | IDENTIFIED WITH *auth_plugin* [AND *2fa_auth_option*]
  | IDENTIFIED WITH *auth_plugin* BY '*auth_string*' [AND *2fa_auth_option*]
  | IDENTIFIED WITH *auth_plugin* BY RANDOM PASSWORD [AND *2fa_auth_option*]
  | IDENTIFIED WITH *auth_plugin* AS '*auth_string*' [AND *2fa_auth_option*]
  | IDENTIFIED WITH *auth_plugin* [*initial_auth_option*]
}

*2fa_auth_option*: {
    IDENTIFIED BY '*auth_string*' [AND *3fa_auth_option*]
  | IDENTIFIED BY RANDOM PASSWORD [AND *3fa_auth_option*]
  | IDENTIFIED WITH *auth_plugin* [AND *3fa_auth_option*]
  | IDENTIFIED WITH *auth_plugin* BY '*auth_string*' [AND *3fa_auth_option*]
  | IDENTIFIED WITH *auth_plugin* BY RANDOM PASSWORD [AND *3fa_auth_option*]
  | IDENTIFIED WITH *auth_plugin* AS '*auth_string*' [AND *3fa_auth_option*]
}

*3fa_auth_option*: {
    IDENTIFIED BY '*auth_string*'
  | IDENTIFIED BY RANDOM PASSWORD
  | IDENTIFIED WITH *auth_plugin*
  | IDENTIFIED WITH *auth_plugin* BY '*auth_string*'
  | IDENTIFIED WITH *auth_plugin* BY RANDOM PASSWORD
  | IDENTIFIED WITH *auth_plugin* AS '*auth_string*'
}

*initial_auth_option*: {
    INITIAL AUTHENTICATION IDENTIFIED BY {RANDOM PASSWORD | '*auth_string*'}
  | INITIAL AUTHENTICATION IDENTIFIED WITH *auth_plugin* AS '*auth_string*'
}

*tls_option*: {
   SSL
 | X509
 | CIPHER '*cipher*'
 | ISSUER '*issuer*'
 | SUBJECT '*subject*'
}

*resource_option*: {
    MAX_QUERIES_PER_HOUR *count*
  | MAX_UPDATES_PER_HOUR *count*
  | MAX_CONNECTIONS_PER_HOUR *count*
  | MAX_USER_CONNECTIONS *count*
}

*password_option*: {
    PASSWORD EXPIRE [DEFAULT | NEVER | INTERVAL *N* DAY]
  | PASSWORD HISTORY {DEFAULT | *N*}
  | PASSWORD REUSE INTERVAL {DEFAULT | *N* DAY}
  | PASSWORD REQUIRE CURRENT [DEFAULT | OPTIONAL]
  | FAILED_LOGIN_ATTEMPTS *N*
  | PASSWORD_LOCK_TIME {*N* | UNBOUNDED}
}

*lock_option*: {
    ACCOUNT LOCK
  | ACCOUNT UNLOCK
}

CREATE USER语句创建新的 MySQL 账户。它使得可以为新账户建立认证、角色、SSL/TLS、资源限制、密码管理、注释和属性属性。它还控制了账户最初是锁定还是解锁的状态。

要使用CREATE USER,您必须具有全局CREATE USER权限,或者对mysql系统模式具有INSERT权限。当启用read_only系统变量时,CREATE USER还需要CONNECTION_ADMIN权限(或已弃用的SUPER权限)。

截至 MySQL 8.0.27,以下附加特权考虑因素适用:

  • authentication_policy 系统变量对CREATE USER语句中与认证相关的子句的使用施加了一定的约束;详情请参阅该变量的描述。如果具有AUTHENTICATION_POLICY_ADMIN权限,则这些约束不适用。

  • 要创建一个使用无密码认证的账户,您必须具有PASSWORDLESS_USER_ADMIN权限。

截至 MySQL 8.0.22,如果要创建的任何账户被命名为任何存储对象的DEFINER属性,则CREATE USER将失败并显示错误。(也就是说,如果创建账户会导致该账户接管当前孤立的存储对象,则该语句将失败。)要执行该操作,您必须具有SET_USER_ID权限;在这种情况下,该语句将以警告成功而不是错误失败。如果没有SET_USER_ID,要执行用户创建操作,请删除孤立对象,创建账户并授予其权限,然后重新创建已删除的对象。有关更多信息,包括如何识别哪些对象将给定账户命名为DEFINER属性,请参阅孤立存储对象。

CREATE USER 对所有命名用户都成功,或者如果发生任何错误则回滚并不起作用。默认情况下,如果尝试创建已存在的用户,则会发生错误。如果给出了 IF NOT EXISTS 子句,则该语句会对每个已存在的命名用户产生警告,而不是错误。

重要提示

在某些情况下,CREATE USER 可能会记录在服务器日志中或客户端的历史文件中,例如 ~/.mysql_history,这意味着明文密码可能被任何具有读取权限的人读取。有关在服务器日志中发生这种情况的条件以及如何控制它的信息,请参见 第 8.1.2.3 节,“密码和日志记录”。有关客户端日志记录的类似信息,请参见 第 6.5.1.3 节,“mysql 客户端日志记录”。

CREATE USER 语句有几个方面,描述如下主题:

  • 创建用户概述

  • 创建用户认证选项

  • 创建用户多因素认证选项

  • 创建用户角色选项

  • 创建用户 SSL/TLS 选项

  • 创建用户资源限制选项

  • 创建用户密码管理选项

  • 创建用户注释和属性选项

  • 创建用户账户锁定选项

  • 创建用户二进制日志记录

创建用户概述

对于每个账户,CREATE USERmysql.user 系统表中创建一行新记录。账户行反映了语句中指定的属性。未指定的属性将设置为它们的默认值:

  • 认证:默认认证插件(如 默认认证插件 中所述确定的),以及空凭据

  • 默认角色:NONE

  • SSL/TLS:NONE

  • 资源限制:无限制

  • 密码管理:PASSWORD EXPIRE DEFAULT PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT PASSWORD REQUIRE CURRENT DEFAULT;禁用了失败登录跟踪和临时帐户锁定

  • 帐户锁定:ACCOUNT UNLOCK

创建时,帐户没有任何权限和默认角色NONE。要为此帐户分配权限或角色,请使用一个或多个GRANT语句。

每个帐户名称都采用第 8.2.4 节,“指定帐户名称”中描述的格式。例如:

CREATE USER 'jeffrey'@'localhost' IDENTIFIED BY '*password*';

如果帐户名称的主机名部分被省略,则默认为'%'。您应该知道,虽然 MySQL 8.0 将授予此类用户的授权视为已授予'*user*'@'localhost',但此行为已在 MySQL 8.0.35 中被弃用,并因此可能在将来的 MySQL 版本中被移除。

每个命名帐户的user值后面可以跟一个可选的auth_option值,表示帐户的认证方式。这些值可以指定帐户认证插件和凭据(例如密码)。每个auth_option值仅适用于紧随其后的帐户。

根据user规范,语句可以包括 SSL/TLS、资源限制、密码管理和锁定属性的选项。所有这些选项都是语句的全局属性,并适用于语句中命名的所有帐户。

示例:创建一个使用默认认证插件和给定密码的帐户。标记密码已过期,以便用户必须在首次连接到服务器时选择新密码:

CREATE USER 'jeffrey'@'localhost'
  IDENTIFIED BY '*new_password*' PASSWORD EXPIRE;

示例:创建一个使用caching_sha2_password认证插件和给定密码的帐户。要求每 180 天选择一个新密码,并启用失败登录跟踪,以便三次连续输入错误密码导致帐户被临时锁定两天:

CREATE USER 'jeffrey'@'localhost'
  IDENTIFIED WITH caching_sha2_password BY '*new_password*'
  PASSWORD EXPIRE INTERVAL 180 DAY
  FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 2;

示例:创建多个帐户,指定一些每个帐户的属性和一些全局属性:

CREATE USER
  'jeffrey'@'localhost' IDENTIFIED WITH mysql_native_password
                                   BY '*new_password1*',
  'jeanne'@'localhost' IDENTIFIED WITH caching_sha2_password
                                  BY '*new_password2*'
  REQUIRE X509 WITH MAX_QUERIES_PER_HOUR 60
  PASSWORD HISTORY 5
  ACCOUNT LOCK;

每个auth_option值(在本例中为IDENTIFIED WITH ... BY)仅适用于紧随其后的帐户,因此每个帐户使用紧随其后的认证插件和密码。

剩余的属性全局适用于语句中命名的所有帐户,因此对于两个帐户:

  • 必须使用有效的 X.509 证书进行连接。

  • 每小时最多允许 60 个查询。

  • 密码更改不能重复使用最近的五个密码。

  • 帐户最初被锁定,因此实际上它是一个占位符,直到管理员解锁它才能使用。

创建用户认证选项

帐户名称后面可以跟一个auth_option认证选项,指定帐户认证插件、凭据或两者。

注意

在 MySQL 8.0.27 之前,auth_option 定义了账户进行身份验证的唯一方法。也就是说,所有账户都使用单因素/单因素身份验证(1FA/SFA)。MySQL 8.0.27 及更高版本支持多因素身份验证(MFA),因此账户可以使用最多三种身份验证方法。也就是说,账户可以使用双因素身份验证(2FA)或三因素身份验证(3FA)。auth_option 的语法和语义保持不变,但auth_option 可以后跟额外身份验证方法的规范。本节描述了auth_option。有关可选 MFA 相关后续子句的详细信息,请参阅 CREATE USER 多因素身份验证选项。

注意

仅适用于使用将凭据存储在 MySQL 内部的身份验证插件的账户的随机密码生成子句。对于使用针对 MySQL 外部凭据系统执行身份验证的插件的账户,密码管理也必须在该系统外部处理。有关内部凭据存储的更多信息,请参阅 第 8.2.15 节,“密码管理”。

  • auth_plugin 指定了一个身份验证插件。插件名称可以是带引号的字符串文字或未带引号的名称。插件名称存储在 mysql.user 系统表的 plugin 列中。

    对于未指定身份验证插件的auth_option 语法,服务器会分配默认插件,具体确定方法请参见 默认身份验证插件。有关每个插件的描述,请参见 第 8.4.1 节,“身份验证插件”。

  • 存储在内部的凭据存储在 mysql.user 系统表中。'*auth_string*' 值或 RANDOM PASSWORD 指定账户凭据,可以是明文(未加密)字符串或以与账户关联的身份验证插件期望的格式进行哈希处理:

    • 对于使用 BY '*auth_string*' 语法的情况,字符串是明文的,并传递给身份验证插件进行可能的哈希处理。插件返回的结果存储在 mysql.user 表中。插件可以按照指定的值使用该值,这种情况下不会发生哈希处理。

    • 对于使用 BY RANDOM PASSWORD 语法的情况,MySQL 生成一个随机密码并以明文形式传递给身份验证插件进行可能的哈希处理。插件返回的结果存储在 mysql.user 表中。插件可以按照指定的值使用该值,这种情况下不会发生哈希处理。

      随机生成的密码可在 MySQL 8.0.18 中使用,并具有随机密码生成中描述的特性。

    • 对于使用AS '*auth_string*'语法的语法,假定字符串已经是认证插件所需的格式,并按原样存储在mysql.user表中。如果插件需要哈希值,则该值必须已经以适合插件的格式进行哈希处理;否则,插件无法使用该值,客户端连接的正确认证也不会发生。

      截至 MySQL 8.0.17,哈希字符串可以是字符串文字或十六进制值。当启用print_identified_with_as_hex系统变量时,后者对应于包含不可打印字符的密码哈希的值类型,该变量由SHOW CREATE USER显示。

    • 如果认证插件不对认证字符串进行哈希处理,则BY '*auth_string*'AS '*auth_string*'子句具有相同的效果:认证字符串按原样存储在mysql.user系统表中。

CREATE USER允许这些auth_option语法:

  • IDENTIFIED BY '*auth_string*'

    将帐户认证插件设置为默认插件,将明文'*auth_string*'值传递给插件进行可能的哈希处理,并将结果存储在mysql.user系统表中的帐户行中。

  • IDENTIFIED BY RANDOM PASSWORD

    将帐户认证插件设置为默认插件,生成随机密码,将明文密码值传递给插件进行可能的哈希处理,并将结果存储在mysql.user系统表中的帐户行中。该语句还将明文密码作为结果集返回,以便用户或执行该语句的应用程序可以使用。有关结果集和随机生成密码的特性的详细信息,请参见随机密码生成。

  • IDENTIFIED WITH *auth_plugin*

    将帐户认证插件设置为auth_plugin,将凭据清除为空字符串,并将结果存储在mysql.user系统表中的帐户行中。

  • IDENTIFIED WITH *auth_plugin* BY '*auth_string*'

    将帐户认证插件设置为auth_plugin,将明文'*auth_string*'值传递给插件进行可能的哈希处理,并将结果存储在mysql.user系统表中的帐户行中。

  • IDENTIFIED WITH *auth_plugin* BY RANDOM PASSWORD

    将账户认证插件设置为 auth_plugin,生成一个随机密码,将明文密码值传递给插件进行可能的哈希处理,并将结果存储在 mysql.user 系统表中的账户行中。该语句还将明文密码作为结果集返回,以便用户或执行该语句的应用程序可以访问。有关结果集和随机生成密码的特性的详细信息,请参阅 随机密码生成。

  • IDENTIFIED WITH *auth_plugin* AS '*auth_string*'

    将账户认证插件设置为 auth_plugin,并将 '*auth_string*' 值原样存储在 mysql.user 账户行中。如果插件需要哈希字符串,则假定字符串已经以插件所需的格式进行了哈希处理。

示例:将密码指定为明文;使用默认插件:

CREATE USER 'jeffrey'@'localhost'
  IDENTIFIED BY '*password*';

示例:指定认证插件,以及明文密码值:

CREATE USER 'jeffrey'@'localhost'
  IDENTIFIED WITH mysql_native_password BY '*password*';

在每种情况下,存储在账户行中的密码值是与账户关联的认证插件对其进行哈希后的明文值 '*password*'

有关设置密码和认证插件的其他信息,请参阅 第 8.2.14 节,“分配账户密码” 和 第 8.2.17 节,“可插拔认证”。

创建用户多因素认证选项

CREATE USER 中的 auth_option 部分定义了一种用于单因素认证(1FA/SFA)的认证方法。从 MySQL 8.0.27 开始,CREATE USER 具有支持多因素认证(MFA)的子句,因此账户可以拥有最多三种认证方法。也就是说,账户可以使用双因素认证(2FA)或三因素认证(3FA)。

authentication_policy 系统变量定义了带有多因素认证(MFA)子句的 CREATE USER 语句的约束条件。例如,authentication_policy 设置控制了账户可以拥有的认证因素数量,以及对于每个因素,允许使用的认证方法。请参阅 配置多因素认证策略。

关于确定未指定插件名称的认证子句的默认认证插件的特定规则的信息,请参见默认认证插件。

auth_option之后,可能会出现不同的可选 MFA 子句:

  • 2fa_auth_option:指定二要素认证方法。以下示例将caching_sha2_password定义为第一要素认证方法,将authentication_ldap_sasl定义为第二要素认证方法。

    CREATE USER 'u1'@'localhost'
      IDENTIFIED WITH caching_sha2_password
        BY '*sha2_password*'
      AND IDENTIFIED WITH authentication_ldap_sasl
        AS 'uid=u1_ldap,ou=People,dc=example,dc=com';
    
  • 3fa_auth_option:在2fa_auth_option之后,可能会出现一个3fa_auth_option子句,用于指定第三要素认证方法。以下示例将caching_sha2_password定义为第一要素认证方法,将authentication_ldap_sasl定义为第二要素认证方法,将authentication_fido定义为第三要素认证方法。

    CREATE USER 'u1'@'localhost'
      IDENTIFIED WITH caching_sha2_password
        BY '*sha2_password*'
      AND IDENTIFIED WITH authentication_ldap_sasl
        AS 'uid=u1_ldap,ou=People,dc=example,dc=com'
      AND IDENTIFIED WITH authentication_fido;
    
  • initial_auth_option:指定用于配置 FIDO 无密码认证的初始认证方法。如下所示,需要使用生成的随机密码或用户指定的auth-string进行临时认证,以启用 FIDO 无密码认证。

    CREATE USER *user*
      IDENTIFIED WITH authentication_fido
      INITIAL AUTHENTICATION IDENTIFIED BY {RANDOM PASSWORD | '*auth_string*'};
    

    有关使用 FIDO 可插拔认证配置无密码认证的信息,请参见 FIDO 无密码认证。

创建用户角色选项

DEFAULT ROLE子句定义了用户连接到服务器并进行身份验证时或用户在会话期间执行SET ROLE DEFAULT语句时激活的角色。

每个角色名称使用第 8.2.5 节,“指定角色名称”中描述的格式。例如:

CREATE USER 'joe'@'10.0.0.1' DEFAULT ROLE administrator, developer;

如果省略角色名称的主机名部分,默认为'%'

DEFAULT ROLE子句允许一个或多个逗号分隔的角色名称列表。这些角色必须在执行CREATE USER时存在;否则语句会引发错误(ER_USER_DOES_NOT_EXIST),并且用户不会被创建。

创建用户 SSL/TLS 选项

MySQL 可以检查 X.509 证书属性,除了基于用户名和凭据的通常认证外。有关在 MySQL 中使用 SSL/TLS 的背景信息,请参见第 8.3 节,“使用加密连接”。

要为 MySQL 账户指定与 SSL/TLS 相关的选项,请使用指定一个或多个tls_option值的REQUIRE子句。

REQUIRE选项的顺序不重要,但不能指定两次选项。REQUIRE选项之间的AND关键字是可选的。

CREATE USER 允许这些 tls_option 值:

  • NONE

    表示语句指定的所有帐户没有 SSL 或 X.509 要求。如果用户名和密码有效,则允许未加密连接。如果客户端具有正确的证书和密钥文件,则客户端可以选择使用加密连接。

    CREATE USER 'jeffrey'@'localhost' REQUIRE NONE;
    

    客户端默认尝试建立安全连接。对于具有 REQUIRE NONE 的客户端,如果无法建立安全连接,则连接尝试回退到未加密连接。要求加密连接,客户端只需指定 --ssl-mode=REQUIRED 选项;如果无法建立安全连接,则连接尝试失败。

    如果未指定任何与 SSL 相关的 REQUIRE 选项,则 NONE 是默认值。

  • SSL

    通知服务器只允许通过语句指定的所有帐户进行加密连接。

    CREATE USER 'jeffrey'@'localhost' REQUIRE SSL;
    

    客户端默认尝试建立安全连接。对于具有 REQUIRE SSL 的帐户,如果无法建立安全连接,则连接尝试失败。

  • X509

    对于语句指定的所有帐户,要求客户端提供有效证书,但确切的证书、颁发者和主题并不重要。唯一的要求是应该能够使用其中一个 CA 证书验证其签名。使用 X.509 证书始终意味着加密,因此在这种情况下 SSL 选项是不必要的。

    CREATE USER 'jeffrey'@'localhost' REQUIRE X509;
    

    对于具有 REQUIRE X509 的帐户,客户端必须指定 --ssl-key--ssl-cert 选项进行连接。(建议但不是必须还指定 --ssl-ca,以便验证服务器提供的公共证书。)对于 ISSUERSUBJECT 也是如此,因为这些 REQUIRE 选项暗示了 X509 的要求。

  • ISSUER '*issuer*'

    对于语句指定的所有帐户,要求客户端提供由 CA '*issuer*' 颁发的有效 X.509 证书。如果客户端提供的证书有效但颁发者不同,则服务器拒绝连接。使用 X.509 证书始终意味着加密,因此在这种情况下 SSL 选项是不必要的。

    CREATE USER 'jeffrey'@'localhost'
      REQUIRE ISSUER '/C=SE/ST=Stockholm/L=Stockholm/
        O=MySQL/CN=CA/emailAddress=ca@example.com';
    

    因为 ISSUER 暗示了 X509 的要求,客户端必须指定 --ssl-key--ssl-cert 选项进行连接。(建议但不是必须还指定 --ssl-ca,以便验证服务器提供的公共证书。)

  • SUBJECT '*subject*'

    对于陈述中命名的所有帐户,要求客户端提供包含主题subject的有效 X.509 证书。如果客户端提供了一个有效但主题不同的证书,服务器将拒绝连接。使用 X.509 证书总是意味着加密,因此在这种情况下SSL选项是不必要的。

    CREATE USER 'jeffrey'@'localhost'
      REQUIRE SUBJECT '/C=SE/ST=Stockholm/L=Stockholm/
        O=MySQL demo client certificate/
        CN=client/emailAddress=client@example.com';
    

    MySQL 对'*subject*'值与证书中的值进行简单的字符串比较,因此字母大小写和组件顺序必须与证书中的完全一致。

    因为SUBJECT暗示了X509的要求,客户端必须指定--ssl-key--ssl-cert选项进行连接。(建议但不是必须还指定--ssl-ca,以便验证服务器提供的公共证书。)

  • CIPHER '*cipher*'

    对于陈述中命名的所有帐户,需要为加密连接指定特定的密码方法。这个选项是必需的,以确保使用足够强度的密码和密钥长度。如果使用旧算法和短加密密钥,加密可能会很弱。

    CREATE USER 'jeffrey'@'localhost'
      REQUIRE CIPHER 'EDH-RSA-DES-CBC3-SHA';
    

SUBJECTISSUERCIPHER选项可以在REQUIRE子句中组合使用:

CREATE USER 'jeffrey'@'localhost'
  REQUIRE SUBJECT '/C=SE/ST=Stockholm/L=Stockholm/
    O=MySQL demo client certificate/
    CN=client/emailAddress=client@example.com'
  AND ISSUER '/C=SE/ST=Stockholm/L=Stockholm/
    O=MySQL/CN=CA/emailAddress=ca@example.com'
  AND CIPHER 'EDH-RSA-DES-CBC3-SHA';
创建用户资源限制选项

可以通过讨论中提到的第 8.2.21 节,“设置帐户资源限制”来限制帐户对服务器资源的使用。为此,请使用指定一个或多个resource_option值的WITH子句。

WITH选项的顺序无关紧要,除非给定资源限制被多次指定,最后一次实例优先。

CREATE USER允许这些resource_option值:

  • MAX_QUERIES_PER_HOUR *count*MAX_UPDATES_PER_HOUR *count*MAX_CONNECTIONS_PER_HOUR *count*

    对于陈述中命名的所有帐户,这些选项限制了每个帐户在任何给定的一个小时内对服务器的查询、更新和连接的次数。如果count0(默认值),这意味着该帐户没有限制。

  • MAX_USER_CONNECTIONS *count*

    对于陈述中命名的所有帐户,限制了每个帐户对服务器的最大同时连接数。非零的count明确指定了该帐户的限制。如果count0(默认值),服务器从max_user_connections系统变量的全局值确定该帐户的同时连接数。如果max_user_connections也是零,则该帐户没有限制。

示例:

CREATE USER 'jeffrey'@'localhost'
  WITH MAX_QUERIES_PER_HOUR 500 MAX_UPDATES_PER_HOUR 100;
创建用户密码管理选项

创建用户 支持几个password_option值用于密码管理:

  • 密码过期选项:您可以手动使账户密码过期并建立其密码过期策略。策略选项不会使密码过期。相反,它们确定服务器如何根据密码年龄自动使账户过期,密码年龄是从最近一次账户密码更改的日期和时间开始评估的。

  • 密码重用选项:您可以基于密码更改次数、经过的时间或两者限制密码重用。

  • 需要密码验证的选项:您可以指示尝试更改账户密码是否必须指定当前密码,以验证试图进行更改的用户实际上知道当前密码。

  • 不正确密码失败登录跟踪选项:您可以导致服务器跟踪失败的登录尝试,并临时锁定给出太多连续不正确密码的账户。失败次数和锁定时间可配置。

本节描述了密码管理选项的语法。有关建立密码管理策略的信息,请参阅第 8.2.15 节,“密码管理”。

如果指定了给定类型的多个密码管理选项,则最后一个优先。例如,密码过期默认 密码过期从不等同于密码过期从不

注意

除了与失败登录跟踪相关的选项外,密码管理选项仅适用于使用将凭据存储在 MySQL 内部的身份验证插件的账户。对于使用针对 MySQL 外部凭据系统执行身份验证的插件的账户,密码管理也必须在该系统外部处理。有关内部凭据存储的更多信息,请参阅第 8.2.15 节,“密码管理”。

如果账户密码已被手动过期或密码年龄被认为大于其允许的生命周期根据自动过期策略。在这种情况下,服务器要么断开客户端连接,要么限制其允许的操作(请参阅第 8.2.16 节,“过期密码的服务器处理”)。受限客户端执行的操作会导致错误,直到用户建立新的账户密码。

创建用户 允许这些password_option值来控制密码过期:

  • 密码过期

    立即标记语句命名的所有账户的密码过期。

    CREATE USER 'jeffrey'@'localhost' PASSWORD EXPIRE;
    
  • 密码过期默认

    设置所有语句中命名的帐户,使全局过期策略适用,如default_password_lifetime系统变量指定的那样。

    CREATE USER 'jeffrey'@'localhost' PASSWORD EXPIRE DEFAULT;
    
  • PASSWORD EXPIRE NEVER

    此过期选项覆盖了语句中命名的所有帐户的全局策略。对于每个帐户,它禁用密码过期,使密码永不过期。

    CREATE USER 'jeffrey'@'localhost' PASSWORD EXPIRE NEVER;
    
  • PASSWORD EXPIRE INTERVAL *N* DAY

    此过期选项覆盖了语句中命名的所有帐户的全局策略。对于每个帐户,它将密码寿命设置为N天。以下语句要求每 180 天更改一次密码:

    CREATE USER 'jeffrey'@'localhost' PASSWORD EXPIRE INTERVAL 180 DAY;
    

CREATE USER允许这些password_option值来控制基于所需最小密码更改次数的先前密码重用:

  • PASSWORD HISTORY DEFAULT

    设置所有语句中命名的帐户,使全局关于密码历史长度的策略适用,以禁止在password_history系统变量指定的更改次数之前重用密码。

    CREATE USER 'jeffrey'@'localhost' PASSWORD HISTORY DEFAULT;
    
  • PASSWORD HISTORY *N*

    此历史长度选项覆盖了语句中命名的所有帐户的全局策略。对于每个帐户,它将密码历史长度设置为N个密码,以禁止重用最近选择的N个密码中的任何一个。以下语句禁止重用之前的 6 个密码中的任何一个:

    CREATE USER 'jeffrey'@'localhost' PASSWORD HISTORY 6;
    

CREATE USER允许这些password_option值来控制基于经过时间的先前密码重用:

  • PASSWORD REUSE INTERVAL DEFAULT

    设置所有帐户中命名的语句,使全局关于经过时间的策略适用,以禁止重用新于password_reuse_interval系统变量指定的天数的密码。

    CREATE USER 'jeffrey'@'localhost' PASSWORD REUSE INTERVAL DEFAULT;
    
  • PASSWORD REUSE INTERVAL *N* DAY

    此经过时间选项覆盖了语句中命名的所有帐户的全局策略。对于每个帐户,它将密码重用间隔设置为N天,以禁止重用新于该天数的密码。以下语句禁止密码在 360 天内重用:

    CREATE USER 'jeffrey'@'localhost' PASSWORD REUSE INTERVAL 360 DAY;
    

CREATE USER允许这些password_option值来控制是否尝试更改帐户密码必须指定当前密码,以验证尝试进行更改的用户实际上知道当前密码:

  • PASSWORD REQUIRE CURRENT

    此验证选项覆盖了语句中命名的所有帐户的全局策略。对于每个帐户,它要求密码更改时指定当前密码。

    CREATE USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT;
    
  • PASSWORD REQUIRE CURRENT OPTIONAL

    此验证选项会覆盖语句指定的所有帐户的全局策略。对于每个帐户,不需要密码更改指定当前密码。(当前密码可以给出,但不是必需的。)

    CREATE USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT OPTIONAL;
    
  • PASSWORD REQUIRE CURRENT DEFAULT

    设置所有由帐户命名的语句,以便全局关于密码验证的策略适用,如password_require_current 系统变量所指定的那样。

    CREATE USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT DEFAULT;
    

从 MySQL 8.0.19 开始,CREATE USER 允许这些 password_option 值来控制登录失败跟踪:

  • FAILED_LOGIN_ATTEMPTS *N*

    是否跟踪指定错误密码的帐户登录尝试。N 必须是从 0 到 32767 的数字。值为 0 会禁用登录失败跟踪。大于 0 的值表示多少连续密码失败会导致临时帐户锁定(如果 PASSWORD_LOCK_TIME 也是非零)。

  • PASSWORD_LOCK_TIME {*N* | UNBOUNDED}

    连续登录尝试提供错误密码后锁定帐户的时间。N 必须是从 0 到 32767 的数字,或者 UNBOUNDED。值为 0 会禁用临时帐户锁定。大于 0 的值表示锁定帐户的天数。值为 UNBOUNDED 会导致帐户锁定持续时间无限制;一旦锁定,帐户将保持锁定状态直到解锁。有关解锁发生的条件的信息,请参阅登录失败跟踪和临时帐户锁定。

要进行登录失败跟踪和临时锁定,帐户的 FAILED_LOGIN_ATTEMPTSPASSWORD_LOCK_TIME 选项都必须非零。以下语句创建一个帐户,在连续四次密码失败后保持锁定两天:

CREATE USER 'jeffrey'@'localhost'
  FAILED_LOGIN_ATTEMPTS 4 PASSWORD_LOCK_TIME 2;
创建用户注释和属性选项

从 MySQL 8.0.21 开始,您可以创建一个带有可选注释或属性的帐户,如下所述:

  • 用户注释

    要设置用户注释,请在 CREATE USER 语句中添加 COMMENT '*user_comment*',其中 user_comment 是用户注释的文本。

    示例(省略其他选项):

    CREATE USER 'jon'@'localhost' COMMENT 'Some information about Jon';
    
  • 用户属性

    用户属性是由一个或多个键值对组成的 JSON 对象,并通过在 CREATE USER 中包含 ATTRIBUTE '*json_object*' 来设置。json_object 必须是一个有效的 JSON 对象。

    示例(省略其他选项):

    CREATE USER 'jim'@'localhost'
        ATTRIBUTE '{"fname": "James", "lname": "Scott", "phone": "123-456-7890"}';
    

用户注释和用户属性一起存储在信息模式 USER_ATTRIBUTES 表的 ATTRIBUTE 列中。此查询显示了刚刚用于创建用户 jim@localhost 的语句插入的此表中的行:

mysql> SELECT * FROM INFORMATION_SCHEMA.USER_ATTRIBUTES
 ->    WHERE USER = 'jim' AND HOST = 'localhost'\G
*************************** 1\. row ***************************
     USER: jim
     HOST: localhost
ATTRIBUTE: {"fname": "James", "lname": "Scott", "phone": "123-456-7890"} 1 row in set (0.00 sec)

实际上,COMMENT选项提供了一个快捷方式,用于设置一个只有comment作为其键且其值为选项提供的参数的用户属性。通过执行语句CREATE USER 'jon'@'localhost' COMMENT 'Some information about Jon',并观察它插入到USER_ATTRIBUTES表中的行,您可以看到这一点:

mysql> CREATE USER 'jon'@'localhost' COMMENT 'Some information about Jon';
Query OK, 0 rows affected (0.06 sec)

mysql> SELECT * FROM INFORMATION_SCHEMA.USER_ATTRIBUTES
 ->    WHERE USER = 'jon' AND HOST = 'localhost';
+------+-----------+-------------------------------------------+
| USER | HOST      | ATTRIBUTE                                 |
+------+-----------+-------------------------------------------+
| jon  | localhost | {"comment": "Some information about Jon"} |
+------+-----------+-------------------------------------------+
1 row in set (0.00 sec)

不能在同一CREATE USER语句中同时使用COMMENTATTRIBUTE;尝试这样做会导致语法错误。要同时设置用户评论和用户属性,使用ATTRIBUTE并在其参数中包含具有comment键的值,如下所示:

mysql> CREATE USER 'bill'@'localhost'
 ->        ATTRIBUTE '{"fname":"William", "lname":"Schmidt",
    ->        "comment":"Website developer"}';
Query OK, 0 rows affected (0.16 sec)

由于ATTRIBUTE行的内容是一个 JSON 对象,您可以使用任何适当的 MySQL JSON 函数或运算符来操作它,如下所示:

mysql> SELECT
 ->   USER AS User,
 ->   HOST AS Host,
 ->   CONCAT(ATTRIBUTE->>"$.fname"," ",ATTRIBUTE->>"$.lname") AS 'Full Name',
 ->   ATTRIBUTE->>"$.comment" AS Comment
 -> FROM INFORMATION_SCHEMA.USER_ATTRIBUTES
 -> WHERE USER='bill' AND HOST='localhost';
+------+-----------+-----------------+-------------------+
| User | Host      | Full Name       | Comment           |
+------+-----------+-----------------+-------------------+
| bill | localhost | William Schmidt | Website developer |
+------+-----------+-----------------+-------------------+
1 row in set (0.00 sec)

要为现有用户设置或更改用户评论或用户属性,可以使用带有ALTER USER语句的COMMENTATTRIBUTE选项。

因为用户评论和用户属性在单个JSON列中内部存储在一起,这为它们的最大组合大小设置了一个上限;有关更多信息,请参阅 JSON 存储要求。

有关更多信息和示例,请参阅信息模式USER_ATTRIBUTES表的描述。

创建用户帐户锁定选项

MySQL 支持使用ACCOUNT LOCKACCOUNT UNLOCK选项对帐户进行锁定和解锁,这些选项指定帐户的锁定状态。有关更多讨论,请参阅 Section 8.2.20,“帐户锁定”。

如果指定了多个帐户锁定选项,则最后一个优先。

创建用户二进制日志

如果CREATE USER成功,则写入二进制日志,但如果失败则不写入;在这种情况下,将发生回滚并且不会进行任何更改。写入二进制日志的语句包括所有命名用户。如果给出了IF NOT EXISTS子句,这甚至包括已经存在且未创建的用户。

写入二进制日志的语句为每个用户指定一个身份验证插件,确定如下:

  • 原始语句中指定的插件。

  • 否则,默认的身份验证插件。特别是,如果用户u1已经存在并使用非默认身份验证插件,则为CREATE USER IF NOT EXISTS u1写入二进制日志的语句将列出默认身份验证插件。(如果必须为用户指定非默认身份验证插件,请在原始语句中包含它。)

如果服务器为二进制日志中写入的任何不存在的用户添加默认身份验证插件,则会在错误日志中写入警告,列出这些用户的名称。

如果原始语句指定了FAILED_LOGIN_ATTEMPTSPASSWORD_LOCK_TIME选项,则写入二进制日志的语句将包括该选项。

支持多因素认证(MFA)的子句的CREATE USER语句将写入二进制日志。

  • CREATE USER ... IDENTIFIED WITH .. INITIAL AUTHENTICATION IDENTIFIED WITH ...语句将作为CREATE USER .. IDENTIFIED WITH .. INITIAL AUTHENTICATION IDENTIFIED WITH .. AS '*password-hash*'写入二进制日志,其中password-hash是用户指定的auth-string或服务器在指定RANDOM PASSWORD子句时生成的随机密码。

原文:dev.mysql.com/doc/refman/8.0/en/drop-role.html

15.7.1.4 DROP ROLE Statement

DROP ROLE [IF EXISTS] *role* [, *role* ] ...

DROP ROLE 移除一个或多个角色(具有特权的命名集合)。要使用此语句,您必须具有全局DROP ROLECREATE USER特权。当启用read_only系统变量时,DROP ROLE 还需要CONNECTION_ADMIN特权(或已弃用的SUPER特权)。

从 MySQL 8.0.16 开始,具有CREATE USER特权的用户可以使用此语句删除已锁定或未锁定的账户。具有DROP ROLE特权的用户只能使用此语句删除已锁定的账户(未锁定的账户可能是用于登录到服务器的用户账户,而不仅仅是角色)。

mandatory_roles系统变量值中命名的角色不能被删除。

DROP ROLE 对所有命名角色要么成功,要么回滚并且如果发生任何错误则不会产生影响。默认情况下,如果尝试删除不存在的角色,则会发生错误。如果给出了IF EXISTS子句,则该语句会对每个不存在的命名角色产生警告,而不是错误。

如果成功,该语句将被写入二进制日志,但如果失败则不会;在这种情况下,将发生回滚并且不会进行任何更改。写入二进制日志的语句包括所有命名角色。如果给出了IF EXISTS子句,则即使是不存在且未被删除的角色也会被包括在内。

每个角色名称使用第 8.2.5 节“指定角色名称”中描述的格式。例如:

DROP ROLE 'admin', 'developer';
DROP ROLE 'webapp'@'localhost';

如果省略角色名称的主机名部分,默认为'%'

一个被撤销的角色会自动从授予该角色的任何用户账户(或角色)中撤销。在该账户的任何当前会话中,其调整后的特权将从执行下一个语句开始生效。

有关角色使用示例,请参见第 8.2.10 节“使用角色”。

原文:dev.mysql.com/doc/refman/8.0/en/drop-user.html

15.7.1.5 DROP USER 语句

DROP USER [IF EXISTS] *user* [, *user*] ...

DROP USER 语句会移除一个或多个 MySQL 账户及其权限。它会从所有授权表中移除该账户的权限行。

mandatory_roles 系统变量值中命名的角色不能被删除。

要使用 DROP USER,你必须拥有全局的 CREATE USER 权限,或者对 mysql 系统模式拥有 DELETE 权限。当启用 read_only 系统变量时,DROP USER 还需要 CONNECTION_ADMIN 权限(或者已弃用的 SUPER 权限)。

截至 MySQL 8.0.22 版本,如果要删除的任何账户被命名为任何存储对象的 DEFINER 属性,则 DROP USER 会因错误而失败。(也就是说,如果删除一个账户会导致存储对象变成孤立状态,则该语句会失败。)要执行该操作,你必须拥有 SET_USER_ID 权限;在这种情况下,该语句会成功并产生警告,而不是失败并产生错误。有关更多信息,包括如何识别哪些对象将给定账户命名为 DEFINER 属性,请参阅 Orphan Stored Objects。

DROP USER 对所有命名用户要么全部成功,要么回滚并且在发生任何错误时不会产生影响。默认情况下,如果尝试删除不存在的用户,则会发生错误。如果给出了 IF EXISTS 子句,则该语句会对每个不存在的命名用户产生警告,而不是错误。

如果成功,该语句会被写入二进制日志,但如果失败则不会;在这种情况下,会发生回滚并且不会有任何更改。写入二进制日志的语句包括所有命名用户。如果给出了 IF EXISTS 子句,则即使用户不存在且未被删除,也会包括这些用户。

每个账户名使用 Section 8.2.4, “Specifying Account Names” 中描述的格式。例如:

DROP USER 'jeffrey'@'localhost';

如果省略账户名的主机名部分,则默认为 '%'

重要提示

DROP USER 不会自动关闭任何打开的用户会话。相反,如果删除具有打开会话的用户,则该语句直到该用户的会话关闭后才生效。一旦会话关闭,用户被删除,该用户的下一次登录尝试将失败。这是设计上的

DROP USER 不会自动删除或使旧用户创建的数据库或其中的对象失效。这包括DEFINER属性指定已删除用户的存储过程或视图。如果在定义者安全上下文中执行这些对象,可能会产生错误。(有关安全上下文的信息,请参见第 27.6 节,“存储对象访问控制”。)

原文:dev.mysql.com/doc/refman/8.0/en/grant.html

15.7.1.6 GRANT 语句

GRANT
    *priv_type* [(*column_list*)]
      [, *priv_type* [(*column_list*)]] ...
    ON [*object_type*] *priv_level*
    TO *user_or_role* [, *user_or_role*] ...
    [WITH GRANT OPTION]
    [AS *user*
        [WITH ROLE
            DEFAULT
          | NONE
          | ALL
          | ALL EXCEPT *role* [, *role* ] ...
          | *role* [, *role* ] ...
        ]
    ]
}

GRANT PROXY ON *user_or_role*
    TO *user_or_role* [, *user_or_role*] ...
    [WITH GRANT OPTION]

GRANT *role* [, *role*] ...
    TO *user_or_role* [, *user_or_role*] ...
    [WITH ADMIN OPTION]

*object_type*: {
    TABLE
  | FUNCTION
  | PROCEDURE
}

*priv_level*: {
    *
  | *.*
  | *db_name*.*
  | *db_name.tbl_name*
  | *tbl_name*
  | *db_name*.*routine_name*
}

*user_or_role*: {
    *user* (see Section 8.2.4, “Specifying Account Names”)
  | *role* (see Section 8.2.5, “Specifying Role Names”)
}

GRANT 语句将特权和角色分配给 MySQL 用户账户和角色。GRANT 语句有几个方面,描述如下主题:

  • GRANT 概述

  • 对象引用指南

  • 账户名称

  • MySQL 支持的特权

  • 全局特权

  • 数据库特权

  • 表特权

  • 列特权

  • 存储过程特权

  • 代理用户特权

  • 授予角色

  • AS 子句和特权限制

  • 其他账户特性

  • MySQL 和标准 SQL 版本的 GRANT

GRANT 概述

GRANT 语句使系统管理员能够授予特权和角色,这些可以授予给用户账户和角色。这些语法限制适用:

  • GRANT 不能在同一语句中混合授予特权和角色。给定的 GRANT 语句必须授予特权或角色之一。

  • ON 子句区分语句是授予特权还是角色:

    • 有了 ON,该语句授予特权。

    • 没有 ON,该语句授予角色。

    • 允许将特权和角色同时分配给一个账户,但必须使用单独的 GRANT 语句,每个语句的语法适用于所要授予的内容。

有关角色的更多信息,请参见 第 8.2.10 节,“使用角色”。

要使用GRANT授予特权,您必须具有GRANT OPTION特权,并且必须具有您正在授予的特权。 (或者,如果您对mysql系统模式中的授权表具有UPDATE特权,则可以授予任何账户任何特权。)当启用read_only系统变量时,GRANT还需要CONNECTION_ADMIN特权(或已弃用的SUPER特权)。

GRANT对所有指定的用户和角色都成功时才会成功,如果出现任何错误,则会回滚并且不会产生任何效果。只有当对所有指定的用户和角色都成功时,该语句才会被写入二进制日志。

REVOKE语句与GRANT相关,允许管理员撤销账户特权。参见 Section 15.7.1.8, “REVOKE Statement”。

每个账户名称使用 Section 8.2.4, “Specifying Account Names”中描述的格式。每个角色名称使用 Section 8.2.5, “Specifying Role Names”中描述的格式。例如:

GRANT ALL ON db1.* TO 'jeffrey'@'localhost';
GRANT 'role1', 'role2' TO 'user1'@'localhost', 'user2'@'localhost';
GRANT SELECT ON world.* TO 'role3';

如果省略账户或角色名称的主机名部分,则默认为'%'

通常,数据库管理员首先使用CREATE USER创建一个账户并定义其非特权特征,如密码、是否使用安全连接以及对服务器资源访问的限制,然后使用GRANT定义其特权。可以使用ALTER USER来更改现有账户的非特权特征。例如:

CREATE USER 'jeffrey'@'localhost' IDENTIFIED BY '*password*';
GRANT ALL ON db1.* TO 'jeffrey'@'localhost';
GRANT SELECT ON db2.invoice TO 'jeffrey'@'localhost';
ALTER USER 'jeffrey'@'localhost' WITH MAX_QUERIES_PER_HOUR 90;

mysql程序中,当成功执行GRANT时,会显示Query OK, 0 rows affected。要确定操作的结果产生了哪些特权,请使用SHOW GRANTS。参见 Section 15.7.7.21, “SHOW GRANTS Statement”。

重要提示

在某些情况下,GRANT可能会记录在服务器日志中或在客户端的历史文件中,例如~/.mysql_history,这意味着任何具有读取该信息权限的人都可以读取明文密码。有关在服务器日志中发生这种情况的条件以及如何控制它的信息,请参阅第 8.1.2.3 节,“密码和日志记录”。有关客户端日志记录的类似信息,请参阅第 6.5.1.3 节,“mysql 客户端日志记录”。

GRANT支持长达 255 个字符的主机名(在 MySQL 8.0.17 之前为 60 个字符)。用户名最多可达 32 个字符。数据库、表、列和例程名称最多可达 64 个字符。

警告

不要尝试通过修改mysql.user系统表来更改用户名称的允许长度。这样做会导致不可预测的行为,甚至可能使用户无法登录到 MySQL 服务器。除非通过第三章,“升级 MySQL”中描述的过程,否则永远不要以任何方式更改mysql系统模式中表的结构。

对象引用指南

GRANT语句中,有几个对象需要引用,尽管在许多情况下引用是可选的:账户、角色、数据库、表、列和例程名称。例如,如果账户名中的user_namehost_name值作为未引用的标识符是合法的,那么你无需对其进行引用。然而,引号是必要的,以指定包含特殊字符(如-)的user_name字符串,或包含特殊字符或通配符字符(例如,'test-user'@'%.com')的host_name字符串。分别引用用户名和主机名。

要指定引用值:

  • 将数据库、表、列和例程名称引用为标识符。

  • 将用户名称和主机名引用为标识符或字符串。

  • 将密码作为字符串进行引用。

有关字符串引用和标识符引用的指南,请参阅第 11.1.1 节,“字符串文字”和第 11.2 节,“模式对象名称”。

重要

如下几段所述的通配符字符%_在 MySQL 8.0.35 中已被弃用,因此可能在未来的 MySQL 版本中被移除。

GRANT语句中指定数据库名称时允许使用_%通配符(GRANT ... ON *db_name*.*)。这意味着,例如,要在数据库名称中使用_字符,可以在GRANT语句中使用\转义字符指定为\_,以防止用户能够访问与通配符模式匹配的其他数据库(例如,GRANT ... ON foo_bar.* TO ...)。

包含通配符的多个GRANT语句可能对 DML 语句产生意外效果;在解析涉及通配符的授权时,MySQL 只考虑第一个匹配的授权。换句话说,如果一个用户有两个使用通配符匹配同一数据库的数据库级授权,那么将应用首先创建的授权。考虑使用以下语句创建的数据库db和表t

mysql> CREATE DATABASE db;
Query OK, 1 row affected (0.01 sec)

mysql> CREATE TABLE db.t (c INT);
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO db.t VALUES ROW(1);
Query OK, 1 row affected (0.00 sec)

接下来(假设当前账户是 MySQL root账户或具有必要权限的其他账户),我们创建一个用户u,然后发出两个包含通配符的GRANT语句,如下所示:

mysql> CREATE USER u;
Query OK, 0 rows affected (0.01 sec)

mysql> GRANT SELECT ON `d_`.* TO u;
Query OK, 0 rows affected (0.01 sec)

mysql> GRANT INSERT ON `d%`.* TO u;
Query OK, 0 rows affected (0.00 sec)

mysql> EXIT
Bye

如果我们结束会话,然后使用mysql客户端再次登录,这次作为u,我们会发现该账户只有第一个匹配授权提供的权限,而不是第二个:

$> mysql -uu -hlocalhost
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 8.0.37-tr Source distribution

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

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

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

mysql> TABLE db.t;
+------+
| c    |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

mysql> INSERT INTO db.t VALUES ROW(2);
ERROR 1142 (42000): INSERT command denied to user 'u'@'localhost' for table 't'

在权限分配中,MySQL 在以下情况下将数据库名称中未转义的_% SQL 通配符字符解释为字面字符:

  • 当数据库名称未用于在数据库级别授予权限,而是作为授予权限给其他对象(例如表或例程)的限定符时(例如,GRANT ... ON *db_name*.*tbl_name*)。

  • 启用partial_revokes会导致 MySQL 将数据库名称中未转义的_%通配符字符解释为字面字符,就好像它们已经被转义为\_\%一样。因为这会改变 MySQL 解释权限的方式,建议在可能启用partial_revokes的安装中避免未转义的通配符字符在权限分配中出现。有关更多信息,请参见 Section 8.2.12,“使用部分撤销进行权限限制”。

账户名称

GRANT语句中的user值表示适用于该语句的 MySQL 账户。为了允许向来自任意主机的用户授予权限,MySQL 支持以'*user_name*'@'*host_name*'形式指定user值。

您可以在主机名中指定通配符。例如,'*user_name*'@'%.example.com'适用于example.com域中的user_name,而'*user_name*'@'198.51.100.%'适用于198.51.100类 C 子网中的user_name

简单形式的'*user_name*''*user_name*'@'%'的同义词。

注意

MySQL 自动将授予'*username*'@'%'的所有权限也分配给'*username*'@'localhost'帐户。此行为在 MySQL 8.0.35 及更高版本中已弃用,并可能在将来的 MySQL 版本中删除。

MySQL 不支持用户名称中的通配符。要引用匿名用户,请使用带有空用户名称的帐户在GRANT语句中指定:

GRANT ALL ON test.* TO ''@'localhost' ...;

在这种情况下,任何使用正确密码从本地主机连接的用户都被允许访问匿名用户帐户关联的权限。

有关帐户名称中用户名和主机名值的附加信息,请参阅 Section 8.2.4, “Specifying Account Names”。

警告

如果允许本地匿名用户连接到 MySQL 服务器,则还应将所有本地用户的权限授予为'*user_name*'@'localhost'。否则,当命名用户尝试从本地计算机登录到 MySQL 服务器时,将在mysql.user系统表中使用localhost的匿名用户帐户。有关详细信息,请参阅 Section 8.2.6, “Access Control, Stage 1: Connection Verification”。

要确定此问题是否适用于您,请执行以下查询,列出任何匿名用户:

SELECT Host, User FROM mysql.user WHERE User='';

要避免刚才描述的问题,使用以下语句删除本地匿名用户帐户:

DROP USER ''@'localhost';
MySQL 支持的权限

以下表总结了可以为GRANTREVOKE语句指定的静态和动态priv_type权限类型,以及可以授予每个权限的级别。有关每个权限的更多信息,请参阅 Section 8.2.2, “Privileges Provided by MySQL”。有关静态和动态权限之间的区别,请参阅 Static Versus Dynamic Privileges。

Table 15.11 Permissible Static Privileges for GRANT and REVOKE

权限 意义和可授予级别
ALL [PRIVILEGES] 在指定的访问级别授予所有权限,除了GRANT OPTIONPROXY
ALTER 启用使用ALTER TABLE。级别:全局,数据库,表。
ALTER ROUTINE 启用存储过程的修改或删除。级别:全局,数据库,存储过程。
CREATE 启用数据库和表的创建。级别:全局,数据库,表。
CREATE ROLE 启用角色的创建。级别:全局。
CREATE ROUTINE 启用存储过程的创建。级别:全局,数据库。
CREATE TABLESPACE 启用创建、修改或删除表空间和日志文件组。级别:全局。
CREATE TEMPORARY TABLES 启用使用CREATE TEMPORARY TABLE。级别:全局,数据库。
CREATE USER 启用使用CREATE USERDROP USERRENAME USERREVOKE ALL PRIVILEGES。级别:全局。
CREATE VIEW 启用视图的创建或修改。级别:全局,数据库,表。
DELETE 启用DELETE的使用。级别:全局,数据库,表。
DROP 启用数据库、表和视图的删除。级别:全局,数据库,表。
DROP ROLE 启用角色的删除。级别:全局。
EVENT 启用事件调度器的事件使用。级别:全局,数据库。
EXECUTE 启用用户执行存储过程。级别:全局,数据库,存储过程。
FILE 启用用户使服务器读取或写入文件。级别:全局。
GRANT OPTION 启用将权限授予或从其他帐户中移除的功能。级别:全局,数据库,表,存储过程,代理。
INDEX 启用索引的创建或删除。级别:全局,数据库,表。
INSERT 启用INSERT的使用。级别:全局,数据库,表,列。
LOCK TABLES 允许在具有SELECT权限的表上使用LOCK TABLES。级别:全局,数据库。
PROCESS 允许用户使用SHOW PROCESSLIST查看所有进程。级别:全局。
PROXY 允许��户代理。级别:从用户到用户。
REFERENCES 允许创建外键。级别:全局,数据库,表,列。
RELOAD 允许使用FLUSH操作。级别:全局。
REPLICATION CLIENT 允许用户查询源或副本服务器的位置。级别:全局。
REPLICATION SLAVE 允许副本从源读取二进制日志事件。级别:全局。
SELECT 允许使用SELECT。级别:全局,数据库,表,列。
SHOW DATABASES 允许使用SHOW DATABASES显示所有数据库。级别:全局。
SHOW VIEW 允许使用SHOW CREATE VIEW。级别:全局,数据库,表。
SHUTDOWN 允许使用mysqladmin shutdown。级别:全局。
SUPER 允许使用其他管理操作,如CHANGE REPLICATION SOURCE TOCHANGE MASTER TOKILLPURGE BINARY LOGSSET GLOBAL以及mysqladmin debug命令。级别:全局。
TRIGGER 允许触发器操作。级别:全局,数据库,表。
UPDATE 允许使用UPDATE。级别:全局,数据库,表,列。
USAGE “无权限”的同义词
权限 含义和可授权级别

表 15.12 GRANT 和 REVOKE 的可允许动态权限

Privilege 意义和可授权级别
APPLICATION_PASSWORD_ADMIN 启用双密码管理。级别:全局。
AUDIT_ABORT_EXEMPT 允许通过审计日志过滤器阻止的查询。级别:全局。
AUDIT_ADMIN 启用审计日志配置。级别:全局。
AUTHENTICATION_POLICY_ADMIN 启用认证策略管理。级别:全局。
BACKUP_ADMIN 启用备份管理。级别:全局。
BINLOG_ADMIN 启用二进制日志控制。级别:全局。
BINLOG_ENCRYPTION_ADMIN 启用二进制日志加密的激活和停用。级别:全局。
CLONE_ADMIN 启用克隆管理。级别:全局。
CONNECTION_ADMIN 启用连接限制/限制控制。级别:全局。
ENCRYPTION_KEY_ADMIN 启用InnoDB密钥轮换。级别:全局。
FIREWALL_ADMIN 启用防火墙规则管理,任何用户。级别:全局。
FIREWALL_EXEMPT 免除用户防火墙限制。级别:全局。
FIREWALL_USER 启用防火墙规则管理,自身。级别:全局。
FLUSH_OPTIMIZER_COSTS 启用优化器成本重新加载。级别:全局。
FLUSH_STATUS 启用状态指示器刷新。级别:全局。
FLUSH_TABLES 启用表刷新。级别:全局。
FLUSH_USER_RESOURCES 启用用户资源刷新。级别:全局。
GROUP_REPLICATION_ADMIN 启用组复制控制。级别:全局。
INNODB_REDO_LOG_ARCHIVE 启用重做日志归档管理。级别:全局。
INNODB_REDO_LOG_ENABLE 启用或禁用重做日志记录。级别:全局。
NDB_STORED_USER 启用在 SQL 节点(NDB 集群)之间共享用户或角色。级别:全局。
PASSWORDLESS_USER_ADMIN 启用无密码用户帐户管理。级别:全局。
PERSIST_RO_VARIABLES_ADMIN 启用持久化只读系统变量。级别:全局。
REPLICATION_APPLIER 作为复制通道的PRIVILEGE_CHECKS_USER。级别:全局。
REPLICATION_SLAVE_ADMIN 启用常规复制控制。级别:全局。
RESOURCE_GROUP_ADMIN 启用资源组管理。级别:全局。
RESOURCE_GROUP_USER 启用资源组管理。级别:全局。
ROLE_ADMIN 启用授予或撤销角色,使用WITH ADMIN OPTION。级别:全局。
SESSION_VARIABLES_ADMIN 启用设置受限会话系统变量。级别:全局。
SET_USER_ID 启用设置非自身DEFINER值。级别:全局。
SHOW_ROUTINE 启用访问存储过程定义。级别:全局。
SKIP_QUERY_REWRITE 不重写此用户执行的查询。级别:全局。
SYSTEM_USER 指定帐户为系统帐户。级别:全局。
SYSTEM_VARIABLES_ADMIN 启用修改或持久化全局系统变量。级别:全局。
TABLE_ENCRYPTION_ADMIN 启用覆盖默认加密设置。级别:全局。
TELEMETRY_LOG_ADMIN 启用在 AWS 上配置 MySQL HeatWave 的遥测日志。级别:全局。
TP_CONNECTION_ADMIN 启用线程池连接管理。级别:全局。
VERSION_TOKEN_ADMIN 启用版本令牌函数的使用。级别:全局。
XA_RECOVER_ADMIN 启用XA RECOVER执行。级别:全局。
权限 意义和可授权级别

触发器与表关联。要创建或删除触发器,必须具有表的TRIGGER权限,而不是触发器的权限。

GRANT 语句中,ALL [PRIVILEGES]PROXY 权限必须单独命名,不能与其他权限一起指定。ALL [PRIVILEGES] 代表在要授予权限的级别上可用的所有权限,但不包括GRANT OPTIONPROXY 权限。

MySQL 账户信息存储在 mysql 系统模式的表中。有关更多详细信息,请参阅 第 8.2 节“访问控制和账户管理” ,该节详细讨论了 mysql 系统模式和访问控制系统。

如果授权表包含包含大小写混合的数据库或表名的权限行,并且 lower_case_table_names 系统变量设置为非零值,则无法使用 REVOKE 来撤销这些权限。在这种情况下,需要直接操作授权表。(GRANT 在设置 lower_case_table_names 时不会创建这样的行,但在设置该变量之前可能已创建这样的行。lower_case_table_names 设置只能在服务器启动时配置。)

根据 ON 子句的语法,可以在几个级别授予权限。对于 REVOKE,相同的 ON 语法指定要移除的权限。

对于全局、数据库、表和例程级别,GRANT ALL 仅分配在您授予的级别存在的权限。例如,GRANT ALL ON *db_name*.* 是一个数据库级别的语句,因此不授予任何全局权限,如FILE。授予ALL 不会分配GRANT OPTIONPROXY 权限。

如果存在 object_type 子句,则当以下对象为表、存储函数或存储过程时,应指定为 TABLEFUNCTIONPROCEDURE

用户对数据库、表、列或例程拥有的权限形成逻辑OR的账户权限的逻辑和,包括全局级别。不可能通过在较低级别缺少该权限来否定在更高级别授予的权限。例如,此语句全局授予SELECTINSERT权限:

GRANT SELECT, INSERT ON *.* TO u1;

全局授予的权限适用于所有数据库、表和列,即使在这些较低级别中没有授予。

从 MySQL 8.0.16 开始,如果启用了partial_revokes系统变量,可以显式拒绝在全局级别授予的权限:

GRANT SELECT, INSERT, UPDATE ON *.* TO u1;
REVOKE INSERT, UPDATE ON db1.* FROM u1;

前述语句的结果是,SELECT全局适用于所有表,而INSERTUPDATE全局适用,除了db1中的表。对db1的账户访问是只读的。

权限检查过程的详细信息在第 8.2.7 节,“访问控制,阶段 2:请求验证”中介绍。

如果您为任何用户使用表、列或例程权限,服务器会检查所有用户的表、列和例程权限,这会稍微减慢 MySQL 的速度。同样,如果限制任何用户的查询、更新或连接次数,服务器必须监视这些值。

MySQL 允许您授予不存在的数据库或表的权限。对于表,要授予的权限必须包括CREATE权限。这种行为是有意设计的,旨在使数据库管理员能够为稍后创建的数据库或表准备用户账户和权限。

重要提示

当您删除数据库或表时,MySQL 不会自动撤销任何权限。但是,如果删除例程,则为该例程授予的任何例程级别权限将被撤销。

全局权限

全局权限是管理性的,或者适用于给定服务器上的所有数据库。要分配全局权限,请使用ON *.*语法:

GRANT ALL ON *.* TO 'someuser'@'somehost';
GRANT SELECT, INSERT ON *.* TO 'someuser'@'somehost';

CREATE TABLESPACECREATE USERFILEPROCESSRELOADREPLICATION CLIENTREPLICATION SLAVESHOW DATABASESSHUTDOWNSUPER静态权限是管理权限,只能全局授予。

动态权限都是全局的,只能全局授予。

其他权限可以全局授予或在更具体的级别授予。

在全局级别授予的GRANT OPTION的影响对于静态和动态权限有所不同:

  • 为任何静态全局权限授予的GRANT OPTION适用于所有静态全局权限。

  • 为任何动态权限授予的GRANT OPTION仅适用于该动态权限。

在全局级别使用GRANT ALL将授予所有静态全局权限和所有当前注册的动态权限。在执行GRANT语句后注册的动态权限不会向任何帐户追溯授予。

MySQL 将全局权限存储在mysql.user系统表中。

数据库权限

数据库权限适用于给定数据库中的所有对象。要分配数据库级别的权限,请使用ON *db_name*.*语法:

GRANT ALL ON mydb.* TO 'someuser'@'somehost';
GRANT SELECT, INSERT ON mydb.* TO 'someuser'@'somehost';

如果您使用ON *语法(而不是ON *.*),则权限将分配给默认数据库的数据库级别。如果没有默认数据库,则会出现错误。

CREATEDROPEVENTGRANT OPTIONLOCK TABLESREFERENCES权限可以在数据库级别指定。表或例程权限也可以在数据库级别指定,这样它们将适用于数据库中的所有表或例程。

MySQL 将数据库权限存储在mysql.db系统表中。

表权限

表权限适用于给定表中的所有列。要分配表级别的权限,请使用ON *db_name.tbl_name*语法:

GRANT ALL ON mydb.mytbl TO 'someuser'@'somehost';
GRANT SELECT, INSERT ON mydb.mytbl TO 'someuser'@'somehost';

如果您指定tbl_name而不是db_name.tbl_name,则该语句适用于默认数据库中的tbl_name。如果没有默认数据库,则会出现错误。

表级别的priv_type值可以是ALTERCREATE VIEWCREATEDELETEDROPGRANT OPTIONINDEXINSERTREFERENCESSELECTSHOW VIEWTRIGGERUPDATE

表级权限适用于基本表和视图。它们不适用于使用CREATE TEMPORARY TABLE创建的表,即使表名匹配。有关TEMPORARY表权限的信息,请参见第 15.1.20.2 节,“CREATE TEMPORARY TABLE Statement”。

MySQL 将表权限存储在mysql.tables_priv系统表中。

列权限

列权限适用于给定表中的单个列。在列级别授予权限时,每个权限都必须跟随列或列,括在括号内。

GRANT SELECT (col1), INSERT (col1, col2) ON mydb.mytbl TO 'someuser'@'somehost';

在列级(即使用column_list子句时)的priv_type值可以是INSERTREFERENCESSELECTUPDATE

MySQL 将列权限存储在mysql.columns_priv系统表中。

存储例程权限

ALTER ROUTINECREATE ROUTINEEXECUTEGRANT OPTION权限适用于存储例程(过程和函数)。它们可以在全局和数据库级别授予。除了CREATE ROUTINE外,这些权限可以在单个例程的例程级别授予。

GRANT CREATE ROUTINE ON mydb.* TO 'someuser'@'somehost';
GRANT EXECUTE ON PROCEDURE mydb.myproc TO 'someuser'@'somehost';

例程级别的priv_type值可以是ALTER ROUTINEEXECUTEGRANT OPTIONCREATE ROUTINE不是例程级别的权限,因为您必须在全局或数据库级别具有权限才能首先创建例程。

MySQL 将例程级权限存储在mysql.procs_priv系统表中。

代理用户权限

PROXY 权限允许一个用户代表另一个用户。代理用户冒充或者取代被代理用户的身份;也就是说,它承担了被代理用户的权限。

GRANT PROXY ON 'localuser'@'localhost' TO 'externaluser'@'somehost';

当授予PROXY时,它必须是GRANT语句中唯一命名的权限,并且唯一允许的WITH选项是WITH GRANT OPTION

代理需要代理用户通过插件进行身份验证,当代理用户连接时,插件将返回被代理用户的名称给服务器,并且代理用户必须具有被代理用户的PROXY权限。有关详细信息和示例,请参见第 8.2.19 节,“代理用户”。

MySQL 将代理权限存储在mysql.proxies_priv系统表中。

授予角色

没有ON子句的GRANT语法授予角色而不是单独的权限。角色是一组命名的权限集合;请参见第 8.2.10 节,“使用角色”。例如:

GRANT 'role1', 'role2' TO 'user1'@'localhost', 'user2'@'localhost';

每个要授予的角色必须存在,以及要授予的每个用户帐户或角色。从 MySQL 8.0.16 开始,无法将角色授予匿名用户。

授予角色并不会自动使角色处于活动状态。有关角色激活和停用的信息,请参见激活角色。

授予角色需要以下权限:

  • 如果您拥有ROLE_ADMIN权限(或已弃用的SUPER权限),则可以向用户或角色授予或撤销任何角色。

  • 如果您使用包含WITH ADMIN OPTION子句的GRANT语句授予了一个角色,那么您就能够将该角色授予其他用户或角色,或者从其他用户或角色中撤销该角色,只要在随后授予或撤销该角色时该角色处于活动状态。这包括使用WITH ADMIN OPTION本身的能力。

  • 要授予具有SYSTEM_USER权限的角色,您必须具有SYSTEM_USER权限。

可以使用GRANT创建循环引用。例如:

CREATE USER 'u1', 'u2';
CREATE ROLE 'r1', 'r2';

GRANT 'u1' TO 'u1';   -- simple loop: u1 => u1
GRANT 'r1' TO 'r1';   -- simple loop: r1 => r1

GRANT 'r2' TO 'u2';
GRANT 'u2' TO 'r2';   -- mixed user/role loop: u2 => r2 => u2

允许循环授权引用,但不会向受权用户添加新的权限或角色,因为用户或角色已经拥有其权限和角色。

AS 子句和权限限制

从 MySQL 8.0.16 开始,GRANT有一个AS *user* [WITH ROLE]子句,用于指定关于语句执行所使用的权限上下文的附加信息。这种语法在 SQL 级别可见,尽管其主要目的是通过在二进制日志中显示部分撤销者强加的授权限制,从而实现所有节点之间的统一复制。有关部分撤销的信息,请参见第 8.2.12 节,“使用部分撤销进行权限限制”。

当指定AS *user*子句时,语句执行将考虑与命名用户相关联的任何权限限制,包括WITH ROLE指定的所有角色(如果存在)。结果是,实际授予的权限可能相对于指定的权限有所减少。

这些条件适用于AS *user*子句:

  • 当命名的user具有权限限制时,AS才会生效(这意味着partial_revokes系统变量已启用)。

  • 如果给定了WITH ROLE,则必须将所有命名的角色授予命名的user

  • 命名的user应该是一个 MySQL 帐户,指定为'*user_name*'@'*host_name*'CURRENT_USER,或CURRENT_USER()。当前用户可以与WITH ROLE一起命名,以便执行用户希望GRANT以应用一组在当前会话中活动的角色不同的角色执行。

  • AS不能用于获取执行GRANT语句的用户不具备的权限。执行用户必须至少具有要授予的权限,但AS子句只能限制授予的权限,而不能提升它们。

  • 关于要授予的权限,AS不能指定一个比执行GRANT语句的用户/角色组合拥有更多权限(更少限制)的用户。AS用户/角色组合可以拥有比执行用户更多的权限,但只有在语句不授予这些额外权限时才可以。

  • AS仅支持授予全局权限(ON *.*)。

  • AS不支持PROXY授权。

以下示例说明了AS子句的效果。创建一个具有一些全局权限以及对这些权限的限制的用户u1

CREATE USER u1;
GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO u1;
REVOKE INSERT, UPDATE ON schema1.* FROM u1;
REVOKE SELECT ON schema2.* FROM u1;

同时创建一个角色r1,解除一些权限限制并将该角色授予u1

CREATE ROLE r1;
GRANT INSERT ON schema1.* TO r1;
GRANT SELECT ON schema2.* TO r1;
GRANT r1 TO u1;

现在,使用一个没有自己权限限制的帐户,向多个用户授予相同的全局权限集,但每个用户都受AS子句施加的不同限制,并检查实际授予了哪些权限。

  • 这里的GRANT语句没有AS子句,因此授予的权限正是指定的那些:

    mysql> CREATE USER u2;
    mysql> GRANT SELECT, INSERT, UPDATE ON *.* TO u2;
    mysql> SHOW GRANTS FOR u2;
    +-------------------------------------------------+
    | Grants for u2@%                                 |
    +-------------------------------------------------+
    | GRANT SELECT, INSERT, UPDATE ON *.* TO `u2`@`%` |
    +-------------------------------------------------+
    
  • 这里的GRANT语句有一个AS子句,因此授予的权限是指定的那些,但应用了来自u1的限制:

    mysql> CREATE USER u3;
    mysql> GRANT SELECT, INSERT, UPDATE ON *.* TO u3 AS u1;
    mysql> SHOW GRANTS FOR u3;
    +----------------------------------------------------+
    | Grants for u3@%                                    |
    +----------------------------------------------------+
    | GRANT SELECT, INSERT, UPDATE ON *.* TO `u3`@`%`    |
    | REVOKE INSERT, UPDATE ON `schema1`.* FROM `u3`@`%` |
    | REVOKE SELECT ON `schema2`.* FROM `u3`@`%`         |
    +----------------------------------------------------+
    

    如前所述,AS子句只能添加权限限制;它不能提升权限。因此,尽管u1具有DELETE权限,但由于语句没有指定授予DELETE,所以这不包括在授予的权限中。

  • 这里的GRANT语句的AS子句使角色r1u1生效。该角色解除了u1的一些限制。因此,授予的权限有一些限制,但不像前一个GRANT语句那样多:

    mysql> CREATE USER u4;
    mysql> GRANT SELECT, INSERT, UPDATE ON *.* TO u4 AS u1 WITH ROLE r1;
    mysql> SHOW GRANTS FOR u4;
    +-------------------------------------------------+
    | Grants for u4@%                                 |
    +-------------------------------------------------+
    | GRANT SELECT, INSERT, UPDATE ON *.* TO `u4`@`%` |
    | REVOKE UPDATE ON `schema1`.* FROM `u4`@`%`      |
    +-------------------------------------------------+
    

如果一个GRANT语句包括一个AS *user*子句,则执行该语句的用户的权限限制将被忽略(而不是像在没有AS子句的情况下那样应用)。

其他帐户特征

可选的WITH子句用于使用户能够向其他用户授予权限。WITH GRANT OPTION子句使用户能够将用户在指定权限级别拥有的任何权限授予其他用户。

要向一个帐户授予GRANT OPTION权限,而不改变其它权限,可以这样做:

GRANT USAGE ON *.* TO 'someuser'@'somehost' WITH GRANT OPTION;

谨慎授予GRANT OPTION权限,因为两个具有不同权限的用户可能能够结合权限!

你不能授予另一个用户你自己没有的权限;GRANT OPTION权限使你只能分配你自己拥有的权限。

请注意,当您在特定权限级别授予用户 GRANT OPTION 权限时,用户拥有的(或将来可能被授予的)该级别的任何权限也可以被该用户授予其他用户。假设您在数据库上授予用户 INSERT 权限。然后在数据库上授予 SELECT 权限并指定 WITH GRANT OPTION,那么该用户不仅可以给其他用户 SELECT 权限,还可以给予 INSERT。如果您然后在数据库上授予用户 UPDATE 权限,该用户可以授予 INSERTSELECTUPDATE

对于非管理员用户,不应该在全局或 mysql 系统模式中授予 ALTER 权限。如果这样做,用户可以尝试通过重命名表来破坏权限系统!

有关与特定权限相关的安全风险的更多信息,请参阅 Section 8.2.2, “Privileges Provided by MySQL”。

MySQL 和标准 SQL 版本的 GRANT

MySQL 和标准 SQL 版本的 GRANT 之间最大的区别是:

  • MySQL 将权限与主机名和用户名的组合关联起来,而不仅仅是用户名。

  • 标准 SQL 没有全局或数据库级别的权限,也不支持 MySQL 支持的所有权限类型。

  • MySQL 不支持标准 SQL 的 UNDER 权限。

  • 标准 SQL 权限以分层方式结构化。如果您移除一个用户,该用户被授予的所有权限都将被撤销。如果您在 MySQL 中使用 DROP USER 也是如此。请参阅 Section 15.7.1.5, “DROP USER Statement”。

  • 在标准 SQL 中,当您删除一个表时,该表的所有权限都将被撤销。在标准 SQL 中,当您撤销一个权限时,基于该权限授予的所有权限也将被撤销。在 MySQL 中,权限可以通过 DROP USERREVOKE 语句来撤销。

  • 在 MySQL 中,可以仅对表中的某些列拥有INSERT权限。在这种情况下,只要您为拥有INSERT权限的列插入值,您仍然可以在表上执行INSERT语句。如果严格 SQL 模式未启用,则省略的列将设置为它们的隐式默认值。在严格模式下,如果任何省略的列没有默认值,则该语句将被拒绝。(标准 SQL 要求您对所有列都拥有INSERT权限。)有关严格 SQL 模式和隐式默认值的信息,请参见第 7.1.11 节,“服务器 SQL 模式”和第 13.6 节,“数据类型默认值”。

posted @ 2024-06-23 00:40  绝不原创的飞龙  阅读(5)  评论(0编辑  收藏  举报