MySQL8-中文参考-二十七-

MySQL8 中文参考(二十七)

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

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

15.2.13.2 连接子句

MySQL 支持以下JOIN语法用于SELECT语句的table_references部分以及多表DELETEUPDATE语句:

*table_references:*
    *escaped_table_reference* [, *escaped_table_reference*] ...

*escaped_table_reference*: {
    *table_reference*
  | { OJ *table_reference* }
}

*table_reference*: {
    *table_factor*
  | *joined_table*
}

*table_factor*: {
    *tbl_name* [PARTITION (*partition_names*)]
        [[AS] *alias*] [*index_hint_list*]
  | [LATERAL] *table_subquery* [AS] *alias* [(*col_list*)]
  | ( *table_references* )
}

*joined_table*: {
    *table_reference* {[INNER | CROSS] JOIN | STRAIGHT_JOIN} *table_factor* [*join_specification*]
  | *table_reference* {LEFT|RIGHT} [OUTER] JOIN *table_reference* *join_specification*
  | *table_reference* NATURAL [INNER | {LEFT|RIGHT} [OUTER]] JOIN *table_factor*
}

*join_specification*: {
    ON *search_condition*
  | USING (*join_column_list*)
}

*join_column_list*:
    *column_name* [, *column_name*] ...

*index_hint_list*:
    *index_hint* [, *index_hint*] ...

*index_hint*: {
    USE {INDEX|KEY}
      [FOR {JOIN|ORDER BY|GROUP BY}] ([*index_list*])
  | {IGNORE|FORCE} {INDEX|KEY}
      [FOR {JOIN|ORDER BY|GROUP BY}] (*index_list*)
}

*index_list*:
    *index_name* [, *index_name*] ...

表引用也被称为连接表达式。

表引用(当它引用分区表时)可以包含一个PARTITION子句,包括一个逗号分隔的分区、子分区列表,或两者。此选项跟随表名之后,并在任何别名声明之前。此选项的效果是仅从列出的分区或子分区中选择行。未在列表中命名的任何分区或子分区将被忽略。有关更多信息和示例,请参见第 26.5 节,“分区选择”。

与标准 SQL 相比,MySQL 中table_factor的语法得到了扩展。标准只接受table_reference,而不是在括号中包含它们的列表。

如果将table_reference项目列表中的每个逗号视为等同于内连接,则这是一种保守的扩展。例如:

SELECT * FROM t1 LEFT JOIN (t2, t3, t4)
                 ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)

等同于:

SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4)
                 ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)

在 MySQL 中,JOINCROSS JOININNER JOIN是语法上的等效(它们可以互相替换)。在标准 SQL 中,它们不是等效的。INNER JOINON子句一起使用,否则使用CROSS JOIN

通常情况下,在仅包含内连接操作的连接表达式中,括号可以忽略。MySQL 还支持嵌套连接。参见第 10.2.1.8 节,“嵌套连接优化”。

可以指定索引提示以影响 MySQL 优化器如何使用索引。有关更多信息,请参见第 10.9.4 节,“索引提示”。优化器提示和optimizer_switch系统变量是影响优化器使用索引的其他方法。请参见第 10.9.3 节,“优化器提示”和第 10.9.2 节,“可切换优化”。

下面的列表描述了编写连接时需要考虑的一般因素:

  • 可以使用*tbl_name* AS *alias_name*tbl_name alias_name为表引用取别名:

    SELECT t1.name, t2.salary
      FROM employee AS t1 INNER JOIN info AS t2 ON t1.name = t2.name;
    
    SELECT t1.name, t2.salary
      FROM employee t1 INNER JOIN info t2 ON t1.name = t2.name;
    
  • table_subquery也被称为FROM子句中的派生表或子查询。参见第 15.2.15.8 节,“派生表”。这样的子查询必须包含一个别名,以给子查询结果一个表名,并且可以选择在括号中包含一个表列名列表。以下是一个简单的示例:

    SELECT * FROM (SELECT 1, 2, 3) AS t1;
    
  • 在单个连接中引用的最大表数为 61。这包括通过将 FROM 子句中的派生表和视图合并到外部查询块中处理的连接(参见 Section 10.2.2.4, “Optimizing Derived Tables, View References, and Common Table Expressions with Merging or Materialization”)。

  • 在没有连接条件的情况下,INNER JOIN,(逗号)在语义上是等效的:两者都会在指定的表之间产生笛卡尔积(即,第一个表中的每一行都与第二个表中的每一行连接)。

    然而,逗号运算符的优先级低于 INNER JOINCROSS JOINLEFT JOIN 等。如果在存在连接条件时混合使用逗号连接和其他连接类型,则可能会出现类似 Unknown column '*col_name*' in 'on clause' 的错误。有关处理此问题的信息稍后在本节中给出。

  • ON 一起使用的 search_condition 是可以在 WHERE 子句中使用的任何条件表达式的形式。通常,ON 子句用于指定如何连接表,而 WHERE 子句用于限制结果集中包含哪些行。

  • 如果在 LEFT JOIN 中右表的 ONUSING 部分中没有匹配的行,则会使用所有列均设置为 NULL 的行作为右表。您可以利用这一点找到一个表中没有对应的另一个表中的行:

    SELECT left_tbl.*
      FROM left_tbl LEFT JOIN right_tbl ON left_tbl.id = right_tbl.id
      WHERE right_tbl.id IS NULL;
    

    此示例查找 left_tbl 中所有具有不在 right_tbl 中存在的 id 值的行(即,所有在 right_tbl 中没有对应行的 left_tbl 中的所有行)。参见 Section 10.2.1.9, “Outer Join Optimization”。

  • USING(*join_column_list*) 子句命名了两个表中必须存在的列的列表。如果表 ab 都包含列 c1c2c3,则以下连接将比较来自两个表的对应列:

    a LEFT JOIN b USING (c1, c2, c3)
    
  • 两个表的 NATURAL [LEFT] JOIN 被定义为与使用命名了两个表中所有列的 USING 子句的 INNER JOINLEFT JOIN 在���义上等效。

  • RIGHT JOIN 的工作方式类似于 LEFT JOIN。为了保持代码在各种数据库中的可移植性,建议您使用 LEFT JOIN 而不是 RIGHT JOIN

  • 在连接语法描述中显示的 { OJ ... } 语法仅用于与 ODBC 的兼容性。语法中的大括号应该按照字面意义写入;它们不是在其他语法描述中使用的元语法。

    SELECT left_tbl.*
        FROM { OJ left_tbl LEFT OUTER JOIN right_tbl
               ON left_tbl.id = right_tbl.id }
        WHERE right_tbl.id IS NULL;
    

    您可以在 { OJ ... } 中使用其他类型的连接,例如 INNER JOINRIGHT OUTER JOIN。这有助于与一些第三方应用程序的兼容性,但不是官方的 ODBC 语法。

  • STRAIGHT_JOIN类似于JOIN,不同之处在于左表始终在右表之前读取。这可以用于那些(少数)情况下,连接优化器以次优顺序处理表的情况。

一些连接示例:

SELECT * FROM table1, table2;

SELECT * FROM table1 INNER JOIN table2 ON table1.id = table2.id;

SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id;

SELECT * FROM table1 LEFT JOIN table2 USING (id);

SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id
  LEFT JOIN table3 ON table2.id = table3.id;

根据 SQL:2003 标准处理NATURAL连接和带有USING的连接,包括外连接变体:

  • NATURAL连接的冗余列不会出现。考虑以下一组语句:

    CREATE TABLE t1 (i INT, j INT);
    CREATE TABLE t2 (k INT, j INT);
    INSERT INTO t1 VALUES(1, 1);
    INSERT INTO t2 VALUES(1, 1);
    SELECT * FROM t1 NATURAL JOIN t2;
    SELECT * FROM t1 JOIN t2 USING (j);
    

    在第一个SELECT语句中,列j出现在两个表中,因此成为连接列,因此,根据标准 SQL,它应该在输出中只出现一次,而不是两次。类似地,在第二个 SELECT 语句中,列jUSING子句中命名,应该在输出中只出现一次,而不是两次。

    因此,这些语句产生这个输出:

    +------+------+------+
    | j    | i    | k    |
    +------+------+------+
    |    1 |    1 |    1 |
    +------+------+------+
    +------+------+------+
    | j    | i    | k    |
    +------+------+------+
    |    1 |    1 |    1 |
    +------+------+------+
    

    根据标准 SQL 进行冗余列消除和列排序,产生这个显示顺序:

    • 首先,按照它们在第一个表中出现的顺序,合并两个连接表的共同列

    • 第二,第一个表中独有的列,按照它们在该表中出现的顺序

    • 第三,第二个表中独有的列,按照它们在该表中出现的顺序

    替换两个共同列的单个结果列是使用合并操作定义的。也就是说,对于两个t1.at2.a,生成的单个连接列a被定义为a = COALESCE(t1.a, t2.a),其中:

    COALESCE(x, y) = (CASE WHEN x IS NOT NULL THEN x ELSE y END)
    

    如果连接操作是任何其他连接,则连接的结果列由连接表的所有列的串联组成。

    合并列的定义的一个结果是,对于外连接,如果两个列中的一个始终为NULL,则合并列包含非NULL列的值。如果两个列都不是NULL或都是NULL,那么两个共同列具有相同的值,因此选择哪个作为合并列的值并不重要。解释这个的一个简单方法是将外连接的合并列表示为JOIN的内表的共同列。假设表t1(a, b)和表t2(a, c)具有以下内容:

    t1    t2
    ----  ----
    1 x   2 z
    2 y   3 w
    

    然后,对于这个连接,列a包含t1.a的值:

    mysql> SELECT * FROM t1 NATURAL LEFT JOIN t2;
    +------+------+------+
    | a    | b    | c    |
    +------+------+------+
    |    1 | x    | NULL |
    |    2 | y    | z    |
    +------+------+------+
    

    相比之下,对于这个连接,列a包含t2.a的值。

    mysql> SELECT * FROM t1 NATURAL RIGHT JOIN t2;
    +------+------+------+
    | a    | c    | b    |
    +------+------+------+
    |    2 | z    | y    |
    |    3 | w    | NULL |
    +------+------+------+
    

    将这些结果与使用JOIN ... ON的等效查询进行比较:

    mysql> SELECT * FROM t1 LEFT JOIN t2 ON (t1.a = t2.a);
    +------+------+------+------+
    | a    | b    | a    | c    |
    +------+------+------+------+
    |    1 | x    | NULL | NULL |
    |    2 | y    |    2 | z    |
    +------+------+------+------+
    
    mysql> SELECT * FROM t1 RIGHT JOIN t2 ON (t1.a = t2.a);
    +------+------+------+------+
    | a    | b    | a    | c    |
    +------+------+------+------+
    |    2 | y    |    2 | z    |
    | NULL | NULL |    3 | w    |
    +------+------+------+------+
    
  • USING子句可以重写为比较相应列的ON子句。然而,尽管USINGON类似,但它们并不完全相同。考虑以下两个查询:

    a LEFT JOIN b USING (c1, c2, c3)
    a LEFT JOIN b ON a.c1 = b.c1 AND a.c2 = b.c2 AND a.c3 = b.c3
    

    就确定哪些行满足连接条件而言,这两个连接在语义上是相同的。

    关于确定要显示哪些列进行SELECT *扩展,这两个连接在语义上并不相同。USING连接选择对应列的合并值,而ON连接选择所有表中的所有列。对于USING连接,SELECT *选择这些值:

    COALESCE(a.c1, b.c1), COALESCE(a.c2, b.c2), COALESCE(a.c3, b.c3)
    

    对于ON连接,SELECT *选择这些值:

    a.c1, a.c2, a.c3, b.c1, b.c2, b.c3
    

    在内连接中,COALESCE(a.c1, b.c1)a.c1b.c1相同,因为两列的值相同。在外连接(如LEFT JOIN)中,两列中的一个可以是NULL。该列将从结果中省略。

  • ON子句只能引用其操作数。

    示例:

    CREATE TABLE t1 (i1 INT);
    CREATE TABLE t2 (i2 INT);
    CREATE TABLE t3 (i3 INT);
    SELECT * FROM t1 JOIN t2 ON (i1 = i3) JOIN t3;
    

    该语句因为i3t3中的列,而不是ON子句的操作数而失败,会出现Unknown column 'i3' in 'on clause'错误。要使连接能够被处理,请将语句重写如下:

    SELECT * FROM t1 JOIN t2 JOIN t3 ON (i1 = i3);
    
  • JOIN比逗号运算符(,)具有更高的优先级,因此连接表达式t1, t2 JOIN t3被解释为(t1, (t2 JOIN t3)),而不是((t1, t2) JOIN t3)。这会影响使用ON子句的语句,因为该子句只能引用连接操作数中的列,而优先级会影响这些操作数的解释。

    示例:

    CREATE TABLE t1 (i1 INT, j1 INT);
    CREATE TABLE t2 (i2 INT, j2 INT);
    CREATE TABLE t3 (i3 INT, j3 INT);
    INSERT INTO t1 VALUES(1, 1);
    INSERT INTO t2 VALUES(1, 1);
    INSERT INTO t3 VALUES(1, 1);
    SELECT * FROM t1, t2 JOIN t3 ON (t1.i1 = t3.i3);
    

    JOIN优先于逗号运算符,因此ON子句的操作数为t2t3。因为t1.i1不是任何操作数中的列,结果是一个Unknown column 't1.i1' in 'on clause'错误。

    要使连接能够被处理,可以使用以下策略之一:

    • 使用括号明确地将前两个表分组,以便ON子句的操作数为(t1, t2)t3

      SELECT * FROM (t1, t2) JOIN t3 ON (t1.i1 = t3.i3);
      
    • 避免使用逗号运算符,改用JOIN代替:

      SELECT * FROM t1 JOIN t2 JOIN t3 ON (t1.i1 = t3.i3);
      

    相同的优先级解释也适用于混合逗号运算符与INNER JOINCROSS JOINLEFT JOINRIGHT JOIN的语句,所有这些连接比逗号运算符具有更高的优先级。

  • 与 SQL:2003 标准相比,MySQL 的一个扩展是允许您对NATURALUSING连接的共同(合并的)列进行限定,而标准则不允许。

15.2.14 使用 UNION、INTERSECT 和 EXCEPT 的集合操作

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

  • 结果集列名和数据类型

  • 使用 TABLE 和 VALUES 语句进行集合操作

  • 使用 DISTINCT 和 ALL 的集合操作

  • 使用 ORDER BY 和 LIMIT 的集合操作

  • 集合操作的限制

SQL 集合操作将多个查询块的结果合并为单个结果。查询块,有时也称为简单表,是任何返回结果集的 SQL 语句,例如 SELECT。MySQL 8.0(8.0.19 及更高版本)还支持 TABLEVALUES 语句。有关这些语句的详细信息,请参见本章其他部分中的各自描述。

SQL 标准定义了以下三种集合操作:

  • UNION:将两个查询块的所有结果合并为单个结果,省略任何重复项。

  • INTERSECT:仅合并两个查询块结果中共有的行,省略任何重复项。

  • EXCEPT:对于两个查询块 AB,返回 A 中不在 B 中出现的所有结果,省略任何重复项。

    (一些数据库系统,如 Oracle,使用 MINUS 作为此运算符的名称。MySQL 不支持此功能。)

MySQL 长期支持 UNION;MySQL 8.0 添加了对 INTERSECTEXCEPT 的支持(MySQL 8.0.31 及更高版本)。

每个集合运算符都支持 ALL 修饰符。当 ALL 关键字跟随一个集合运算符时,这会导致结果中包含重复项。有关更多信息和示例,请参阅涵盖各个运算符的以下部分。

所有三个集合运算符还支持 DISTINCT 关键字,用于在结果中消除重复项。由于这是集合运算符的默认行为,通常不需要显式指定 DISTINCT

一般来说,查询块和集合操作可以以任意数量和顺序组合。这里展示了一个大大简化的表示:

*query_block* [*set_op* *query_block*] [*set_op* *query_block*] ...

*query_block*:
    SELECT | TABLE | VALUES

*set_op*:
    UNION | INTERSECT | EXCEPT

这可以更准确地表示,并更详细地描述如下:

*query_expression*:
  [*with_clause*] /* WITH clause */ 
  *query_expression_body*
  [*order_by_clause*] [*limit_clause*] [*into_clause*]

*query_expression_body*:
    *query_term*
 |  *query_expression_body* UNION [ALL | DISTINCT] *query_term*
 |  *query_expression_body* EXCEPT [ALL | DISTINCT] *query_term*

*query_term*:
    *query_primary*
 |  *query_term* INTERSECT [ALL | DISTINCT] *query_primary*

*query_primary*:
    *query_block*
 |  '(' *query_expression_body* [*order_by_clause*] [*limit_clause*] [*into_clause*] ')'

*query_block*:   /* also known as a simple table */
    *query_specification*                     /* SELECT statement */
 |  *table_value_constructor*                 /* VALUES statement */
 |  *explicit_table*                          /* TABLE statement  */

您应该知道INTERSECTUNIONEXCEPT之前进行评估。这意味着,例如,TABLE x UNION TABLE y INTERSECT TABLE z总是被评估为TABLE x UNION (TABLE y INTERSECT TABLE z)。有关更多信息,请参见第 15.2.8 节,“INTERSECT 子句”。

此外,您应该记住,虽然UNIONINTERSECT集合运算符是可交换的(顺序不重要),但EXCEPT不是(操作数的顺序会影响结果)。换句话说,以下所有语句都是正确的:

  • TABLE x UNION TABLE yTABLE y UNION TABLE x产生相同的结果,尽管行的排序可能不同。您可以使用ORDER BY强制它们相同;请参见联合中的 ORDER BY 和 LIMIT。

  • TABLE x INTERSECT TABLE yTABLE y INTERSECT TABLE x返回相同的结果。

  • TABLE x EXCEPT TABLE yTABLE y EXCEPT TABLE x不会产生相同的结果。请参见第 15.2.4 节,“EXCEPT 子句”,以获取示例。

更多信息和示例可以在接下来的章节中找到。

结果集列名和数据类型

集合操作的结果的列名取自第一个查询块的列名。示例:

mysql> CREATE TABLE t1 (x INT, y INT);
Query OK, 0 rows affected (0.04 sec)

mysql> INSERT INTO t1 VALUES ROW(4,-2), ROW(5,9);
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> CREATE TABLE t2 (a INT, b INT);
Query OK, 0 rows affected (0.04 sec)

mysql> INSERT INTO t2 VALUES ROW(1,2), ROW(3,4);
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> TABLE t1 UNION TABLE t2;
+------+------+
| x    | y    |
+------+------+
|    4 |   -2 |
|    5 |    9 |
|    1 |    2 |
|    3 |    4 |
+------+------+
4 rows in set (0.00 sec)

mysql> TABLE t2 UNION TABLE t1;
+------+------+
| a    | b    |
+------+------+
|    1 |    2 |
|    3 |    4 |
|    4 |   -2 |
|    5 |    9 |
+------+------+
4 rows in set (0.00 sec)

对于UNIONEXCEPTINTERSECT查询都是如此。

每个查询块中列的选定位置应具有相同的数据类型。例如,第一个语句选择的第一列应与其他语句选择的第一列具有相同的类型。如果相应结果列的数据类型不匹配,则结果中的列的类型和长度将考虑所有查询块检索的值。例如,结果集中的列长度不受限于第一个语句中的值的长度,如下所示:

mysql> SELECT REPEAT('a',1) UNION SELECT REPEAT('b',20);
+----------------------+
| REPEAT('a',1)        |
+----------------------+
| a                    |
| bbbbbbbbbbbbbbbbbbbb |
+----------------------+

使用TABLEVALUES语句进行集合操作

从 MySQL 8.0.19 开始,您还可以在可以使用等效的SELECT语句的地方使用TABLE语句或VALUES语句。假设表t1t2如下所示创建和填充:

CREATE TABLE t1 (x INT, y INT);
INSERT INTO t1 VALUES ROW(4,-2),ROW(5,9);

CREATE TABLE t2 (a INT, b INT);
INSERT INTO t2 VALUES ROW(1,2),ROW(3,4);

在忽略以VALUES开头的查询输出中的列名的情况下,以下所有UNION查询都产生相同的结果:

SELECT * FROM t1 UNION SELECT * FROM t2;
TABLE t1 UNION SELECT * FROM t2;
VALUES ROW(4,-2), ROW(5,9) UNION SELECT * FROM t2;
SELECT * FROM t1 UNION TABLE t2;
TABLE t1 UNION TABLE t2;
VALUES ROW(4,-2), ROW(5,9) UNION TABLE t2;
SELECT * FROM t1 UNION VALUES ROW(4,-2),ROW(5,9);
TABLE t1 UNION VALUES ROW(4,-2),ROW(5,9);
VALUES ROW(4,-2), ROW(5,9) UNION VALUES ROW(4,-2),ROW(5,9);

要强制列名相同,请将左侧的查询块包装在SELECT语句中,并使用别名,如下所示:

mysql> SELECT * FROM (TABLE t2) AS t(x,y) UNION TABLE t1;
+------+------+
| x    | y    |
+------+------+
|    1 |    2 |
|    3 |    4 |
|    4 |   -2 |
|    5 |    9 |
+------+------+
4 rows in set (0.00 sec)

使用DISTINCTALL进行集合操作

默认情况下,集合操作的结果中会删除重复行。可选的DISTINCT关键字具有相同的效果,但使其显式化。使用可选的ALL关键字,不会删除重复行,结果将包含联合中所有查询的所有匹配行。

你可以在同一查询中混合使用ALLDISTINCT。混合类型的处理方式是,使用DISTINCT的集合操作会覆盖左侧使用ALL的任何操作。可以通过在UNIONINTERSECTEXCEPT后显式地使用DISTINCT,或者在没有跟随DISTINCTALL关键字的情况下隐式地使用集合操作来生成DISTINCT集合。

在 MySQL 8.0.19 及更高版本中,当一个或多个TABLE语句、VALUES语句或两者用于生成集合时,集合操作的工作方式相同。

使用ORDER BYLIMIT的集合操作

要对作为联合、交集或其他集合操作的一部分使用的单个查询块应用ORDER BYLIMIT子句,请将查询块括在括号中,并将子句放在括号内,就像这样:

(SELECT a FROM t1 WHERE a=10 AND b=1 ORDER BY a LIMIT 10)
UNION
(SELECT a FROM t2 WHERE a=11 AND b=2 ORDER BY a LIMIT 10);

(TABLE t1 ORDER BY x LIMIT 10) 
INTERSECT 
(TABLE t2 ORDER BY a LIMIT 10);

对于单个查询块或语句使用ORDER BY并不意味着结果中行的顺序,因为默认情况下,集合操作生成的行是无序的。因此,在这种情况下,ORDER BY通常与LIMIT结合使用,以确定要检索的所选行的子集,即使它并不一定影响这些行在最终结果中的顺序。如果在查询块中没有LIMIT出现ORDER BY,则会被优化掉,因为在任何情况下都没有影响。

要对整个集合操作的结果进行排序或限制,请将ORDER BYLIMIT放在最后一个语句之后:

SELECT a FROM t1
EXCEPT
SELECT a FROM t2 WHERE a=11 AND b=2
ORDER BY a LIMIT 10;

TABLE t1
UNION 
TABLE t2
ORDER BY a LIMIT 10;

如果一个或多个单独的语句使用了ORDER BYLIMIT或两者,并且另外,你希望对整个结果应用ORDER BYLIMIT或两者,则必须将每个这样的单独语句括在括号中。

(SELECT a FROM t1 WHERE a=10 AND b=1)
EXCEPT
(SELECT a FROM t2 WHERE a=11 AND b=2)
ORDER BY a LIMIT 10;

(TABLE t1 ORDER BY a LIMIT 10) 
UNION 
TABLE t2 
ORDER BY a LIMIT 10;

没有ORDER BYLIMIT子句的语句不需要括号;在刚刚显示的两个语句的第二个语句中用(TABLE t2)替换TABLE t2不会改变UNION的结果。

你也可以在集合操作中使用ORDER BYLIMIT,就像在这个使用mysql客户端的示例中所示的那样:

mysql> VALUES ROW(4,-2), ROW(5,9), ROW(-1,3) 
 -> UNION 
 -> VALUES ROW(1,2), ROW(3,4), ROW(-1,3) 
 -> ORDER BY column_0 DESC LIMIT 3;
+----------+----------+
| column_0 | column_1 |
+----------+----------+
|        5 |        9 |
|        4 |       -2 |
|        3 |        4 |
+----------+----------+
3 rows in set (0.00 sec)

(请记住,TABLE语句和VALUES语句都不接受WHERE子句。)

这种类型的ORDER BY不能使用包含表名的列引用(即以tbl_name.col_name格式的名称)。相反,在第一个查询块中提供一个列别名,并在ORDER BY子句中引用该别名。 (你也可以在ORDER BY子句中使用列位置引用该列,但这种列位置的使用已被弃用,因此可能在未来的 MySQL 版本中被移除。)

如果要排序的列被别名,ORDER BY子句必须引用别名,而不是列名。以下两个语句中第一个是允许的,但第二个会因为Unknown column 'a' in 'order clause'错误而失败:

(SELECT a AS b FROM t) UNION (SELECT ...) ORDER BY b;
(SELECT a AS b FROM t) UNION (SELECT ...) ORDER BY a;

为了使UNION结果的行由每个查询块检索的行集合依次组成,需要在每个查询块中选择一个额外的列作为排序列,并在最后一个查询块后添加一个按照该列排序的ORDER BY子句:

(SELECT 1 AS sort_col, col1a, col1b, ... FROM t1)
UNION
(SELECT 2, col2a, col2b, ... FROM t2) ORDER BY sort_col;

为了在各个结果中保持排序顺序,向ORDER BY子句添加一个次要列:

(SELECT 1 AS sort_col, col1a, col1b, ... FROM t1)
UNION
(SELECT 2, col2a, col2b, ... FROM t2) ORDER BY sort_col, col1a;

使用额外的列还可以让你确定每行来自哪个查询块。额外的列还可以提供其他标识信息,比如指示表名的字符串。

集合操作的限制

MySQL 中的集合操作受到一些限制,这些限制在接下来的几段中描述。

包括SELECT语句在内的集合操作有以下限制:

  • 第一个SELECT中的HIGH_PRIORITY没有效果。任何后续SELECT中的HIGH_PRIORITY都会产生语法错误。

  • 仅最后一个SELECT语句可以使用INTO子句。然而,整个UNION结果将被写入INTO输出目的地。

截至 MySQL 8.0.20,这两个包含INTOUNION变体已被弃用;你应该期待它们在未来的 MySQL 版本中被移除的支持:

  • 在查询表达式的尾随查询块中,在FROM之前使用INTO会产生警告。例如:

    ... UNION SELECT * INTO OUTFILE '*file_name*' FROM *table_name*;
    
  • 在查询表达式的括号尾随块中,使用INTO(无论其相对于FROM的位置如何)都会产生警告。例如:

    ... UNION (SELECT * INTO OUTFILE '*file_name*' FROM *table_name*);
    

    这些变体已经被弃用,因为它们很令人困惑,好像它们收集的信息来自命名表而不是整个查询表达式(UNION)。

ORDER BY子句中使用聚合函数的集合操作将被拒绝,并显示ER_AGGREGATE_ORDER_FOR_UNION。虽然错误名称可能暗示这仅适用于UNION查询,但前述情况也适用于EXCEPTINTERSECT查询,如下所示:

mysql> TABLE t1 INTERSECT TABLE t2 ORDER BY MAX(x);
ERROR 3028 (HY000): Expression #1 of ORDER BY contains aggregate function and applies to a UNION, EXCEPT or INTERSECT

锁定子句(比如FOR UPDATELOCK IN SHARE MODE)适用于其后的查询块。这意味着,在与集合操作一起使用的SELECT语句中,只有在查询块和锁定子句被括号括起来时才能使用锁定子句。

15.2.15 子查询

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

15.2.15.1 子查询作为标量操作数

15.2.15.2 使用子查询进行比较

15.2.15.3 使用 ANY、IN 或 SOME 的子查询

15.2.15.4 使用 ALL 的子查询

15.2.15.5 行子查询

15.2.15.6 使用 EXISTS 或 NOT EXISTS 的子查询

15.2.15.7 相关子查询

15.2.15.8 派生表

15.2.15.9 横向派生表

15.2.15.10 子查询错误

15.2.15.11 优化子查询

15.2.15.12 子查询的限制

子查询是另一个语句内的SELECT语句。

所有 SQL 标准要求的子查询形式和操作都得到支持,以及一些 MySQL 特有的功能。

这里是一个子查询的示例:

SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2);

在这个示例中,SELECT * FROM t1 ...外部查询(或外部语句),(SELECT column1 FROM t2)子查询。我们说子查询嵌套在外部查询中,实际上可以在其他子查询中嵌套子查询,深度相当大。子查询必须始终出现在括号内。

子查询的主要优点是:

  • 它们允许查询结构化,以便可以隔离语句的每个部分。

  • 它们提供了执行通常需要复杂连接和联合的操作的替代方法。

  • 许多人发现子查询比复杂的连接或联合更易读。事实上,正是子查询的创新给人们最初的想法,称早期的 SQL 为“结构化查询语言”。

这里是一个示例语句,展示了 SQL 标准规定的子查询语法的主要要点,并在 MySQL 中得到支持:

DELETE FROM t1
WHERE s11 > ANY
 (SELECT COUNT(*) /* no hint */ FROM t2
  WHERE NOT EXISTS
   (SELECT * FROM t3
    WHERE ROW(5*t2.s1,77)=
     (SELECT 50,11*s1 FROM t4 UNION SELECT 50,77 FROM
      (SELECT * FROM t5) AS t5)));

子查询可以返回标量(单个值)、单行、单列或表(一个或多个列的一个或多行)。这些称为标量、列、行和表子查询。通常只能在特定上下文中使用返回特定类型结果的子查询,如下节所述。

子查询可以在哪些类型的语句中使用没有太多限制。子查询可以包含许多普通SELECT可以包含的关键字或子句:DISTINCTGROUP BYORDER BYLIMIT、连接、索引提示、UNION构造、注释、函数等等。

从 MySQL 8.0.19 开始,TABLEVALUES语句可以在子查询中使用。使用VALUES的子查询通常是更冗长的子查询版本,可以使用集合表示法更简洁地重写,或者使用SELECTTABLE语法;假设表ts是使用语句CREATE TABLE ts VALUES ROW(2), ROW(4), ROW(6)创建的,这里显示的语句都是等效的:

SELECT * FROM tt
    WHERE b > ANY (VALUES ROW(2), ROW(4), ROW(6));

SELECT * FROM tt
    WHERE b > ANY (SELECT * FROM ts);

SELECT * FROM tt
    WHERE b > ANY (TABLE ts);

TABLE子查询的示例将在接下来的章节中展示。

子查询的外部语句可以是任何一个:SELECT, INSERT, UPDATE, DELETE, SET, 或 DO

有关优化器如何处理子查询的信息,请参阅第 10.2.2 节,“优化子查询、派生表、视图引用和公共表达式”。有关子查询使用的限制讨论,包括某些形式子查询语法的性能问题,请参阅第 15.2.15.12 节,“子查询的限制”。

原文:dev.mysql.com/doc/refman/8.0/en/scalar-subqueries.html

15.2.15.1 标量操作数的子查询

在其最简单的形式中,子查询是返回单个值的标量子查询。标量子查询是一个简单的操作数,你几乎可以在任何地方使用它,只要单列值或字面值是合法的,并且你可以期望它具有所有操作数具有的特征:数据类型、长度、可以为NULL的指示等。例如:

CREATE TABLE t1 (s1 INT, s2 CHAR(5) NOT NULL);
INSERT INTO t1 VALUES(100, 'abcde');
SELECT (SELECT s2 FROM t1);

这个SELECT中的子查询返回一个单一值('abcde'),其数据类型为CHAR,长度为 5,字符集和排序规则等于CREATE TABLE时生效的默认值,并指示列中的值可以为NULL。标量子查询选择的值的可空性不会被复制,因为如果子查询结果为空,结果就是NULL。对于刚刚显示的子查询,如果t1为空,结果将是NULL,即使s2NOT NULL

在一些情况下,标量子查询无法使用。如果语句只允许使用字面值,你就不能使用子查询。例如,LIMIT需要字面整数参数,而LOAD DATA需要字面字符串文件名。你不能使用子查询来提供这些值。

当你在以下部分看到包含相当简陋结构(SELECT column1 FROM t1)的示例时,请想象你自己的代码包含更加多样化和复杂的结构。

假设我们创建了两个表:

CREATE TABLE t1 (s1 INT);
INSERT INTO t1 VALUES (1);
CREATE TABLE t2 (s1 INT);
INSERT INTO t2 VALUES (2);

然后执行一个SELECT

SELECT (SELECT s1 FROM t2) FROM t1;

结果是2,因为t2中有一行包含一个值为2的列s1

在 MySQL 8.0.19 及更高版本中,前面的查询也可以这样写,使用TABLE

SELECT (TABLE t2) FROM t1;

标量子查询可以是表达式的一部分,但记住括号,即使子查询是为函数提供参数的操作数。例如:

SELECT UPPER((SELECT s1 FROM t1)) FROM t2;

在 MySQL 8.0.19 及更高版本中,可以使用SELECT UPPER((TABLE t1)) FROM t2获得相同的结果。

原文:dev.mysql.com/doc/refman/8.0/en/comparisons-using-subqueries.html

15.2.15.2 使用子查询进行比较

子查询最常见的用法是形式:

*non_subquery_operand* *comparison_operator* (*subquery*)

其中comparison_operator是这些运算符之一:

=  >  <  >=  <=  <>  !=  <=>

例如:

... WHERE 'a' = (SELECT column1 FROM t1)

MySQL 也允许这种结构:

*non_subquery_operand* LIKE (*subquery*)

曾经,子查询的唯一合法位置是在比较的右侧,您可能仍然会发现一些坚持这一点的旧 DBMS。

这是一个常见形式的子查询比较的例子,使用连接无法完成。它查找表t1中所有column1值等于表t2中最大值的行:

SELECT * FROM t1
  WHERE column1 = (SELECT MAX(column2) FROM t2);

这里是另一个例子,这个例子再次使用连接是不可能的,因为它涉及对其中一个表进行聚合。它查找表t1中包含在给定列中出现两次值的所有行:

SELECT * FROM t1 AS t
  WHERE 2 = (SELECT COUNT(*) FROM t1 WHERE t1.id = t.id);

对于将子查询与标量进行比较,子查询必须返回一个标量。对于将子查询与行构造函数进行比较,子查询必须是返回与行构造函数相同数量值的行子查询。参见第 15.2.15.5 节,“行子查询”。

原文:dev.mysql.com/doc/refman/8.0/en/any-in-some-subqueries.html

15.2.15.3 带有 ANY、IN 或 SOME 的子查询

语法:

*operand* *comparison_operator* ANY (*subquery*)
*operand* IN (*subquery*)
*operand* *comparison_operator* SOME (*subquery*)

其中comparison_operator是以下这些运算符之一:

=  >  <  >=  <=  <>  !=

ANY关键字必须跟在比较运算符后面,意思是“如果子查询返回的列中的任何值对比较为TRUE,则返回TRUE”。例如:

SELECT s1 FROM t1 WHERE s1 > ANY (SELECT s1 FROM t2);

假设表t1中有一行包含(10)。如果表t2包含(21,14,7),则表达式为TRUE,因为t2中有一个值7小于10。如果表t2包含(20,10),或者表t2为空,则表达式为FALSE。如果表t2包含(NULL,NULL,NULL),则表达式为unknown(即NULL)。

在与子查询一起使用时,IN= ANY的别名。因此,这两个语句是相同的:

SELECT s1 FROM t1 WHERE s1 = ANY (SELECT s1 FROM t2);
SELECT s1 FROM t1 WHERE s1 IN    (SELECT s1 FROM t2);

当与表达式列表一起使用时,IN= ANY不是同义词。IN可以接受表达式列表,但= ANY不能。参见 Section 14.4.2, “Comparison Functions and Operators”。

NOT IN不是<> ANY的别名,而是<> ALL的别名。参见 Section 15.2.15.4, “Subqueries with ALL”。

SOME这个词是ANY的别名。因此,这两个语句是相同的:

SELECT s1 FROM t1 WHERE s1 <> ANY  (SELECT s1 FROM t2);
SELECT s1 FROM t1 WHERE s1 <> SOME (SELECT s1 FROM t2);

使用SOME这个词的情况很少,但这个例子说明了为什么它可能有用。对大多数人来说,英语短语“a is not equal to any b”意味着“没有 b 等于 a”,但这并不是 SQL 语法的意思。该语法的含义是“有一些 b 不等于 a”。使用<> SOME可以确保每个人都理解查询的真正含义。

从 MySQL 8.0.19 开始,您可以在标量INANYSOME子查询中使用TABLE,前提是表只包含一列。如果t2只有一列,那么本节中先前显示的语句可以写成这样,在每种情况下用TABLE t2替换SELECT s1 FROM t2

SELECT s1 FROM t1 WHERE s1 > ANY (TABLE t2);

SELECT s1 FROM t1 WHERE s1 = ANY (TABLE t2);

SELECT s1 FROM t1 WHERE s1 IN (TABLE t2);

SELECT s1 FROM t1 WHERE s1 <> ANY  (TABLE t2);

SELECT s1 FROM t1 WHERE s1 <> SOME (TABLE t2);

原文:dev.mysql.com/doc/refman/8.0/en/all-subqueries.html

15.2.15.4 带有 ALL 的子查询

语法:

*operand* *comparison_operator* ALL (*subquery*)

单词 ALL 必须跟在比较运算符后面,意思是“如果比较对子查询返回的列中的所有值都为 TRUE,则返回 TRUE”。例如:

SELECT s1 FROM t1 WHERE s1 > ALL (SELECT s1 FROM t2);

假设表 t1 中包含一行 (10)。如果表 t2 包含 (-5,0,+5),表达式为 TRUE,因为 10 大于 t2 中的所有三个值。如果表 t2 包含 (12,6,NULL,-100),表达式为 FALSE,因为 t2 中有一个值 12 大于 10。如果表 t2 包含 (0,NULL,1),表达式为 unknown(即 NULL)。

最后,如果表 t2 是空的,表达式为 TRUE。因此,当表 t2 为空时,以下表达式为 TRUE

SELECT * FROM t1 WHERE 1 > ALL (SELECT s1 FROM t2);

但是当表 t2 为空时,此表达式为 NULL

SELECT * FROM t1 WHERE 1 > (SELECT s1 FROM t2);

此外,当表 t2 为空时,以下表达式为 NULL

SELECT * FROM t1 WHERE 1 > ALL (SELECT MAX(s1) FROM t2);

一般来说,包含 NULL 值的表空表 是“边缘情况”。在编写子查询时,始终考虑是否考虑了这两种可能性。

NOT IN<> ALL 的别名。因此,这两个语句是相同的:

SELECT s1 FROM t1 WHERE s1 <> ALL (SELECT s1 FROM t2);
SELECT s1 FROM t1 WHERE s1 NOT IN (SELECT s1 FROM t2);

MySQL 8.0.19 支持 TABLE 语句。与 INANYSOME 一样,你可以在 TABLE 中使用 ALLNOT IN,前提是满足以下两个条件:

  • 子查询中只包含一列

  • 子查询不依赖于列表达式

例如,假设表 t2 只包含一列,前面显示的最后两个语句可以这样使用 TABLE t2 编写:

SELECT s1 FROM t1 WHERE s1 <> ALL (TABLE t2);
SELECT s1 FROM t1 WHERE s1 NOT IN (TABLE t2);

无法使用 TABLE t2 编写诸如 SELECT * FROM t1 WHERE 1 > ALL (SELECT MAX(s1) FROM t2); 这样的查询,因为子查询依赖于列表达式。

原文:dev.mysql.com/doc/refman/8.0/en/row-subqueries.html

15.2.15.5 行子查询

标量或列子查询返回单个值或一列值。行子查询是一种返回单行的子查询变体,因此可以返回多个列值。行子查询比较的合法运算符有:

=  >  <  >=  <=  <>  !=  <=>

以下是两个示例:

SELECT * FROM t1
  WHERE (col1,col2) = (SELECT col3, col4 FROM t2 WHERE id = 10);
SELECT * FROM t1
  WHERE ROW(col1,col2) = (SELECT col3, col4 FROM t2 WHERE id = 10);

对于这两个查询,如果表t2包含一个id = 10的单行,子查询返回一个单行。如果此行的col3col4值等于任何t1行的col1col2值,则WHERE表达式为TRUE,每个查询都返回这些t1行。如果t2行的col3col4值不等于任何t1行的col1col2值,则表达式为FALSE,查询返回一个空结果集。如果子查询未产生行,则表达式为未知(即NULL)。如果子查询产生多行,则会出现错误,因为行子查询最多只能返回一行。

有关每个运算符如何用于行比较的信息,请参阅第 14.4.2 节,“比较函数和运算符”。

表达式(1,2)ROW(1,2)有时被称为行构造器。这两者是等价的。子查询返回的行构造器和行必须包含相同数量的值。

行构造器用于与返回两个或更多列的子查询进行比较。当子查询返回单列时,这被视为标量值而不是行,因此不能将行构造器与不返回至少两列的子查询一起使用。因此,以下查询由于语法错误而失败:

SELECT * FROM t1 WHERE ROW(1) = (SELECT column1 FROM t2)

行构造器在其他情境下也是合法的。例如,以下两个语句在语义上是等价的(并且由优化器以相同方式处理):

SELECT * FROM t1 WHERE (column1,column2) = (1,1);
SELECT * FROM t1 WHERE column1 = 1 AND column2 = 1;

以下查询回答了请求,“找到表t1中存在于表t2中的所有行”:

SELECT column1,column2,column3
  FROM t1
  WHERE (column1,column2,column3) IN
         (SELECT column1,column2,column3 FROM t2);

有关优化器和行构造器的更多信息,请参阅第 10.2.1.22 节,“行构造器表达式优化”

原文:dev.mysql.com/doc/refman/8.0/en/exists-and-not-exists-subqueries.html

15.2.15.6 带有 EXISTS 或 NOT EXISTS 的子查询

如果子查询返回任何行,EXISTS *子查询*TRUE,而 NOT EXISTS *子查询*FALSE。例如:

SELECT column1 FROM t1 WHERE EXISTS (SELECT * FROM t2);

传统上,EXISTS 子查询以 SELECT * 开头,但也可以以 SELECT 5SELECT column1 或任何其他内容开头。MySQL 会忽略这种子查询中的 SELECT 列表,因此不会有任何区别。

对于前面的示例,如果 t2 包含任何行,即使行中只有 NULL 值,EXISTS 条件也为 TRUE。这实际上是一个不太可能的示例,因为 [NOT] EXISTS 子查询几乎总是包含相关性。以下是一些更现实的示例:

  • 一个或多个城市中存在什么样的商店?

    SELECT DISTINCT store_type FROM stores
      WHERE EXISTS (SELECT * FROM cities_stores
                    WHERE cities_stores.store_type = stores.store_type);
    
  • 没有城市中存在什么样的商店?

    SELECT DISTINCT store_type FROM stores
      WHERE NOT EXISTS (SELECT * FROM cities_stores
                        WHERE cities_stores.store_type = stores.store_type);
    
  • 所有城市中存在什么样的商店?

    SELECT DISTINCT store_type FROM stores
      WHERE NOT EXISTS (
        SELECT * FROM cities WHERE NOT EXISTS (
          SELECT * FROM cities_stores
           WHERE cities_stores.city = cities.city
           AND cities_stores.store_type = stores.store_type));
    

最后一个示例是一个双重嵌套的 NOT EXISTS 查询。也就是说,它在一个 NOT EXISTS 子句中有一个 NOT EXISTS 子句。形式上,它回答了“是否存在一个城市有一个不在 Stores 中的商店”这个问题?但更容易说的是,嵌套的 NOT EXISTS 回答了“对于所有 yx 是否都为 TRUE?”

在 MySQL 8.0.19 及更高版本中,您还可以在子查询中使用 NOT EXISTSNOT EXISTSTABLE,就像这样:

SELECT column1 FROM t1 WHERE EXISTS (TABLE t2);

结果与在子查询中没有 WHERE 子句的情况下使用 SELECT * 相同。

原文:dev.mysql.com/doc/refman/8.0/en/correlated-subqueries.html

15.2.15.7 相关子查询

相关子查询 是一个包含对外部查询中也出现的表的引用的子查询。例如:

SELECT * FROM t1
  WHERE column1 = ANY (SELECT column1 FROM t2
                       WHERE t2.column2 = t1.column2);

注意,子查询包含对 t1 列的引用,即使子查询的 FROM 子句没有提及表 t1。因此,MySQL 查找子查询外部,在外部查询中找到 t1

假设表 t1 包含一行,其中 column1 = 5column2 = 6;同时,表 t2 包含一行,其中 column1 = 5column2 = 7。简单表达式 ... WHERE column1 = ANY (SELECT column1 FROM t2) 将是 TRUE,但在这个例子中,子查询中的 WHERE 子句是 FALSE(因为 (5,6) 不等于 (5,7)),因此整个表达式是 FALSE

作用域规则: MySQL 从内到外进行评估。例如:

SELECT column1 FROM t1 AS x
  WHERE x.column1 = (SELECT column1 FROM t2 AS x
    WHERE x.column1 = (SELECT column1 FROM t3
      WHERE x.column2 = t3.column1));

在这个语句中,x.column2 必须是表 t2 中的一列,因为 SELECT column1 FROM t2 AS x ... 重命名了 t2。它不是表 t1 中的一列,因为 SELECT column1 FROM t1 ... 是一个更远处的外部查询。

从 MySQL 8.0.24 开始,当 optimizer_switch 变量的 subquery_to_derived 标志启用时,优化器可以将相关标量子查询转换为派生表。考虑这里显示的查询:

SELECT * FROM t1 
    WHERE ( SELECT a FROM t2 
              WHERE t2.a=t1.a ) > 0;

为了避免为给定的派生表多次实例化,我们可以代替多次实例化一个派生表,该派生表在内部查询中引用的表(t2.a)上添加一个分组,然后在提升的谓词(t1.a = derived.a)上进行外连接,以选择正确的组与外部行匹配。 (如果子查询已经有明确的分组,则额外的分组将添加到分组列表的末尾。)因此,先前显示的查询可以像这样重写:

SELECT t1.* FROM t1 
    LEFT OUTER JOIN
        (SELECT a, COUNT(*) AS ct FROM t2 GROUP BY a) AS derived
    ON  t1.a = derived.a 
        AND 
        REJECT_IF(
            (ct > 1),
            "ERROR 1242 (21000): Subquery returns more than 1 row"
            )
    WHERE derived.a > 0;

在重写的查询中,REJECT_IF() 表示一个内部函数,用于测试给定条件(这里是比较 ct > 1)并在条件为真时引发给定错误(在本例中是 ER_SUBQUERY_NO_1_ROW)。这反映了优化器在评估 JOINWHERE 子句之前执行的基数检查,之后才评估任何提升的谓词,只有在子查询不返回多于一行时才执行。

只有满足以下条件时,才能执行这种类型的转换:

  • 子查询可以是SELECT列表、WHERE条件或HAVING条件的一部分,但不能是JOIN条件的一部分,并且不能包含LIMITOFFSET子句。此外,子查询不能包含任何集合操作,如UNION

  • WHERE 子句可以包含一个或多个谓词,并用AND组合。如果WHERE 子句包含一个OR子句,则无法进行转换。WHERE 子句中至少有一个谓词必须符合转换条件,且没有一个谓词可以拒绝转换。

  • 要符合转换的条件,WHERE 子句谓词必须是一个等式谓词,其中每个操作数都应该是一个简单的列引用。没有其他谓词—包括其他比较谓词—符合转换条件。该谓词必须使用等号操作符=进行比较;在这种情况下,不支持空安全≪=>操作符。

  • 只包含内部引用的WHERE子句谓词不符合转换条件,因为它可以在分组之前进行评估。只包含外部引用的WHERE子句谓词符合转换条件,即使它可以提升到外部查询块。这是通过在派生表中添加一个不带分组的基数检查来实现的。

  • 要符合条件,WHERE 子句谓词必须有一个操作数仅包含内部引用,另一个操作数仅包含外部引用。如果由于此规则而使谓词不符合条件,则拒绝转换查询。

  • 相关列只能存在于子查询的WHERE子句中(而不是SELECT列表、JOINORDER BY子句、GROUP BY列表或HAVING子句)。子查询的FROM列表中也不能有任何相关列。

  • 相关列不能包含在聚合函数的参数列表中。

  • 相关列必须在直接包含待转换子查询的查询块中解析。

  • WHERE子句中的嵌套标量子查询中不能存在相关列。

  • 子查询不能包含任何窗口函数,并且不能包含在子查询外部的查询块中聚合的任何聚合函数。如果SELECT列表元素中包含COUNT()聚合函数,则必须在最高级别,并且不能是表达式的一部分。

另请参阅第 15.2.15.8 节,“派生表”。

原文:dev.mysql.com/doc/refman/8.0/en/derived-tables.html

15.2.15.8 派生表

本节讨论了派生表的一般特性。有关由LATERAL关键字引导的横向派生表的信息,请参见 Section 15.2.15.9,“横向派生表”。

派生表是在查询FROM子句的范围内生成表的表达式。例如,在SELECT语句的FROM子句中的子查询是一个派生表:

SELECT ... FROM (*subquery*) [AS] *tbl_name* ...

JSON_TABLE()函数生成一个表,并提供了创建派生表的另一种方法:

SELECT * FROM JSON_TABLE(*arg_list*) [AS] *tbl_name* ...

[AS] *tbl_name*子句是必需的,因为FROM子句中的每个表都必须有一个名称。派生表中的任何列必须具有唯一的名称。或者,tbl_name`后面可以跟着一个括号括起来的列名列表:

SELECT ... FROM (*subquery*) [AS] *tbl_name* (*col_list*) ...

列名的数量必须与表列的数量相同。

为了说明问题,假设您有这个表:

CREATE TABLE t1 (s1 INT, s2 CHAR(5), s3 FLOAT);

下面是如何在FROM子句中使用子查询,使用示例表:

INSERT INTO t1 VALUES (1,'1',1.0);
INSERT INTO t1 VALUES (2,'2',2.0);
SELECT sb1,sb2,sb3
  FROM (SELECT s1 AS sb1, s2 AS sb2, s3*2 AS sb3 FROM t1) AS sb
  WHERE sb1 > 1;

结果:

+------+------+------+
| sb1  | sb2  | sb3  |
+------+------+------+
|    2 | 2    |    4 |
+------+------+------+

这里是另一个例子:假设您想知道一个分组表的一组求和的平均值。这不起作用:

SELECT AVG(SUM(column1)) FROM t1 GROUP BY column1;

然而,这个查询提供了所需的信息:

SELECT AVG(sum_column1)
  FROM (SELECT SUM(column1) AS sum_column1
        FROM t1 GROUP BY column1) AS t1;

注意,在子查询中使用的列名(sum_column1)在外部查询中被识别。

派生表的列名来自其选择列表:

mysql> SELECT * FROM (SELECT 1, 2, 3, 4) AS dt;
+---+---+---+---+
| 1 | 2 | 3 | 4 |
+---+---+---+---+
| 1 | 2 | 3 | 4 |
+---+---+---+---+

要明确提供列名,请在派生表名称后面跟着一个括号括起来的列名列表:

mysql> SELECT * FROM (SELECT 1, 2, 3, 4) AS dt (a, b, c, d);
+---+---+---+---+
| a | b | c | d |
+---+---+---+---+
| 1 | 2 | 3 | 4 |
+---+---+---+---+

派生表可以返回标量、列、行或表。

派生表受到以下限制:

  • 派生表不能包含对同一SELECT的其他表的引用(使用LATERAL派生表进行处理;请参见 Section 15.2.15.9,“横向派生表”)。

  • 在 MySQL 8.0.14 之前,派生表不能包含外部引用。这是 MySQL 在 MySQL 8.0.14 中解除的限制,而不是 SQL 标准的限制。例如,以下查询中的派生表dt包含对外部查询中表t1的引用t1.b

    SELECT * FROM t1
    WHERE t1.d > (SELECT AVG(dt.a)
                    FROM (SELECT SUM(t2.a) AS a
                          FROM t2
                          WHERE t2.b = t1.b GROUP BY t2.c) dt
                  WHERE dt.a > 10);
    

    该查询在 MySQL 8.0.14 及更高版本中有效。在 8.0.14 之前,它会产生一个错误:Unknown column 't1.b' in 'where clause'

优化器以一种不需要将派生表实例化的方式确定有关派生表的信息,因此EXPLAIN不需要将其实例化。请参见 Section 10.2.2.4,“使用合并或实例化优化派生表、视图引用和通用表达式”。

在某些情况下,使用EXPLAIN SELECT可能会修改表数据。如果外部查询访问任何表,并且内部查询调用修改表中一个或多个行的存储函数,则可能会发生这种情况。假设数据库d1中有两个表t1t2,以及一个修改t2的存储函数f1,创建如下所示:

CREATE DATABASE d1;
USE d1;
CREATE TABLE t1 (c1 INT);
CREATE TABLE t2 (c1 INT);
CREATE FUNCTION f1(p1 INT) RETURNS INT
  BEGIN
    INSERT INTO t2 VALUES (p1);
    RETURN p1;
  END;

直接在EXPLAIN SELECT中引用函数对t2没有影响,如下所示:

mysql> SELECT * FROM t2;
Empty set (0.02 sec)

mysql> EXPLAIN SELECT f1(5)\G
*************************** 1\. row ***************************
           id: 1
  select_type: SIMPLE
        table: NULL
   partitions: NULL
         type: NULL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: NULL
     filtered: NULL
        Extra: No tables used 1 row in set (0.01 sec)

mysql> SELECT * FROM t2;
Empty set (0.01 sec)

这是因为SELECT语句没有引用任何表,可以在输出的tableExtra列中看到。这也适用于以下嵌套的SELECT

mysql> EXPLAIN SELECT NOW() AS a1, (SELECT f1(5)) AS a2\G
*************************** 1\. row ***************************
           id: 1
  select_type: PRIMARY
        table: NULL
         type: NULL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: NULL
     filtered: NULL
        Extra: No tables used 1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS;
+-------+------+------------------------------------------+
| Level | Code | Message                                  |
+-------+------+------------------------------------------+
| Note  | 1249 | Select 2 was reduced during optimization |
+-------+------+------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT * FROM t2;
Empty set (0.00 sec)

但是,如果外部SELECT引用任何表,优化器也会执行子查询中的语句,结果导致t2被修改:

mysql> EXPLAIN SELECT * FROM t1 AS a1, (SELECT f1(5)) AS a2\G
*************************** 1\. row ***************************
           id: 1
  select_type: PRIMARY
        table: <derived2>
   partitions: NULL
         type: system
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: NULL
*************************** 2\. row ***************************
           id: 1
  select_type: PRIMARY
        table: a1
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: NULL
*************************** 3\. row ***************************
           id: 2
  select_type: DERIVED
        table: NULL
   partitions: NULL
         type: NULL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: NULL
     filtered: NULL
        Extra: No tables used 3 rows in set (0.00 sec)

mysql> SELECT * FROM t2;
+------+
| c1   |
+------+
|    5 |
+------+
1 row in set (0.00 sec)

衍生表优化也可以与许多相关的(标量)子查询一起使用(MySQL 8.0.24 及更高版本)。有关更多信息和示例,请参见第 15.2.15.7 节,“相关子查询”。

原文:dev.mysql.com/doc/refman/8.0/en/lateral-derived-tables.html

15.2.15.9 侧向派生表

通常,派生表不能引用同一FROM子句中之前表的列。从 MySQL 8.0.14 开始,可以将派生表定义为侧向派生表,以指定允许这种引用。

非侧向派生表使用第 15.2.15.8 节“派生表”中讨论的语法来指定。侧向派生表的语法与非侧向派生表相同,只是在派生表规范之前指定了关键字LATERALLATERAL关键字必须在每个要用作侧向派生表的表之前。

侧向派生表受到以下限制:

  • 侧向派生表只能出现在FROM子句中,可以是用逗号分隔的表列表,也可以是连接规范(JOININNER JOINCROSS JOINLEFT [OUTER] JOINRIGHT [OUTER] JOIN)中。

  • 如果一个侧向派生表在连接子句的右操作数中,并且包含对左操作数的引用,则连接操作必须是INNER JOINCROSS JOINLEFT [OUTER] JOIN

    如果表在左操作数中,并且包含对右操作数的引用,则连接操作必须是INNER JOINCROSS JOINRIGHT [OUTER] JOIN

  • 如果一个侧向派生表引用了一个聚合函数,则该函数的聚合查询不能是包含侧向派生表的FROM子句所属的查询。

  • 根据 SQL 标准,MySQL 始终将与表函数(如JSON_TABLE())的连接视为已使用LATERAL。这在 MySQL 的任何版本中都是正确的,这就是为什么即使在 MySQL 8.0.14 之前的版本中也可以针对此函数进行连接。在 MySQL 8.0.14 及更高版本中,LATERAL关键字是隐式的,并且不允许在JSON_TABLE()之前使用。这也符合 SQL 标准。

以下讨论显示了侧向派生表如何使得某些 SQL 操作成为可能,这些操作无法通过非侧向派生表完成,或者需要更低效的解决方法。

假设我们想解决这个问题:给定一个销售团队成员的表(其中每行描述一个销售团队成员),以及所有销售的表(其中每行描述一笔销售:销售人员、客户、金额、日期),确定每个销售人员的最大销售额及其客户。这个问题可以有两种方法来解决。

解决问题的第一种方法:对于每个销售人员,计算最大销售额,并找到提供此最大销售额的客户。在 MySQL 中,可以这样做:

SELECT
  salesperson.name,
  -- find maximum sale size for this salesperson
  (SELECT MAX(amount) AS amount
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id)
  AS amount,
  -- find customer for this maximum size
  (SELECT customer_name
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id
    AND all_sales.amount =
         -- find maximum size, again
         (SELECT MAX(amount) AS amount
           FROM all_sales
           WHERE all_sales.salesperson_id = salesperson.id))
  AS customer_name
FROM
  salesperson;

那个查询是低效的,因为它在每个销售人员中计算最大尺寸两次(在第一个子查询中一次,在第二个子查询中一次)。

我们可以尝试通过在每个销售人员中计算最大值并在派生表中“缓存”它来实现效率提升,如这个修改后的查询所示:

SELECT
  salesperson.name,
  max_sale.amount,
  max_sale_customer.customer_name
FROM
  salesperson,
  -- calculate maximum size, cache it in transient derived table max_sale
  (SELECT MAX(amount) AS amount
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id)
  AS max_sale,
  -- find customer, reusing cached maximum size
  (SELECT customer_name
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id
    AND all_sales.amount =
        -- the cached maximum size
        max_sale.amount)
  AS max_sale_customer;

然而,在 SQL-92 中,该查询是非法的,因为派生表不能依赖于同一 FROM 子句中的其他表。派生表必须在查询的持续时间内保持恒定,不能包含对其他 FROM 子句表列的引用。如此编写的查询会产生以下错误:

ERROR 1054 (42S22): Unknown column 'salesperson.id' in 'where clause'

在 SQL:1999 中,如果派生表前面有 LATERAL 关键字(表示“这个派生表依赖于其左侧的先前表”),则查询变得合法:

SELECT
  salesperson.name,
  max_sale.amount,
  max_sale_customer.customer_name
FROM
  salesperson,
  -- calculate maximum size, cache it in transient derived table max_sale
  LATERAL
  (SELECT MAX(amount) AS amount
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id)
  AS max_sale,
  -- find customer, reusing cached maximum size
  LATERAL
  (SELECT customer_name
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id
    AND all_sales.amount =
        -- the cached maximum size
        max_sale.amount)
  AS max_sale_customer;

一个侧向派生表不需要是恒定的,并且每当依赖的前一个表中的新行被顶层查询处理时,它就会被更新。

解决问题的第二种方法:如果 SELECT 列表中的子查询可以返回多列,则可以使用不同的解决方案:

SELECT
  salesperson.name,
  -- find maximum size and customer at same time
  (SELECT amount, customer_name
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id
    ORDER BY amount DESC LIMIT 1)
FROM
  salesperson;

那是高效的但是非法的。它不起作用,因为这样的子查询只能返回单列:

ERROR 1241 (21000): Operand should contain 1 column(s)

重写查询的一种尝试是从派生表中选择多列:

SELECT
  salesperson.name,
  max_sale.amount,
  max_sale.customer_name
FROM
  salesperson,
  -- find maximum size and customer at same time
  (SELECT amount, customer_name
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id
    ORDER BY amount DESC LIMIT 1)
  AS max_sale;

然而,那也不起作用。派生表依赖于 salesperson 表,因此在没有 LATERAL 的情况下失败:

ERROR 1054 (42S22): Unknown column 'salesperson.id' in 'where clause'

添加 LATERAL 关键字使查询合法:

SELECT
  salesperson.name,
  max_sale.amount,
  max_sale.customer_name
FROM
  salesperson,
  -- find maximum size and customer at same time
  LATERAL
  (SELECT amount, customer_name
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id
    ORDER BY amount DESC LIMIT 1)
  AS max_sale;

简而言之,LATERAL 是刚刚讨论的两种方法中所有缺点的高效解决方案。

原文:dev.mysql.com/doc/refman/8.0/en/subquery-errors.html

15.2.15.10 子查询错误

有一些错误仅适用于子查询。本节描述了这些错误。

  • 不支持的子查询语法:

    ERROR 1235 (ER_NOT_SUPPORTED_YET)
    SQLSTATE = 42000
    Message = "This version of MySQL doesn't yet support
    'LIMIT & IN/ALL/ANY/SOME subquery'"
    

    这意味着 MySQL 不支持以下类似语句:

    SELECT * FROM t1 WHERE s1 IN (SELECT s2 FROM t2 ORDER BY s1 LIMIT 1)
    
  • 子查询返回的列数不正确:

    ERROR 1241 (ER_OPERAND_COL)
    SQLSTATE = 21000
    Message = "Operand should contain 1 column(s)"
    

    在这种情况下会发生此错误:

    SELECT (SELECT column1, column2 FROM t2) FROM t1;
    

    如果子查询返回多列用于行比较,则可以使用子查询。在其他情境下,子查询必须是标量操作数。参见第 15.2.15.5 节,“行子查询”。

  • 子查询返回的行数不正确:

    ERROR 1242 (ER_SUBSELECT_NO_1_ROW)
    SQLSTATE = 21000
    Message = "Subquery returns more than 1 row"
    

    对于子查询必须返回最多一行但返回多行的语句会发生此错误。考虑以下示例:

    SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2);
    

    如果 SELECT column1 FROM t2 只返回一行,则前面的查询有效。如果子查询返回多于一行,则会出现错误 1242。在这种情况下,查询应重写为:

    SELECT * FROM t1 WHERE column1 = ANY (SELECT column1 FROM t2);
    
  • 子查询中错误使用表:

    Error 1093 (ER_UPDATE_TABLE_USED)
    SQLSTATE = HY000
    Message = "You can't specify target table 'x'
    for update in FROM clause"
    

    在尝试在子查询中修改表并从同一表中进行选择的情况下会发生此错误:

    UPDATE t1 SET column2 = (SELECT MAX(column1) FROM t1);
    

    您可以使用公共表达式或派生表来解决此问题。参见第 15.2.15.12 节,“子查询限制”。

在 MySQL 8.0.19 及更高版本中,当在子查询中使用TABLE时,本节描述的所有错误也适用。

对于事务性存储引擎,子查询失败会导致整个语句失败。对于非事务性存储引擎,在遇到错误之前进行的数据修改会被保留。

原文:dev.mysql.com/doc/refman/8.0/en/optimizing-subqueries.html

15.2.15.11 优化子查询

开发仍在进行中,因此长期来看,没有可靠的优化提示。以下列表提供了一些有趣的技巧,您可能想尝试一下。另请参阅 Section 10.2.2,“优化子查询、派生表、视图引用和公共表达式”。

  • 将子查询外部的子句移到内部。例如,使用这个查询:

    SELECT * FROM t1
      WHERE s1 IN (SELECT s1 FROM t1 UNION ALL SELECT s1 FROM t2);
    

    而不是这个查询:

    SELECT * FROM t1
      WHERE s1 IN (SELECT s1 FROM t1) OR s1 IN (SELECT s1 FROM t2);
    

    举个例子,使用这个查询:

    SELECT (SELECT column1 + 5 FROM t1) FROM t2;
    

    而不是这个查询:

    SELECT (SELECT column1 FROM t1) + 5 FROM t2;
    

译文:dev.mysql.com/doc/refman/8.0/en/subquery-restrictions.html

15.2.15.12 子查询的限制

  • 一般来说,你不能修改一个表并在子查询中从同一个表中选择。例如,这个限制适用于以下形式的语句:

    DELETE FROM t WHERE ... (SELECT ... FROM t ...);
    UPDATE t ... WHERE col = (SELECT ... FROM t ...);
    {INSERT|REPLACE} INTO t (SELECT ... FROM t ...);
    

    例外:前述禁令不适用于如果对修改的表使用了派生表,并且该派生表是实现而不是合并到外部查询中。 (参见 Section 10.2.2.4, “使用合并或实现优化派生表、视图引用和公共表达式”.) 例如:

    UPDATE t ... WHERE col = (SELECT * FROM (SELECT ... FROM t...) AS dt ...);
    

    这里派生表的结果被实现为一个临时表,因此在对t进行更新时,相关行已经被选择。

    一般来说,你可以通过添加NO_MERGE优化器提示来影响优化器实现一个派生表。参见 Section 10.9.3, “优化器提示”。

  • 行比较操作只部分支持:

    • 对于*expr* [NOT] IN *subquery*, expr可以是一个n元组(使用行构造器语法指定),子查询可以返回n元组行。因此,允许的语法更具体地表达为*row_constructor* [NOT] IN *table_subquery*

    • 对于*expr* *op* {ALL|ANY|SOME} *subquery*, expr必须是一个标量值,子查询必须是一个列子查询;它不能返回多列行。

    换句话说,对于返回n元组行的子查询,这是支持的:

    (*expr_1*, ..., *expr_n*) [NOT] IN *table_subquery*
    

    但是这是不支持的:

    (*expr_1*, ..., *expr_n*) *op* {ALL|ANY|SOME} *subquery*
    

    之所以支持IN的行比较而不支持其他操作的原因是,IN是通过将其重写为一系列=比较和AND操作来实现的。这种方法不能用于ALLANYSOME

  • 在 MySQL 8.0.14 之前,FROM子句中的子查询不能是相关子查询。它们在查询执行期间被整体实现(评估以产生结果集),因此不能针对外部查询的每一行进行评估。优化器延迟实现直到结果需要,这可能允许避免实现。参见 Section 10.2.2.4, “使用合并或实现优化派生表、视图引用和公共表达式”。

  • MySQL 不支持在某些子查询操作符中的子查询中使用LIMIT

    mysql> SELECT * FROM t1
           WHERE s1 IN (SELECT s2 FROM t2 ORDER BY s1 LIMIT 1);
    ERROR 1235 (42000): This version of MySQL doesn't yet support
     'LIMIT & IN/ALL/ANY/SOME subquery'
    

    参见第 15.2.15.10 节,“子查询错误”。

  • MySQL 允许子查询引用具有插入行等数据修改副作用的存储函数。例如,如果f()插入行,则以下查询可以修改数据:

    SELECT ... WHERE x IN (SELECT f() ...);
    

    这种行为是对 SQL 标准的扩展。在 MySQL 中,它可能产生不确定的结果,因为f()可能在给定查询的不同执行中由优化器选择处理的方式而执行不同次数。

    对于基于语句或混合格式的复制,这种不确定性的一个影响是这样的查询可能在源数据库和其副本上产生不同的结果。

15.2.16 TABLE 语句

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

TABLE是 MySQL 8.0.19 中引入的 DML 语句,返回命名表的行和列。

TABLE *table_name* [ORDER BY *column_name*] [LIMIT *number* [OFFSET *number*]]

TABLE语句在某些方面类似于SELECT。给定名为t的表存在,以下两个语句产生相同的输出:

TABLE t;

SELECT * FROM t;

您可以使用ORDER BYLIMIT子句对TABLE生成的行数进行排序和限制。这些与在SELECT中使用相同的子句完全相同(包括与LIMIT一起使用的可选OFFSET子句),如下所示:

mysql> TABLE t;
+----+----+
| a  | b  |
+----+----+
|  1 |  2 |
|  6 |  7 |
|  9 |  5 |
| 10 | -4 |
| 11 | -1 |
| 13 |  3 |
| 14 |  6 |
+----+----+
7 rows in set (0.00 sec)

mysql> TABLE t ORDER BY b;
+----+----+
| a  | b  |
+----+----+
| 10 | -4 |
| 11 | -1 |
|  1 |  2 |
| 13 |  3 |
|  9 |  5 |
| 14 |  6 |
|  6 |  7 |
+----+----+
7 rows in set (0.00 sec)

mysql> TABLE t LIMIT 3;
+---+---+
| a | b |
+---+---+
| 1 | 2 |
| 6 | 7 |
| 9 | 5 |
+---+---+
3 rows in set (0.00 sec)

mysql> TABLE t ORDER BY b LIMIT 3;
+----+----+
| a  | b  |
+----+----+
| 10 | -4 |
| 11 | -1 |
|  1 |  2 |
+----+----+
3 rows in set (0.00 sec)

mysql> TABLE t ORDER BY b LIMIT 3 OFFSET 2;
+----+----+
| a  | b  |
+----+----+
|  1 |  2 |
| 13 |  3 |
|  9 |  5 |
+----+----+
3 rows in set (0.00 sec)

TABLE在两个关键方面与SELECT不同:

  • TABLE总是显示表的所有列。

    例外TABLE的输出包括不可见列。查看第 15.1.20.10 节,“不可见列”。

  • TABLE不允许对行进行任意过滤;也就是说,TABLE不支持任何WHERE子句。

为了限制返回的表列,过滤超出ORDER BYLIMIT所能实现的行,或两者都使用,使用SELECT

TABLE可以与临时表一起使用。

TABLE也可以用于取代SELECT在许多其他结构中,包括以下列出的结构:

  • 使用诸如UNION之类的集合运算符,如下所示:

    mysql> TABLE t1;
    +---+----+
    | a | b  |
    +---+----+
    | 2 | 10 |
    | 5 |  3 |
    | 7 |  8 |
    +---+----+
    3 rows in set (0.00 sec)
    
    mysql> TABLE t2;
    +---+---+
    | a | b |
    +---+---+
    | 1 | 2 |
    | 3 | 4 |
    | 6 | 7 |
    +---+---+
    3 rows in set (0.00 sec)
    
    mysql> TABLE t1 UNION TABLE t2;
    +---+----+
    | a | b  |
    +---+----+
    | 2 | 10 |
    | 5 |  3 |
    | 7 |  8 |
    | 1 |  2 |
    | 3 |  4 |
    | 6 |  7 |
    +---+----+
    6 rows in set (0.00 sec)
    

    刚刚显示的UNION等效于以下语句:

    mysql> SELECT * FROM t1 UNION SELECT * FROM t2;
    +---+----+
    | a | b  |
    +---+----+
    | 2 | 10 |
    | 5 |  3 |
    | 7 |  8 |
    | 1 |  2 |
    | 3 |  4 |
    | 6 |  7 |
    +---+----+
    6 rows in set (0.00 sec)
    

    TABLE还可以与SELECT语句、VALUES语句或两者一起在集合操作中使用。查看第 15.2.18 节,“UNION 子句”、第 15.2.4 节,“EXCEPT 子句”和第 15.2.8 节,“INTERSECT 子句”,获取更多信息和示例。另请参阅第 15.2.14 节,“使用 UNION、INTERSECT 和 EXCEPT 的集合操作”。

  • 使用INTO填充用户变量,并使用INTO OUTFILEINTO DUMPFILE将表数据写入文件。查看第 15.2.13.1 节,“SELECT ... INTO 语句”,获取更具体的信息和示例。

  • 在许多情况下,您可以使用子查询。给定具有名为a的列的任何表t1,以及具有单列的第二个表t2,以下语句是可能的:

    SELECT * FROM t1 WHERE a IN (TABLE t2);
    

    假设表t1的单列命名为x,前述与以下各语句等效(在任一情况下产生完全相同的结果):

    SELECT * FROM t1 WHERE a IN (SELECT x FROM t2);
    
    SELECT * FROM t1 WHERE a IN (SELECT * FROM t2);
    

    查看第 15.2.15 节,“子查询”,获取更多信息。

  • INSERTREPLACE语句中,您原本会使用SELECT *。有关更多信息和示例,请参阅第 15.2.7.1 节,“INSERT ... SELECT 语句”。

  • TABLE在许多情况下也可以代替SELECTCREATE TABLE ... SELECTCREATE VIEW ... SELECT中使用。有关这些语句的更多信息和示例,请参阅其描述。

15.2.17 UPDATE 语句

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

UPDATE是修改表中行的 DML 语句。

一个UPDATE语句可以以WITH")子句开头,以定义在UPDATE中可访问的常用表达式。请参阅第 15.2.20 节,“WITH (Common Table Expressions)”")。

单表语法:

UPDATE [LOW_PRIORITY] [IGNORE] *table_reference*
    SET *assignment_list*
    [WHERE *where_condition*]
    [ORDER BY ...]
    [LIMIT *row_count*]

*value*:
    {*expr* | DEFAULT}

*assignment*:
    *col_name* = *value*

*assignment_list*:
    *assignment* [, *assignment*] ...

多表语法:

UPDATE [LOW_PRIORITY] [IGNORE] *table_references*
    SET *assignment_list*
    [WHERE *where_condition*]

对于单表语法,UPDATE语句使用新值更新命名表中现有行的列。SET子句指示要修改的列以及它们应该被赋予的值。每个值可以作为表达式给出,或者使用关键字DEFAULT将列明确设置为其默认值。如果给出WHERE子句,则指定标识要更新的行的条件。如果没有WHERE子句,则更新所有行。如果指定了ORDER BY子句,则按指定的顺序更新行。LIMIT子句限制可以更新的行数。

对于多表语法,UPDATE更新满足条件的每个表中的行。每个匹配的行只更新一次,即使它多次匹配条件。对于多表语法,不能使用ORDER BYLIMIT

对于分区表,此语句的单表和多表形式都支持PARTITION子句作为表引用的一部分。此选项接受一个或多个分区或子分区(或两者)的列表。仅检查列出的分区(或子分区)是否匹配,并且不在任何这些分区或子分区中的行不会被更新,无论它是否满足where_condition

注意

与在INSERTREPLACE语句中使用PARTITION时不同,即使列出的分区(或子分区)中没有行与where_condition匹配,否则有效的UPDATE ... PARTITION语句也被认为是成功的。

有关更多信息和示例,请参见第 26.5 节,“分区选择”。

where_condition是一个对每个要更新的行求值为 true 的表达式。有关表达式语法,请参见第 11.5 节,“表达式”。

table_referenceswhere_condition的指定如第 15.2.13 节,“SELECT 语句”所述。

只有在实际上被更新的UPDATE中引用的列才需要UPDATE权限。对于只读取但不修改的任何列,只需要SELECT权限。

UPDATE 语句支持以下修饰符:

  • 使用LOW_PRIORITY修饰符,UPDATE 的执行会延迟,直到没有其他客户端从表中读取数据。这仅影响仅使用表级锁定的存储引擎(如MyISAMMEMORYMERGE)。

  • 使用IGNORE修饰符,即使在更新过程中发生错误,更新语句也不会中止。对于唯一键值上发生重复键冲突的行不会被更新。将更新为会导致数据转换错误的值的行将被更新为最接近的有效值。更多信息,请参见 The Effect of IGNORE on Statement Execution。

UPDATE IGNORE 语句,包括具有ORDER BY子句的语句,被标记为不安全的用于基于语句的复制。(这是因为更新行的顺序决定了哪些行被忽略。)这样的语句在使用基于语句模式时会在错误日志中产生警告,并在使用MIXED模式时以基于行的格式写入二进制日志。(Bug #11758262, Bug #50439)更多信息请参见 Section 19.2.1.3, “Determination of Safe and Unsafe Statements in Binary Logging”。

如果在表达式中访问要更新的表中的列,UPDATE 会使用列的当前值。例如,以下语句将col1设置为比其当前值多一的值:

UPDATE t1 SET col1 = col1 + 1;

以下语句中的第二个赋值将col2设置为当前(更新后)的col1值,而不是原始的col1值。结果是col1col2具有相同的值。这种行为与标准 SQL 不同。

UPDATE t1 SET col1 = col1 + 1, col2 = col1;

单表UPDATE赋值通常从左到右进行评估。对于多表更新,不能保证赋值按任何特定顺序执行。

如果将列设置为其当前值,MySQL 会注意到这一点并且不会对其进行更新。

如果您通过将已声明为NOT NULL的列设置为NULL来更新列,并且启用了严格的 SQL 模式,则会发生错误;否则,该列将设置为列数据类型的隐式默认值,并且警告计数会增加。对于数值类型,隐式默认值为0,对于字符串类型,为空字符串(''),对于日期和时间类型,为“零”值。请参见第 13.6 节,“数据类型默认值”。

如果显式更新生成列,则唯一允许的值是DEFAULT。有关生成列的信息,请参见第 15.1.20.8 节,“CREATE TABLE and Generated Columns”。

UPDATE返回实际更改的行数。mysql_info() C API 函数返回匹配并更新的行数以及在UPDATE过程中发生的警告数。

您可以使用LIMIT *row_count*来限制UPDATE的范围。LIMIT子句是一个匹配行数的限制。一旦找到满足WHERE子句的row_count行,无论它们是否实际更改,语句都会停止。

如果UPDATE语句包含ORDER BY子句,则按照子句指定的顺序更新行。在某些情况下,这可能会避免错误。假设表t包含具有唯一索引的列id。以下语句可能会因为更新行的顺序不同而导致重复键错误:

UPDATE t SET id = id + 1;

例如,如果表中id列包含 1 和 2,并且在将 1 更新为 2 之前将 2 更新为 3,则会发生错误。为避免此问题,请添加ORDER BY子句,以使具有较大id值的行在具有较小值的行之前更新:

UPDATE t SET id = id + 1 ORDER BY id DESC;

您还可以执行涵盖多个表的UPDATE操作。但是,不能在多表UPDATE中使用ORDER BYLIMITtable_references子句列出了参与连接的表。其语法在第 15.2.13.2 节,“JOIN Clause”中描述。以下是一个示例:

UPDATE items,month SET items.price=month.price
WHERE items.id=month.id;

前面的示例显示了使用逗号运算符的内连接,但是多表UPDATE语句可以使用在SELECT语句中允许的任何类型的连接,例如LEFT JOIN

如果您使用涉及InnoDB表的多表UPDATE语句,并且这些表存在外键约束,MySQL 优化器可能会以与它们的父/子关系不同的顺序处理表。在这种情况下,该语句将失败并回滚。相反,更新单个表,并依赖InnoDB提供的ON UPDATE功能,以使其他表相应地进行修改。请参阅第 15.1.20.5 节,“外键约束”。

您不能在子查询中直接更新表并从同一表中进行选择。您可以通过使用多表更新来解决此问题,其中一个表是从您实际希望更新的表派生的,并使用别名引用派生表。假设您希望更新一个名为items的表,该表是使用以下语句定义的:

CREATE TABLE items (
    id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    wholesale DECIMAL(6,2) NOT NULL DEFAULT 0.00,
    retail DECIMAL(6,2) NOT NULL DEFAULT 0.00,
    quantity BIGINT NOT NULL DEFAULT 0
);

要减少任何标记幅度为 30%或更高且库存少于一百的任何商品的零售价格,您可以尝试使用类似以下的UPDATE语句,其中在WHERE子句中使用了子查询。如下所示,此语句不起作用:

mysql> UPDATE items
     > SET retail = retail * 0.9
     > WHERE id IN
     >     (SELECT id FROM items
     >         WHERE retail / wholesale >= 1.3 AND quantity > 100);
ERROR 1093 (HY000): You can't specify target table 'items' for update in FROM clause

相反,您可以使用多表更新,其中子查询移动到要更新的表列表中,并使用别名在最外层WHERE子句中引用它,就像这样:

UPDATE items,
       (SELECT id FROM items
        WHERE id IN
            (SELECT id FROM items
             WHERE retail / wholesale >= 1.3 AND quantity < 100))
        AS discounted
SET items.retail = items.retail * 0.9
WHERE items.id = discounted.id;

因为优化器默认尝试将派生表discounted合并到最外层查询块中,所以只有在强制实体化派生表时才起作用。您可以通过在运行更新之前将optimizer_switch系统变量的derived_merge标志设置为off,或者使用NO_MERGE优化提示来实现这一点,如下所示:

UPDATE /*+ NO_MERGE(discounted) */ items,
       (SELECT id FROM items
        WHERE retail / wholesale >= 1.3 AND quantity < 100)
        AS discounted
    SET items.retail = items.retail * 0.9
    WHERE items.id = discounted.id;

在这种情况下使用优化提示的优势在于它仅在使用它的查询块内部应用,因此在执行UPDATE后不需要再次更改optimizer_switch的值。

另一种可能性是重写子查询,使其不使用INEXISTS,就像这样:

UPDATE items,
       (SELECT id, retail / wholesale AS markup, quantity FROM items)
       AS discounted
    SET items.retail = items.retail * 0.9
    WHERE discounted.markup >= 1.3
    AND discounted.quantity < 100
    AND items.id = discounted.id;

在这种情况下,默认情况下子查询是实体化的,而不是合并的,因此不需要禁用派生表的合并。

15.2.18 UNION 子句

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

*query_expression_body* UNION [ALL | DISTINCT] *query_block*
    [UNION [ALL | DISTINCT] *query_expression_body*]
    [...]

*query_expression_body*:
    *See Section 15.2.14, “Set Operations with UNION, INTERSECT, and EXCEPT”*

UNION 将多个查询块的结果合并为单个结果集。此示例使用 SELECT 语句:

mysql> SELECT 1, 2;
+---+---+
| 1 | 2 |
+---+---+
| 1 | 2 |
+---+---+
mysql> SELECT 'a', 'b';
+---+---+
| a | b |
+---+---+
| a | b |
+---+---+
mysql> SELECT 1, 2 UNION SELECT 'a', 'b';
+---+---+
| 1 | 2 |
+---+---+
| 1 | 2 |
| a | b |
+---+---+

MySQL 8.0 中的 UNION 处理与 MySQL 5.7 相比

在 MySQL 8.0 中,SELECTUNION 的解析器规则进行了重构,以使其更一致(在每个上下文中统一应用相同的 SELECT 语法)并减少重复。与 MySQL 5.7 相比,这项工作产生了几个用户可见的效果,可能需要重写某些语句:

  • NATURAL JOIN 允许可选的 INNER 关键字(NATURAL INNER JOIN),符合标准 SQL。

  • 允许不带括号的右深度连接(例如,... JOIN ... JOIN ... ON ... ON),符合标准 SQL。

  • STRAIGHT_JOIN 现在允许 USING 子句,类似于其他内连接。

  • 解析器接受围绕查询表达式的括号。例如,(SELECT ... UNION SELECT ...) 是允许的。另请参阅 第 15.2.11 节,“带括号的查询表达式”。

  • 解析器更好地符合文档中允许放置 SQL_CACHESQL_NO_CACHE 查询修饰符的规定。

  • 左侧嵌套联合,以前仅在子查询中允许,现在在顶层语句中也允许。例如,此语句现在被接受为有效:

    (SELECT 1 UNION SELECT 1) UNION SELECT 1;
    
  • 锁定子句(FOR UPDATELOCK IN SHARE MODE)仅允许在非 UNION 查询中使用。这意味着必须对包含锁定子句的 SELECT 语句使用括号。此语句不再被接受为有效:

    SELECT 1 FOR UPDATE UNION SELECT 1 FOR UPDATE;
    

    相反,应该这样写陈述:

    (SELECT 1 FOR UPDATE) UNION (SELECT 1 FOR UPDATE);
    

15.2.19 VALUES Statement

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

VALUES是 MySQL 8.0.19 中引入的 DML 语句,它将一组一个或多个行作为表返回。换句话说,它是一个表值构造函数,也可以作为独立的 SQL 语句。

VALUES *row_constructor_list* [ORDER BY *column_designator*] [LIMIT *number*]

*row_constructor_list*:
    ROW(*value_list*)[, ROW(*value_list*)][, ...]

*value_list*:
    *value*[, *value*][, ...]

*column_designator*:
    column_*index*

VALUES语句由VALUES关键字后跟一个或多个行构造函数列表组成,用逗号分隔。行构造函数由ROW()行构造函数子句组成,其值列表由一个或多个标量值包含在括号中。一个值可以是任何 MySQL 数据类型的文字值或解析为标量值的表达式。

ROW()不能是空的(但提供的每个标量值可以是NULL)。在同一个VALUES语句中,每个ROW()在其值列表中必须具有相同数量的值。

DEFAULT关键字不受VALUES支持,并导致语法错误,除非它用于在INSERT语句中提供值。

VALUES的输出是一个表:

mysql> VALUES ROW(1,-2,3), ROW(5,7,9), ROW(4,6,8);
+----------+----------+----------+
| column_0 | column_1 | column_2 |
+----------+----------+----------+
|        1 |       -2 |        3 |
|        5 |        7 |        9 |
|        4 |        6 |        8 |
+----------+----------+----------+
3 rows in set (0.00 sec)

VALUES输出的表的列具有隐式命名的列column_0column_1column_2等,始终从0开始。这个事实可以用来使用可选的ORDER BY子句按列对行进行排序,就像这个子句在SELECT语句中的工作方式一样,如下所示:

mysql> VALUES ROW(1,-2,3), ROW(5,7,9), ROW(4,6,8) ORDER BY column_1;
+----------+----------+----------+
| column_0 | column_1 | column_2 |
+----------+----------+----------+
|        1 |       -2 |        3 |
|        4 |        6 |        8 |
|        5 |        7 |        9 |
+----------+----------+----------+
3 rows in set (0.00 sec)

在 MySQL 8.0.21 及更高版本中,VALUES语句还支持LIMIT子句以限制输出中的行数。(以前,LIMIT是允许的,但不起作用。)

VALUES语句在列值的数据类型方面是宽松的;您可以在同一列中混合类型,如下所示:

mysql> VALUES ROW("q", 42, '2019-12-18'),
 ->     ROW(23, "abc", 98.6),
 ->     ROW(27.0002, "Mary Smith", '{"a": 10, "b": 25}');
+----------+------------+--------------------+
| column_0 | column_1   | column_2           |
+----------+------------+--------------------+
| q        | 42         | 2019-12-18         |
| 23       | abc        | 98.6               |
| 27.0002  | Mary Smith | {"a": 10, "b": 25} |
+----------+------------+--------------------+
3 rows in set (0.00 sec)

重要提示

具有一个或多个ROW()实例的VALUES充当表值构造函数;尽管它可以用于在INSERTREPLACE语句中提供值,但不要将其与也用于此目的的VALUES关键字混淆。您也不要将其与指代INSERT ... ON DUPLICATE KEY UPDATE中列值的VALUES()函数混淆。

您还应该记住ROW()是一个行值构造函数(参见 Section 15.2.15.5, “Row Subqueries”),而VALUES ROW()是一个表值构造函数;这两者不能互换使用。

VALUES 可以在许多情况下使用 SELECT,包括以下情况:

  • 使用 UNION,如下所示:

    mysql> SELECT 1,2 UNION SELECT 10,15;
    +----+----+
    | 1  | 2  |
    +----+----+
    |  1 |  2 |
    | 10 | 15 |
    +----+----+
    2 rows in set (0.00 sec)
    
    mysql> VALUES ROW(1,2) UNION VALUES ROW(10,15);
    +----------+----------+
    | column_0 | column_1 |
    +----------+----------+
    |        1 |        2 |
    |       10 |       15 |
    +----------+----------+
    2 rows in set (0.00 sec)
    

    您可以将多行构建的表联合在一起,就像这样:

    mysql> VALUES ROW(1,2), ROW(3,4), ROW(5,6)
         >     UNION VALUES ROW(10,15),ROW(20,25);
    +----------+----------+
    | column_0 | column_1 |
    +----------+----------+
    |        1 |        2 |
    |        3 |        4 |
    |        5 |        6 |
    |       10 |       15 |
    |       20 |       25 |
    +----------+----------+
    5 rows in set (0.00 sec)
    

    在这种情况下,您也可以(通常更可取地)完全省略 UNION 并使用单个 VALUES 语句,就像这样:

    mysql> VALUES ROW(1,2), ROW(3,4), ROW(5,6), ROW(10,15), ROW(20,25);
    +----------+----------+
    | column_0 | column_1 |
    +----------+----------+
    |        1 |        2 |
    |        3 |        4 |
    |        5 |        6 |
    |       10 |       15 |
    |       20 |       25 |
    +----------+----------+
    

    VALUES 也可以与 SELECT 语句、TABLE 语句或两者一起在联合中使用。

    UNION 中的构建表必须包含相同数量的列,就像您使用 SELECT 一样。有关更多示例,请参见 Section 15.2.18, “UNION Clause”。

    在 MySQL 8.0.31 及更高版本中,您可以像使用 UNION 一样使用 EXCEPTINTERSECTVALUES,如下所示:

    mysql> VALUES ROW(1,2), ROW(3,4), ROW(5,6)
     ->   INTERSECT 
     -> VALUES ROW(10,15), ROW(20,25), ROW(3,4);
    +----------+----------+
    | column_0 | column_1 |
    +----------+----------+
    |        3 |        4 |
    +----------+----------+
    1 row in set (0.00 sec)
    
    mysql> VALUES ROW(1,2), ROW(3,4), ROW(5,6)
     ->   EXCEPT 
     -> VALUES ROW(10,15), ROW(20,25), ROW(3,4);
    +----------+----------+
    | column_0 | column_1 |
    +----------+----------+
    |        1 |        2 |
    |        5 |        6 |
    +----------+----------+
    2 rows in set (0.00 sec)
    

    有关更多信息,请参见 Section 15.2.4, “EXCEPT Clause” 和 Section 15.2.8, “INTERSECT Clause”。

  • 在连接中。有关更多信息和示例,请参见 Section 15.2.13.2, “JOIN Clause”。

  • INSERTREPLACE 语句中代替 VALUES(),在这种情况下,其语义与此处描述的略有不同。有关详细信息,请参见 Section 15.2.7, “INSERT Statement”。

  • CREATE TABLE ... SELECTCREATE VIEW ... SELECT 中代替源表。有关更多信息和示例,请参见这些语句的描述。

15.2.20 WITH(公共表达式)

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

公共表达式(CTE)是存在于单个语句范围内的命名临时结果集,可以在该语句中稍后引用,可能多次。以下讨论描述了如何编写使用 CTE 的语句。

  • 公共表达式

  • 递归公共表达式

  • 限制公共表达式递归

  • 递归公共表达式示例

  • 与类似结构比较的公共表达式

有关 CTE 优化的信息,请参见第 10.2.2.4 节,“使用合并或材料化优化派生表、视图引用和公共表达式”。

其他资源

这些文章包含有关在 MySQL 中使用 CTEs 的其他信息,包括许多示例:

公共表达式

要指定公共表达式,请使用具有一个或多个逗号分隔子句的WITH")子句。每个子句提供一个生成结果集的子查询,并将一个名称与子查询关联。以下示例在WITH")子句中定义了名为cte1cte2的 CTE,并在跟随WITH")子句的顶层SELECT中引用它们:

WITH
  cte1 AS (SELECT a, b FROM table1),
  cte2 AS (SELECT c, d FROM table2)
SELECT b, d FROM cte1 JOIN cte2
WHERE cte1.a = cte2.c;

在包含WITH")子句的语句中,每个 CTE 名称都可以被引用以访问相应的 CTE 结果集。

CTE 名称可以在其他 CTE 中引用,使得 CTE 可以基于其他 CTE 定义。

CTE 可以引用自身来定义递归 CTE。递归 CTE 的常见应用包括序列生成和遍历分层或树形数据。

公共表达式是 DML 语句语法的可选部分。它们使用WITH")子句定义:

*with_clause*:
    WITH [RECURSIVE]
        *cte_name* [(*col_name* [, *col_name*] ...)] AS (*subquery*)
        [, *cte_name* [(*col_name* [, *col_name*] ...)] AS (*subquery*)] ...

cte_name指定一个单个公共表达式,并且可以在包含WITH")子句的语句中作为表引用。

AS (*子查询*)部分中的子查询称为“CTE 的子查询”,并生成 CTE 结果集。AS后面的括号是必需的。

如果 CTE 的子查询引用其自身的名称,则该公共表达式是递归的。如果WITH")子句中的任何 CTE 是递归的,则必须包含RECURSIVE关键字。有关更多信息,请参见递归公共表达式。

给定 CTE 的列名确定如下:

  • 如果 CTE 名称后跟着一个括号括起的名称列表,则这些名称是列名:

    WITH cte (col1, col2) AS
    (
      SELECT 1, 2
      UNION ALL
      SELECT 3, 4
    )
    SELECT col1, col2 FROM cte;
    

    名称列表中的名称数量必须与结果集中的列数相同。

  • 否则,列名来自AS (*子查询*)部分中第一个SELECT的选择列表:

    WITH cte AS
    (
      SELECT 1 AS col1, 2 AS col2
      UNION ALL
      SELECT 3, 4
    )
    SELECT col1, col2 FROM cte;
    

在以下情况下允许使用WITH")子句:

  • SELECTUPDATEDELETE语句的开头。

    WITH ... SELECT ...
    WITH ... UPDATE ...
    WITH ... DELETE ...
    
  • 在子查询(包括派生表子查询)的开头:

    SELECT ... WHERE id IN (WITH ... SELECT ...) ...
    SELECT * FROM (WITH ... SELECT ...) AS dt ...
    
  • 在包含SELECT语句的语句之前:

    INSERT ... WITH ... SELECT ...
    REPLACE ... WITH ... SELECT ...
    CREATE TABLE ... WITH ... SELECT ...
    CREATE VIEW ... WITH ... SELECT ...
    DECLARE CURSOR ... WITH ... SELECT ...
    EXPLAIN ... WITH ... SELECT ...
    

同一级别只允许一个WITH")子句。不允许在同一级别后跟WITH")再跟WITH"),因此这是不合法的:

WITH cte1 AS (...) WITH cte2 AS (...) SELECT ...

要使语句合法,使用一个单独的WITH")子句,通过逗号分隔子句:

WITH cte1 AS (...), cte2 AS (...) SELECT ...

但是,如果它们出现在不同级别,则语句可以包含多个WITH")子句:

WITH cte1 AS (SELECT 1)
SELECT * FROM (WITH cte2 AS (SELECT 2) SELECT * FROM cte2 JOIN cte1) AS dt;

WITH")子句可以定义一个或多个公共表达式,但每个 CTE 名称必须对该子句唯一。这是不合法的:

WITH cte1 AS (...), cte1 AS (...) SELECT ...

要使语句合法,定义具有唯一名称的 CTE:

WITH cte1 AS (...), cte2 AS (...) SELECT ...

CTE 可以引用自身或其他 CTE:

  • 自引用 CTE 是递归的。

  • CTE 可以引用在同一WITH")子句中较早定义的 CTE,但不能引用稍后定义的 CTE。

    这个约束排除了相互递归的 CTE,其中cte1引用cte2,而cte2引用cte1。这些引用中的一个必须是对稍后定义的 CTE 的引用,这是不允许的。

  • 在给定查询块中,CTE 可以引用更外层级别的查询块中定义的 CTE,但不能引用更内层级别的查询块中定义的 CTE。

对于解析具有相同名称的对象引用,派生表隐藏 CTE;而 CTE 隐藏基本表、TEMPORARY表和视图。名称解析通过在同一查询块中搜索对象,然后依次在外部块中查找,直到找到具有该名称的对象为止。

与派生表类似,在 MySQL 8.0.14 之前,CTE 不能包含外部引用。这是 MySQL 在 MySQL 8.0.14 中解除的限制,而不是 SQL 标准的限制。有关递归 CTE 的特定语法考虑事项,请参见递归公共表达式。

递归公共表达式

递归公共表达式是具有引用其自身名称的子查询的表达式。例如:

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte WHERE n < 5
)
SELECT * FROM cte;

当执行时,该语句产生这样的结果,一个包含简单线性序列的单列:

+------+
| n    |
+------+
|    1 |
|    2 |
|    3 |
|    4 |
|    5 |
+------+

递归 CTE 具有以下结构:

  • 如果WITH子句中的任何 CTE 引用自身,则WITH子句必须以WITH RECURSIVE开头。(如果没有 CTE 引用自身,则允许使用RECURSIVE但不是必需的。)

    如果忘记为递归 CTE 添加RECURSIVE,则可能会出现以下错误:

    ERROR 1146 (42S02): Table '*cte_name*' doesn't exist
    
  • 递归 CTE 子查询有两部分,由UNION ALLUNION [DISTINCT]分隔:

    SELECT ...      -- return initial row set
    UNION ALL
    SELECT ...      -- return additional row sets
    

    第一个 SELECT 生成 CTE 的初始行或行,并不引用 CTE 名称。第二个 SELECT 生成额外的行并通过在其 FROM 子句中引用 CTE 名称进行递归。当这部分不再生成新行时,递归结束。因此,递归 CTE 由一个非递归的 SELECT 部分后跟一个递归的 SELECT 部分组成。

    每个 SELECT 部分本身可以是多个 SELECT 语句的联合。

  • CTE 结果列的类型是从非递归 SELECT 部分的列类型推断出来的,所有列都是可空的。对于类型确定,递归 SELECT 部分将被忽略。

  • 如果非递归部分和递归部分由 UNION DISTINCT 分隔,重复行将被消除。这对执行传递闭包的查询很有用,以避免无限循环。

  • 递归部分的每次迭代仅在前一次迭代生成的行上操作。如果递归部分有多个查询块,每个查询块的迭代按照未指定的顺序进行安排,并且每个查询块操作的行是由其前一次迭代或自其前一次迭代结束以来其他查询块生成的行。

之前显示的递归 CTE 子查询有这个非递归部分,用于检索单行以生成初始行集合:

SELECT 1

CTE 子查询也有这个递归部分:

SELECT n + 1 FROM cte WHERE n < 5

在每次迭代中,SELECT 会生成一个新值,比上一行集合中的 n 的值大 1。第一次迭代操作初始行集合(1)并生成 1+1=2;第二次迭代操作第一次迭代的行集合(2)并生成 2+1=3;依此类推。直到递归结束,即当 n 不再小于 5 时。

如果 CTE 的递归部分为某列生成了比非递归部分更宽的值,可能需要扩展非递归部分中的列以避免数据截断。考虑以下语句:

WITH RECURSIVE cte AS
(
  SELECT 1 AS n, 'abc' AS str
  UNION ALL
  SELECT n + 1, CONCAT(str, str) FROM cte WHERE n < 3
)
SELECT * FROM cte;

在非严格 SQL 模式下,该语句会产生如下输出:

+------+------+
| n    | str  |
+------+------+
|    1 | abc  |
|    2 | abc  |
|    3 | abc  |
+------+------+

str 列的值都是 'abc',因为非递归 SELECT 确定了列宽度。因此,递归 SELECT 生成的更宽的 str 值会被截断。

在严格 SQL 模式下,该语句会产生错误:

ERROR 1406 (22001): Data too long for column 'str' at row 1

为了解决这个问题,使语句不产生截断或错误,可以在非递归的SELECT中使用CAST()来使str列变宽:

WITH RECURSIVE cte AS
(
  SELECT 1 AS n, CAST('abc' AS CHAR(20)) AS str
  UNION ALL
  SELECT n + 1, CONCAT(str, str) FROM cte WHERE n < 3
)
SELECT * FROM cte;

现在该语句产生了这个结果,没有截断:

+------+--------------+
| n    | str          |
+------+--------------+
|    1 | abc          |
|    2 | abcabc       |
|    3 | abcabcabcabc |
+------+--------------+

列通过名称访问,而不是位置,这意味着递归部分中的列可以访问非递归部分中位置不同的列,正如这个 CTE 所示:

WITH RECURSIVE cte AS
(
  SELECT 1 AS n, 1 AS p, -1 AS q
  UNION ALL
  SELECT n + 1, q * 2, p * 2 FROM cte WHERE n < 5
)
SELECT * FROM cte;

因为一行中的p是从前一行中的q派生的,反之亦然,所以输出的每一行中正负值交换位置:

+------+------+------+
| n    | p    | q    |
+------+------+------+
|    1 |    1 |   -1 |
|    2 |   -2 |    2 |
|    3 |    4 |   -4 |
|    4 |   -8 |    8 |
|    5 |   16 |  -16 |
+------+------+------+

一些语法约束适用于递归 CTE 子查询:

  • 递归SELECT部分不能包含以下结构:

    • 聚合函数如SUM()

    • 窗口函数

    • GROUP BY

    • ORDER BY

    • DISTINCT

    在 MySQL 8.0.19 之前,递归 CTE 的递归SELECT部分也不能使用LIMIT子句。这个限制在 MySQL 8.0.19 中被解除,现在在这种情况下支持LIMIT,还有一个可选的OFFSET子句。结果集的效果与在最外层SELECT中使用LIMIT时相同,但更有效,因为在递归SELECT中使用它会在生成请求的行数后立即停止生成行。

    这些约束不适用于递归 CTE 的非递归SELECT部分。对DISTINCT的禁止仅适用于UNION成员;允许UNION DISTINCT

  • 递归的SELECT部分必须仅在其FROM子句中引用 CTE 一次,而且不能在任何子查询中引用。它可以引用除 CTE 之外的其他表,并将它们与 CTE 连接起来。如果在这样的连接中使用,CTE 不能位于LEFT JOIN的右侧。

这些约束来自 SQL 标准,除了 MySQL 特定的排除ORDER BYLIMIT(MySQL 8.0.18 及更早版本)和DISTINCT之外。

对于递归 CTE,EXPLAIN输出的递归SELECT部分在Extra列中显示Recursive

EXPLAIN显示的成本估计表示每次迭代的成本,这可能与总成本有很大差异。优化器无法预测迭代次数,因为它无法预测WHERE子句何时变为 false。

CTE 的实际成本也可能受到结果集大小的影响。产生许多行的 CTE 可能需要一个足够大的内部临时表,以便从内存转换为磁盘格式,并可能遭受性能损失。如果是这样,增加允许的内存临时表大小可能会提高性能;参见第 10.4.4 节,“MySQL 中的内部临时表使用”。

限制公共表达式递归

对于递归 CTE 非常重要的是,递归的SELECT部分包含一个终止递归的条件。作为一种开发技术,防止递归 CTE 失控的方法是通过设置执行时间限制来强制终止:

  • cte_max_recursion_depth系统变量为 CTE 的递归级别数量设置了限制。服务器会终止任何递归超过此变量值的 CTE 的执行。

  • max_execution_time系统变量强制执行当前会话中执行的SELECT语句的执行超时。

  • MAX_EXECUTION_TIME优化提示为其中出现的SELECT语句强制执行每个查询的执行超时。

假设递归 CTE 被错误地编写为没有递归执行终止条件:

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte
)
SELECT * FROM cte;

默认情况下,cte_max_recursion_depth的值为 1000,当递归超过 1000 级时,CTE 会终止。应用程序可以更改会话值以满足其需求:

SET SESSION cte_max_recursion_depth = 10;      -- permit only shallow recursion
SET SESSION cte_max_recursion_depth = 1000000; -- permit deeper recursion

你也可以设置全局cte_max_recursion_depth值,以影响随后开始的所有会话。

对于执行缓慢且因此递归的查询,或者有理由将cte_max_recursion_depth值设置得非常高的情况,另一种防止深度递归的方法是设置每个会话的超时时间。为此,在执行 CTE 语句之前执行类似以下语句:

SET max_execution_time = 1000; -- impose one second timeout

或者,在 CTE 语句本身中包含一个优化提示:

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte
)
SELECT /*+ SET_VAR(cte_max_recursion_depth = 1M) */ * FROM cte;

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte
)
SELECT /*+ MAX_EXECUTION_TIME(1000) */ * FROM cte;

从 MySQL 8.0.19 开始,你也可以在递归查询中使用LIMIT来对最外层的SELECT返回的最大行数进行限制,例如:

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte LIMIT 10000
)
SELECT * FROM cte;

你可以在设置时间限制之外或代替设置时间限制。因此,以下 CTE 在返回一万行或运行一秒钟(1000 毫秒)后终止:

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte LIMIT 10000
)
SELECT /*+ MAX_EXECUTION_TIME(1000) */ * FROM cte;

如果没有执行时间限制的递归查询进入无限循环,您可以从另一个会话中使用KILL QUERY终止它。在会话本身中,用于运行查询的客户端程序可能提供了终止查询的方法。例如,在mysql中,键入Control+C会中断当前语句。

递归公共表达式示例

如前所述,递归公共表达式(CTEs)经常用于生成系列和遍历分层或树形结构数据。本节展示了这些技术的一些简单示例。

  • 斐波那契数列生成

  • 日期系列生成

  • 分层数据遍历

斐波那契数列生成

斐波那契数列以数字 0 和 1(或 1 和 1)开始,之后每个数字都是前两个数字的和。如果递归SELECT生成的每行都可以访问系列中前两个数字,则递归公共表达式可以生成斐波那契数列。以下 CTE 使用 0 和 1 作为前两个数字生成了一个包含 10 个数字的系列:

WITH RECURSIVE fibonacci (n, fib_n, next_fib_n) AS
(
  SELECT 1, 0, 1
  UNION ALL
  SELECT n + 1, next_fib_n, fib_n + next_fib_n
    FROM fibonacci WHERE n < 10
)
SELECT * FROM fibonacci;

CTE 生成了这个结果:

+------+-------+------------+
| n    | fib_n | next_fib_n |
+------+-------+------------+
|    1 |     0 |          1 |
|    2 |     1 |          1 |
|    3 |     1 |          2 |
|    4 |     2 |          3 |
|    5 |     3 |          5 |
|    6 |     5 |          8 |
|    7 |     8 |         13 |
|    8 |    13 |         21 |
|    9 |    21 |         34 |
|   10 |    34 |         55 |
+------+-------+------------+

CTE 的工作原理如下:

  • n是一个显示列,表示该行包含第n个斐波那契数。例如,第 8 个斐波那契数是 13。

  • fib_n列显示斐波那契数n

  • next_fib_n列显示数字n后面的下一个斐波那契数。该列为下一行提供了下一个系列值,以便该行可以在其fib_n列中生成前两个系列值的和。

  • n达到 10 时,递归结束。这是一个任意选择,以限制输出为一小组行。

前面的输出显示了整个 CTE 结果。要仅选择其中的一部分,请在顶层SELECT中添加适当的WHERE子句。例如,要选择第 8 个斐波那契数,执行以下操作:

mysql> WITH RECURSIVE fibonacci ...
       ...
       SELECT fib_n FROM fibonacci WHERE n = 8;
+-------+
| fib_n |
+-------+
|    13 |
+-------+
日期系列生成

公共表达式可以生成一系列连续的日期,这对于生成包含系列中所有日期的行的摘要非常有用,包括未在摘要数据中表示的日期。

假设销售数字表包含以下行:

mysql> SELECT * FROM sales ORDER BY date, price;
+------------+--------+
| date       | price  |
+------------+--------+
| 2017-01-03 | 100.00 |
| 2017-01-03 | 200.00 |
| 2017-01-06 |  50.00 |
| 2017-01-08 |  10.00 |
| 2017-01-08 |  20.00 |
| 2017-01-08 | 150.00 |
| 2017-01-10 |   5.00 |
+------------+--------+

此查询总结了每天的销售情况:

mysql> SELECT date, SUM(price) AS sum_price
       FROM sales
       GROUP BY date
       ORDER BY date;
+------------+-----------+
| date       | sum_price |
+------------+-----------+
| 2017-01-03 |    300.00 |
| 2017-01-06 |     50.00 |
| 2017-01-08 |    180.00 |
| 2017-01-10 |      5.00 |
+------------+-----------+

然而,该结果对于表跨越的日期范围中未表示的日期存在“空洞”。可以使用递归 CTE 生成该日期集合,然后与销售数据进行LEFT JOIN连接以生成表示范围内所有日期的结果。

这是生成日期范围系列的 CTE:

WITH RECURSIVE dates (date) AS
(
  SELECT MIN(date) FROM sales
  UNION ALL
  SELECT date + INTERVAL 1 DAY FROM dates
  WHERE date + INTERVAL 1 DAY <= (SELECT MAX(date) FROM sales)
)
SELECT * FROM dates;

CTE 生成此结果:

+------------+
| date       |
+------------+
| 2017-01-03 |
| 2017-01-04 |
| 2017-01-05 |
| 2017-01-06 |
| 2017-01-07 |
| 2017-01-08 |
| 2017-01-09 |
| 2017-01-10 |
+------------+

CTE 的工作原理:

  • 非递归SELECT生成sales表跨度日期范围内最早的日期。

  • 递归SELECT生成的每行将日期增加一天到前一行生成的日期。

  • 当日期达到sales表跨度的日期范围内的最高日期时,递归结束。

将 CTE 与sales表进行LEFT JOIN连接,生成每个日期范围内的销售摘要:

WITH RECURSIVE dates (date) AS
(
  SELECT MIN(date) FROM sales
  UNION ALL
  SELECT date + INTERVAL 1 DAY FROM dates
  WHERE date + INTERVAL 1 DAY <= (SELECT MAX(date) FROM sales)
)
SELECT dates.date, COALESCE(SUM(price), 0) AS sum_price
FROM dates LEFT JOIN sales ON dates.date = sales.date
GROUP BY dates.date
ORDER BY dates.date;

输出如下所示:

+------------+-----------+
| date       | sum_price |
+------------+-----------+
| 2017-01-03 |    300.00 |
| 2017-01-04 |      0.00 |
| 2017-01-05 |      0.00 |
| 2017-01-06 |     50.00 |
| 2017-01-07 |      0.00 |
| 2017-01-08 |    180.00 |
| 2017-01-09 |      0.00 |
| 2017-01-10 |      5.00 |
+------------+-----------+

一些需要注意的要点:

  • 查询是否低效,特别是对于递归SELECT中每行执行的包含MAX()子查询?EXPLAIN显示包含MAX()的子查询仅评估一次,并且结果被缓存。

  • 使用COALESCE()避免在sales表中没有销售数据的日期中在sum_price列中显示NULL

分层数据遍历

递归通用表达式对于遍历形成层次结构的数据非常有用。考虑以下创建一个小数据集的语句,该数据集显示公司中每个员工的员工姓名和 ID 号,以及员工的经理的 ID。顶层员工(CEO)的经理 ID 为NULL(没有经理)。

CREATE TABLE employees (
  id         INT PRIMARY KEY NOT NULL,
  name       VARCHAR(100) NOT NULL,
  manager_id INT NULL,
  INDEX (manager_id),
FOREIGN KEY (manager_id) REFERENCES employees (id)
);
INSERT INTO employees VALUES
(333, "Yasmina", NULL),  # Yasmina is the CEO (manager_id is NULL)
(198, "John", 333),      # John has ID 198 and reports to 333 (Yasmina)
(692, "Tarek", 333),
(29, "Pedro", 198),
(4610, "Sarah", 29),
(72, "Pierre", 29),
(123, "Adil", 692);

结果数据集如下所示:

mysql> SELECT * FROM employees ORDER BY id;
+------+---------+------------+
| id   | name    | manager_id |
+------+---------+------------+
|   29 | Pedro   |        198 |
|   72 | Pierre  |         29 |
|  123 | Adil    |        692 |
|  198 | John    |        333 |
|  333 | Yasmina |       NULL |
|  692 | Tarek   |        333 |
| 4610 | Sarah   |         29 |
+------+---------+------------+

要为每个员工生成组织结构图和每个员工的管理链(即从 CEO 到员工的路径),请使用递归 CTE:

WITH RECURSIVE employee_paths (id, name, path) AS
(
  SELECT id, name, CAST(id AS CHAR(200))
    FROM employees
    WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.name, CONCAT(ep.path, ',', e.id)
    FROM employee_paths AS ep JOIN employees AS e
      ON ep.id = e.manager_id
)
SELECT * FROM employee_paths ORDER BY path;

CTE 生成此输出:

+------+---------+-----------------+
| id   | name    | path            |
+------+---------+-----------------+
|  333 | Yasmina | 333             |
|  198 | John    | 333,198         |
|   29 | Pedro   | 333,198,29      |
| 4610 | Sarah   | 333,198,29,4610 |
|   72 | Pierre  | 333,198,29,72   |
|  692 | Tarek   | 333,692         |
|  123 | Adil    | 333,692,123     |
+------+---------+-----------------+

CTE 的工作原理:

  • 非递归SELECT生成 CEO 的行(具有NULL管理者 ID 的行)。

    path列扩展为CHAR(200),以确保递归SELECT生成的较长path值有足够的空间。

  • 递归SELECT生成的每行找到所有直接向前一行生成的员工报告的员工。对于每个这样的员工,行包括员工 ID 和姓名,以及员工的管理链。链是经理的链,员工 ID 添加到末尾。

  • 当员工没有其他人向他们报告时,递归结束。

要查找特定员工或员工的路径,请在顶层SELECT添加WHERE子句。例如,要显示 Tarek 和 Sarah 的结果,请修改SELECT如下:

mysql> WITH RECURSIVE ...
       ...
       SELECT * FROM employees_extended
       WHERE id IN (692, 4610)
       ORDER BY path;
+------+-------+-----------------+
| id   | name  | path            |
+------+-------+-----------------+
| 4610 | Sarah | 333,198,29,4610 |
|  692 | Tarek | 333,692         |
+------+-------+-----------------+

与类似结构比较的通用表达式

通用表达式(CTEs)在某些方面类似于派生表:

  • 两种结构都有名称。

  • 两种结构存在于单个语句的范围内。

由于这些相似之处,CTE 和派生表通常可以互换使用。作为一个简单的例子,以下语句是等价的:

WITH cte AS (SELECT 1) SELECT * FROM cte;
SELECT * FROM (SELECT 1) AS dt;

然而,CTE 相对于派生表有一些优势:

  • 派生表只能在查询中引用一次。而 CTE 可以被多次引用。要使用派生表结果的多个实例,必须多次派生结果。

  • 一个 CTE 可以是自引用的(递归的)。

  • 一个 CTE 可以引用另一个 CTE。

  • 一个 CTE 在语句中的定义出现在开头时可能更容易阅读,而不是嵌入其中。

CTE 类似于使用CREATE [TEMPORARY] TABLE创建的表,但不需要显式定义或删除。对于 CTE,您无需拥有创建表的权限。

15.3 事务和锁定语句

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

15.3.1 START TRANSACTION、COMMIT 和 ROLLBACK 语句

15.3.2 无法回滚的语句

15.3.3 导致隐式提交的语句

15.3.4 SAVEPOINT、ROLLBACK TO SAVEPOINT 和 RELEASE SAVEPOINT 语句

15.3.5 LOCK INSTANCE FOR BACKUP 和 UNLOCK INSTANCE 语句

15.3.6 LOCK TABLES 和 UNLOCK TABLES 语句

15.3.7 SET TRANSACTION 语句

15.3.8 XA 事务

MySQL 通过诸如SET autocommitSTART TRANSACTIONCOMMITROLLBACK等语句支持本地事务(在给定的客户端会话中)。请参阅第 15.3.1 节,“START TRANSACTION, COMMIT, and ROLLBACK Statements”。XA 事务支持使 MySQL 能够参与分布式事务。请参阅第 15.3.8 节,“XA Transactions”。

15.3.1 START TRANSACTIONCOMMITROLLBACK语句

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

START TRANSACTION
    [*transaction_characteristic* [, *transaction_characteristic*] ...]

*transaction_characteristic*: {
    WITH CONSISTENT SNAPSHOT
  | READ WRITE
  | READ ONLY
}

BEGIN [WORK]
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
SET autocommit = {0 | 1}

这些语句提供了对事务的控制:

  • START TRANSACTIONBEGIN 开始一个新事务。

  • COMMIT 提交当前事务,使其更改永久生效。

  • ROLLBACK 回滚当前事务,取消其更改。

  • SET autocommit 禁用或启用当前会话的默认自动提交模式。

默认情况下,MySQL 运行时启用自动提交模式。这意味着,当不在事务内时,每个语句都是原子的,就好像被 START TRANSACTIONCOMMIT 包围。您无法使用 ROLLBACK 撤消效果;但是,如果在执行语句时发生错误,则会回滚该语句。

要隐式禁用单个语句序列的自动提交模式,请使用 START TRANSACTION 语句:

START TRANSACTION;
SELECT @A:=SUM(salary) FROM table1 WHERE type=1;
UPDATE table2 SET summary=@A WHERE type=1;
COMMIT;

使用 START TRANSACTION,自动提交保持禁用,直到您使用 COMMITROLLBACK 结束事务。然后自动提交模式恢复到先前的状态。

START TRANSACTION 允许多个修饰符控制事务特性。要指定多个修饰符,请用逗号分隔它们。

  • WITH CONSISTENT SNAPSHOT 修饰符为能够执行此操作的存储引擎启动一致性读取。这仅适用于 InnoDB。其效果与发出 START TRANSACTION 然后从任何 InnoDB 表中进行 SELECT 相同。请参阅 Section 17.7.2.3, “Consistent Nonlocking Reads”。WITH CONSISTENT SNAPSHOT 修饰符不会更改当前事务的隔离级别,因此仅在当前隔离级别允许一致性读取时才提供一致的快照。唯一允许一致性读取的隔离级别是 REPEATABLE READ。对于所有其他隔离级别,WITH CONSISTENT SNAPSHOT 子句将被忽略。当忽略 WITH CONSISTENT SNAPSHOT 子句时会生成警告。

  • READ WRITEREAD ONLY 修饰符设置事务访问模式。它们允许或禁止对事务中使用的表进行更改。READ ONLY 限制阻止事务修改或锁定对其他事务可见的事务和非事务表;事务仍然可以修改或锁定临时表。

    当事务已知为只读时,MySQL 在InnoDB表上的查询会启用额外的优化。指定READ ONLY可确保在无法自动确定只读状态的情况下应用这些优化。有关更多信息,请参阅第 10.5.3 节,“优化 InnoDB 只读事务”。

    如果未指定访问模式,则应用默认模式。除非默认值已更改,否则为读/写。在同一语句中不允许同时指定READ WRITEREAD ONLY

    在只读模式下,仍然可以使用 DML 语句更改使用TEMPORARY关键字创建的表。使用 DDL 语句进行的更改是不允许的,就像永久表一样。

    有关事务访问模式的其他信息,包括更改默认模式的方法,请参阅第 15.3.7 节,“SET TRANSACTION 语句”。

    如果启用了read_only系统变量,则使用START TRANSACTION READ WRITE显式启动事务需要CONNECTION_ADMIN权限(或已弃用的SUPER权限)。

重要提示

许多用于编写 MySQL 客户端应用程序的 API(如 JDBC)提供了自己的启动事务方法,可以(有时应该)代替从客户端发送START TRANSACTION语句。有关更多信息,请参阅第三十一章,“连接器和 API”,或您的 API 文档。

要显式禁用自动提交模式,请使用以下语句:

SET autocommit=0;

autocommit变量设置为零以禁用自动提交模式后,对事务安全表(例如InnoDBNDB)的更改不会立即生效。您必须使用COMMIT将更改存储到磁盘,或使用ROLLBACK忽略更改。

autocommit 是一个会话变量,必须为每个会话设置。要为每个新连接禁用自动提交模式,请参阅autocommit系统变量在第 7.1.8 节,“服务器系统变量”的描述。

BEGINBEGIN WORK可作为START TRANSACTION的别名来启动事务。START TRANSACTION是标准 SQL 语法,是启动临时事务的推荐方式,并允许BEGIN不支持的修饰符。

BEGIN语句与使用BEGIN关键字开始BEGIN ... END复合语句的方式不同。后者不会开始事务。请参见第 15.6.1 节,“BEGIN ... END 复合语句”。

注意

在所有存储程序(存储过程和函数、触发器和事件)中,解析器将BEGIN [WORK]视为BEGIN ... END块的开始。在这种情况下使用START TRANSACTION开始一个事务。

可选的WORK关键字支持COMMITROLLBACK,以及CHAINRELEASE子句。CHAINRELEASE可用于对事务完成进行额外控制。completion_type系统变量的值确定默认完成行为。请参见第 7.1.8 节,“服务器系统变量”。

AND CHAIN子句导致新事务在当前事务结束后立即开始,并且新事务具有与刚终止事务相同的隔离级别。新事务还使用与刚终止事务相同的访问模式(READ WRITEREAD ONLY)。RELEASE子句导致服务器在终止当前事务后断开当前客户端会话。包括NO关键字可以抑制CHAINRELEASE完成,如果completion_type系统变量设置为默认导致链接或释放完成时,这可能很有用。

开始事务会导致任何待处理的事务被提交。更多信息请参见第 15.3.3 节,“导致隐式提交的语句”。

开始事务还会导致使用LOCK TABLES获取的表锁被释放,就好像执行了UNLOCK TABLES一样。开始事务不会释放使用FLUSH TABLES WITH READ LOCK获取的全局读锁。

为了获得最佳结果,事务应仅使用单个事务安全存储引擎管理的表执行。否则,可能会出现以下问题:

  • 如果使用来自多个事务安全存储引擎(如InnoDB)的表,并且事务隔离级别不是SERIALIZABLE,那么当一个事务提交时,使用相同表的另一个正在进行的事务可能只看到第一个事务所做的一部分更改。也就是说,混合引擎的事务的原子性不能得到保证,可能会导致不一致性。(如果混合引擎事务不频繁,您可以使用SET TRANSACTION ISOLATION LEVEL根据需要在每个事务基础上将隔离级别设置为SERIALIZABLE。)

  • 如果在事务中使用非事务安全的表,那么对这些表的更改将立即存储,而不考虑自动提交模式的状态。

  • 如果在事务中更新非事务表后发出ROLLBACK语句,将会出现ER_WARNING_NOT_COMPLETE_ROLLBACK警告。事务安全表的更改将被回滚,但非事务安全表的更改不会被回滚。

每个事务在COMMIT时以一个块的形式存储在二进制日志中。被回滚的事务不会被记录。(例外:对非事务表的修改无法回滚。如果被回滚的事务包括对非事务表的修改,则整个事务将在结尾处使用ROLLBACK语句记录,以确保对非事务表的修改被复制。)请参阅 Section 7.4.4, “The Binary Log”。

你可以使用SET TRANSACTION语句更改事务的隔离级别或访问模式。请参阅 Section 15.3.7, “SET TRANSACTION Statement”。

回滚可能是一个缓慢的操作,可能会在用户没有明确要求的情况下隐式发生(例如,当发生错误时)。因此,SHOW PROCESSLIST在会话的State列中显示Rolling back,不仅适用于使用ROLLBACK语句执行的显式回滚,还适用于隐式回滚。

注意

在 MySQL 8.0 中,BEGINCOMMITROLLBACK不受--replicate-do-db--replicate-ignore-db规则的影响。

InnoDB执行事务的完全回滚时,事务设置的所有锁都会被释放。如果事务中的一个 SQL 语句由于错误(如重复键错误)而回滚,那么该语句设置的锁将在事务保持活动状态时保留。这是因为InnoDB以一种格式存储行锁,以至于事后无法知道哪个锁是由哪个语句设置的。

如果事务中的一个SELECT语句调用了一个存储函数,并且存储函数中的一个语句失败,那么该语句将会回滚。如果随后为该事务执行ROLLBACK,整个事务将会回滚。

15.3.2 无法回滚的语句

原文:dev.mysql.com/doc/refman/8.0/en/cannot-roll-back.html

有些语句是无法回滚的。一般来说,这些包括数据定义语言(DDL)语句,比如创建或删除数据库的语句,创建、删除或修改表或存储过程的语句。

你应该设计你的事务不包括这样的语句。如果你在事务早期发出一个无法回滚的语句,然后稍后另一个语句失败,那么在这种情况下,通过发出一个ROLLBACK语句无法完全回滚事务的效果。

15.3.3 导致隐式提交的语句

原文:dev.mysql.com/doc/refman/8.0/en/implicit-commit.html

本节列出的语句(以及它们的任何同义词)会隐式结束当前会话中的任何活动事务,就好像在执行该语句之前已经执行了一个COMMIT

大多数这些语句在执行后也会导致隐式提交。其目的是在其自己的特殊事务中处理每个这样的语句。事务控制和锁定语句是例外情况:如果在执行之前发生隐式提交,则在之后不会再发生另一个提交。

  • 数据定义语言(DDL)语句用于定义或修改数据库对象。 ALTER EVENT, ALTER FUNCTION, ALTER PROCEDURE, ALTER SERVER, ALTER TABLE, ALTER TABLESPACE, ALTER VIEW, CREATE DATABASE, CREATE EVENT, CREATE FUNCTION, CREATE INDEX, CREATE PROCEDURE, CREATE ROLE, CREATE SERVER, CREATE SPATIAL REFERENCE SYSTEM, CREATE TABLE, CREATE TABLESPACE, CREATE TRIGGER, CREATE VIEW, DROP DATABASE, DROP EVENT, DROP FUNCTION, DROP INDEX, DROP PROCEDURE, DROP ROLE, DROP SERVER, DROP SPATIAL REFERENCE SYSTEM, DROP TABLE, DROP TABLESPACE, DROP TRIGGER, DROP VIEW, INSTALL PLUGIN, RENAME TABLE, TRUNCATE TABLE, UNINSTALL PLUGIN.

    使用 TEMPORARY 关键字的 CREATE TABLEDROP TABLE 语句不会提交事务。(这不适用于对临时表的其他操作,如 ALTER TABLECREATE INDEX,这些操作会导致提交。)然而,虽然没有隐式提交,但也不能回滚该语句,这意味着使用这些语句会违反事务的原子性。例如,如果使用 CREATE TEMPORARY TABLE 然后回滚事务,表仍然存在。

    InnoDB 中,CREATE TABLE 语句被处理为单个事务。这意味着用户的 ROLLBACK 不会撤消用户在该事务期间进行的 CREATE TABLE 语句。

    当创建非临时表时,CREATE TABLE ... SELECT 在执行语句之前和之后会导致隐式提交。(对于 CREATE TEMPORARY TABLE ... SELECT 不会发生提交。)

  • 隐式使用或修改 mysql 数据库中表的语句。 ALTER USER, CREATE USER, DROP USER, GRANT, RENAME USER, REVOKE, SET PASSWORD.

  • 事务控制和锁定语句。 BEGIN, LOCK TABLES, 如果值尚未为 1,则 SET autocommit = 1START TRANSACTION, UNLOCK TABLES.

    只有当使用 LOCK TABLES 获取非事务表锁时,UNLOCK TABLES 才会提交事务。对于跟随 FLUSH TABLES WITH READ LOCKUNLOCK TABLES 不会发生提交,因为后者不会获取表级锁。

    事务不能嵌套。这是在发出START TRANSACTION语句或其同义词时为当前事务执行的隐式提交的结果。

    导致隐式提交的语句不能在事务处于ACTIVE状态时用于 XA 事务。

    BEGIN语句与开始BEGIN ... END复合语句的BEGIN关键字的使用不同。后者不会导致隐式提交。请参阅 Section 15.6.1, “BEGIN ... END Compound Statement”。

  • 数据加载语句。 LOAD DATALOAD DATA仅对使用NDB存储引擎的表造成隐式提交。

  • 管理语句。 ANALYZE TABLECACHE INDEXCHECK TABLEFLUSHLOAD INDEX INTO CACHEOPTIMIZE TABLEREPAIR TABLERESET(但不包括RESET PERSIST)。

  • 复制控制语句START REPLICASTOP REPLICARESET REPLICACHANGE REPLICATION SOURCE TOCHANGE MASTER TO。在 MySQL 8.0.22 中,SLAVE 关键字被 REPLICA 替换。

15.3.4 保存点、回滚到保存点和释放保存点语句

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

SAVEPOINT *identifier*
ROLLBACK [WORK] TO [SAVEPOINT] *identifier*
RELEASE SAVEPOINT *identifier*

InnoDB支持 SQL 语句SAVEPOINTROLLBACK TO SAVEPOINTRELEASE SAVEPOINT以及用于ROLLBACK的可选WORK关键字。

SAVEPOINT语句使用identifier设置具有名称的事务保存点。如果当前事务具有相同名称的保存点,则旧保存点将被删除,并设置一个新的保存点。

ROLLBACK TO SAVEPOINT语句将事务回滚到指定的保存点,而不终止事务。在回滚中,当前事务在设置保存点后对行所做的修改将被撤消,但InnoDB不会释放保存点后存储在内存中的行锁。(对于新插入的行,锁信息由存储在行中的事务 ID 携带;锁不会单独存储在内存中。在这种情况下,行锁在撤消时被释放。)比指定保存点设置的保存点将被删除。

如果ROLLBACK TO SAVEPOINT语句返回以下错误,则表示不存在具有指定名称的保存点:

ERROR 1305 (42000): SAVEPOINT *identifier* does not exist

RELEASE SAVEPOINT语句从当前事务的保存点集中移除指定的保存点。不会发生提交或回滚。如果保存点不存在,则会报错。

如果执行COMMIT或未命名保存点的ROLLBACK,则会删除当前事务的所有保存点。

当调用存储函数或触发器被激活时,将创建一个新的保存点级别。之前级别上的保存点变得不可用,因此不会与新级别上的保存点发生冲突。当函数或触发器终止时,它创建的任何保存点都会被释放,并且之前的保存点级别会被恢复。

15.3.5 LOCK INSTANCE FOR BACKUP 和 UNLOCK INSTANCE 语句

原文:dev.mysql.com/doc/refman/8.0/en/lock-instance-for-backup.html

LOCK INSTANCE FOR BACKUP

UNLOCK INSTANCE

LOCK INSTANCE FOR BACKUP获取一个实例级别的备份锁,允许在在线备份期间进行 DML 操作,同时阻止可能导致不一致快照的操作。

执行LOCK INSTANCE FOR BACKUP语句需要BACKUP_ADMIN权限。当从早期版本升级到 MySQL 8.0 时,具有RELOAD权限的用户会自动被授予BACKUP_ADMIN权限。

多个会话可以同时持有备份锁。

UNLOCK INSTANCE释放当前会话持有的备份锁。如果会话终止,会话持有的备份锁也会被释放。

LOCK INSTANCE FOR BACKUP阻止创建、重命名或删除文件。REPAIR TABLE TRUNCATE TABLEOPTIMIZE TABLE和账户管理语句被阻止。参见第 15.7.1 节,“账户管理语句”。还会阻止修改未记录在InnoDB重做日志中的InnoDB文件的操作。

LOCK INSTANCE FOR BACKUP允许执行仅影响用户创建的临时表的 DDL 操作。实际上,在持有备份锁时,可以创建、重命名或删除属于用户创建的临时表的文件。也允许创建二进制日志文件。

在实例上有效的LOCK INSTANCE FOR BACKUP语句生效期间,不应发出PURGE BINARY LOGS语句,因为这违反了备份锁的规则,会从服务器中删除文件。从 MySQL 8.0.28 开始,这是不允许的。

LOCK INSTANCE FOR BACKUP获取的备份锁独立于事务锁和由FLUSH TABLES *tbl_name* [, *tbl_name*] ... WITH READ LOCK获取的锁,并允许以下语句序列:

LOCK INSTANCE FOR BACKUP;
FLUSH TABLES *tbl_name* [, *tbl_name*] ... WITH READ LOCK;
UNLOCK TABLES;
UNLOCK INSTANCE;
FLUSH TABLES *tbl_name* [, *tbl_name*] ... WITH READ LOCK;
LOCK INSTANCE FOR BACKUP;
UNLOCK INSTANCE;
UNLOCK TABLES;

lock_wait_timeout设置定义了LOCK INSTANCE FOR BACKUP语句在放弃之前等待获取锁的时间。

15.3.6 LOCK TABLESUNLOCK TABLES 语句

原文:dev.mysql.com/doc/refman/8.0/en/lock-tables.html

LOCK {TABLE | TABLES}
    *tbl_name* [[AS] *alias*] *lock_type*
    [, *tbl_name* [[AS] *alias*] *lock_type*] ...

*lock_type*: {
    READ [LOCAL]
  | [LOW_PRIORITY] WRITE
}

UNLOCK {TABLE | TABLES}

MySQL 允许客户端会话明确为协作访问表的其他会话或在会话需要独占访问表时阻止其他会话修改表而获取表锁。一个会话只能为自己获取或释放锁。一个会话不能为另一个会话获取锁或释放另一个会话持有的锁。

锁可以用于模拟事务或在更新表时获得更快的速度。这在 Table-Locking Restrictions and Conditions 中有更详细的解释。

LOCK TABLES 明确为当前客户端会话获取表锁。表锁可以用于基表或视图。你必须拥有 LOCK TABLES 权限,并且对每个要锁定的对象都必须拥有 SELECT 权限。

对于视图锁定,LOCK TABLES 会将视图中使用的所有基表添加到要锁定的表集合中,并自动锁定它们。对于任何被锁定视图下的表,LOCK TABLES 会检查视图定义者(对于 SQL SECURITY DEFINER 视图)或调用者(对于所有视图)对表具有适当的权限。

如果你使用 LOCK TABLES 明确锁定一个表,那么任何触发器中使用的表也会隐式锁定,如 LOCK TABLES and Triggers 中所述。

如果你使用 LOCK TABLES 明确锁定一个表,那么任何通过外键约束相关的表会被隐式打开并锁定。对于外键检查,相关表会被采取共享只读锁(LOCK TABLES READ)。对于级联更新,涉及操作的相关表会被采取共享无写锁(LOCK TABLES WRITE)。

UNLOCK TABLES 明确释放当前会话持有的任何表锁。在获取新锁之前,LOCK TABLES 会隐式释放当前会话持有的任何表锁。

UNLOCK TABLES的另一个用途是释放使用FLUSH TABLES WITH READ LOCK语句获取的全局读锁,该语句使您可以锁定所有数据库中的所有表。请参见第 15.7.8.3 节,“FLUSH 语句”。(如果您有可以在某个时间点进行快照的文件系统(如 Veritas),这是一个非常方便的备份方式。)

LOCK TABLELOCK TABLES的同义词;UNLOCK TABLEUNLOCK TABLES的同义词。

表锁仅防止其他会话进行不当读取或写入。持有WRITE锁的会话可以执行诸如DROP TABLETRUNCATE TABLE等表级操作。对于持有READ锁的会话,不允许执行DROP TABLETRUNCATE TABLE操作。

以下讨论仅适用于非TEMPORARY表。对于TEMPORARY表,允许(但会被忽略)使用LOCK TABLES。该表可以在创建它的会话中自由访问,而不受其他锁定的影响。不需要锁定,因为没有其他会话可以看到该表。

  • 表锁定获取

  • 表锁定释放

  • 表锁定和事务的交互

  • LOCK TABLES 和触发器

  • 表锁定的限制和条件

表锁定获取

要在当前会话中获取表锁,请使用LOCK TABLES语句,该语句获取元数据锁(参见第 10.11.4 节,“元数据锁定”)。

可用的锁类型如下:

READ [LOCAL]锁:

  • 拥有锁的会话可以读取表(但不能写入)。

  • 多个会话可以同时为表获取READ锁。

  • 其他会话可以在不显式获取READ锁的情况下读取表。

  • LOCAL 修饰符允许其他会话在持有锁的情况下执行非冲突的 INSERT 语句(并发插入)。但是,如果在持有锁的同时使用外部进程操作数据库,则不能使用 READ LOCAL。对于 InnoDB 表,READ LOCALREAD 相同。

[LOW_PRIORITY] WRITE 锁:

  • 持有锁的会话可以读取和写入表。

  • 只有持有锁的会话才能访问表。在锁被释放之前,其他会话无法访问它。

  • 当持有 WRITE 锁时,其他会话对表的锁请求将被阻塞。

  • LOW_PRIORITY 修饰符没有影响。在 MySQL 的早期版本中,它会影响锁定行为,但现在不再有效。它现在已被弃用,使用 WRITE 而不带 LOW_PRIORITY 会产生警告。

WRITE 锁通常比 READ 锁具有更高的优先级,以确保更新尽快处理。这意味着如果一个会话获取了 READ 锁,然后另一个会话请求 WRITE 锁,后续的 READ 锁请求将等待,直到请求 WRITE 锁的会话获取并释放锁。(对于 max_write_lock_count 系统变量的小值,可能会有例外情况;请参见 Section 10.11.4, “Metadata Locking”.)

如果由于其他会话在任何表上持有的锁而导致 LOCK TABLES 语句必须等待,它将阻塞直到所有锁都可以被获取。

需要锁定表的会话必须在单个 LOCK TABLES 语句中获取所有需要的锁。在持有这些锁的情况下,会话只能访问被锁定的表。例如,在以下语句序列中,尝试访问 t2 会导致错误,因为它在 LOCK TABLES 语句中未被锁定:

mysql> LOCK TABLES t1 READ;
mysql> SELECT COUNT(*) FROM t1;
+----------+
| COUNT(*) |
+----------+
|        3 |
+----------+
mysql> SELECT COUNT(*) FROM t2;
ERROR 1100 (HY000): Table 't2' was not locked with LOCK TABLES

INFORMATION_SCHEMA 数据库中的表是一个例外。即使一个会话持有使用 LOCK TABLES 获取的表锁,也可以在不显式锁定的情况下访问它们。

不能在单个查询中多次引用具有相同名称的锁定表。请改用别名,并为表和每个别名获取单独的锁:

mysql> LOCK TABLE t WRITE, t AS t1 READ;
mysql> INSERT INTO t SELECT * FROM t;
ERROR 1100: Table 't' was not locked with LOCK TABLES
mysql> INSERT INTO t SELECT * FROM t AS t1;

第一个INSERT出现错误是因为对于一个被锁定的表有两个相同名称的引用。第二个INSERT成功是因为对表的引用使用了不同的名称。

如果您的语句通过别名引用表,那么必须使用相同的别名锁定表。不能不指定别名而锁定表:

mysql> LOCK TABLE t READ;
mysql> SELECT * FROM t AS myalias;
ERROR 1100: Table 'myalias' was not locked with LOCK TABLES

相反,如果您使用别名锁定表,那么在语句中必须使用该别名引用它:

mysql> LOCK TABLE t AS myalias READ;
mysql> SELECT * FROM t;
ERROR 1100: Table 't' was not locked with LOCK TABLES
mysql> SELECT * FROM t AS myalias;

表锁定释放

当会话持有的表锁被释放时,它们会同时被释放。一个会话可以显式释放其锁,或者在某些条件下锁可能会隐式释放。

  • 一个会话可以通过UNLOCK TABLES显式释放其锁。

  • 如果一个会话在已经持有锁的情况下发出LOCK TABLES语句以获取锁,那么在授予新锁之前,现有的锁会被隐式释放。

  • 如果一个会话开始一个事务(例如,使用START TRANSACTION),会执行一个隐式的UNLOCK TABLES,导致现有的锁被释放。(有关表锁定和事务之间的交互的更多信息,请参阅表锁定和事务的交互.)

如果客户端会话的连接终止,无论是正常还是异常,服务器会隐式释放会话持有的所有表锁(事务性和非事务性)。如果客户端重新连接,那么这些锁将不再生效。此外,如果客户端有一个活动事务,在断开连接时服务器会回滚该事务,如果重新连接发生,则新会话将以自动提交启用的方式开始。因此,客户端可能希望禁用自动重新连接。启用自动重新连接时,如果重新连接发生,客户端不会收到通知,但任何表锁或当前事务都会丢失。禁用自动重新连接时,如果连接断开,下一个发出的语句将出现错误。客户端可以检测错误并采取适当的措施,如重新获取锁或重做事务。请参阅自动重新连接控制。

注意

如果你在一个被锁定的表上使用 ALTER TABLE,它可能会变为未锁定状态。例如,如果你尝试第二次 ALTER TABLE 操作,结果可能是一个错误 Table '*tbl_name*' was not locked with LOCK TABLES。为了处理这个问题,在第二次修改之前再次锁定表。另请参阅 Section B.3.6.1, “ALTER TABLE 存在的问题”。

表锁定和事务的交互

LOCK TABLESUNLOCK TABLES 与事务的使用交互如下:

  • LOCK TABLES 不是事务安全的,并在尝试锁定表之前隐式提交任何活动事务。

  • UNLOCK TABLES 隐式提交任何活动事务,但仅在使用 LOCK TABLES 获取表锁时。例如,在以下一组语句中,UNLOCK TABLES 释放全局读锁但不提交事务,因为没有表锁生效:

    FLUSH TABLES WITH READ LOCK;
    START TRANSACTION;
    SELECT ... ;
    UNLOCK TABLES;
    
  • 开始一个事务(例如,使用 START TRANSACTION)隐式提交任何当前事务并释放现有的表锁。

  • FLUSH TABLES WITH READ LOCK 获取全局读锁而不是表锁,因此它不受 LOCK TABLESUNLOCK TABLES 关于表锁定和隐式提交的相同行为影响。例如,START TRANSACTION 不会释放全局读锁。请参见 Section 15.7.8.3, “FLUSH 语句”。

  • 其他隐式导致事务提交的语句不会释放现有的表锁。有关此类语句的列表,请参见 Section 15.3.3, “导致隐式提交的语句”。

  • 使用事务表(如InnoDB表)的正确方法是使用SET autocommit = 0(而不是START TRANSACTION)开始事务,然后使用LOCK TABLES,直到显式提交事务之前不要调用UNLOCK TABLES。例如,如果你需要向表t1写入并从表t2读取,你可以这样做:

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

    当你调用LOCK TABLES时,InnoDB内部会获取自己的表锁,而 MySQL 会获取自己的表锁。InnoDB在下一次提交时释放其内部表锁,但要释放 MySQL 的表锁,你必须调用UNLOCK TABLES。你不应该设置autocommit = 1,因为这样InnoDB会在调用LOCK TABLES后立即释放其内部表锁,很容易发生死锁。如果设置autocommit = 1InnoDB根本不会获取内部表锁,以帮助旧应用程序避免不必要的死锁。

  • ROLLBACK不会释放表锁。

表锁和触发器

如果使用LOCK TABLES显式锁定一个表,那么触发器中使用的任何表也会隐式锁定:

  • 这些锁与使用LOCK TABLES语句显式获取的锁同时获取。

  • 触发器中使用的表的锁取决于表是否仅用于读取。如果是,那么读取锁就足够了。否则,会使用写入锁。

  • 如果一个表被使用LOCK TABLES显式锁定以供读取,但由于可能在触发器中被修改而需要写入锁定,那么会获取写入锁定而不是读取锁定。(也就是说,由于表在触发器中的出现导致对表的显式读取锁请求被转换为写入锁请求。)

假设你使用这个语句锁定了两个表,t1t2

LOCK TABLES t1 WRITE, t2 READ;

如果t1t2有任何触发器,触发器中使用的表也会被锁定。假设t1有一个定义如下的触发器:

CREATE TRIGGER t1_a_ins AFTER INSERT ON t1 FOR EACH ROW
BEGIN
  UPDATE t4 SET count = count+1
      WHERE id = NEW.id AND EXISTS (SELECT a FROM t3);
  INSERT INTO t2 VALUES(1, 2);
END;

LOCK TABLES语句的结果是,t1t2被锁定,因为它们出现在语句中,而t3t4被锁定,因为它们在触发器中使用:

  • 根据WRITE锁请求,t1被锁定为写入。

  • 即使请求是读取锁,t2也被锁定为写入。这是因为在触发器内插入了t2,所以读取请求被转换为写入请求。

  • 因为只在触发器内部读取,所以t3被锁定为只读。

  • 因为可能在触发器内更新,所以t4被锁定为写入。

表锁定的限制和条件

您可以安全地使用KILL来终止等待表锁定的会话。请参阅第 15.7.8.4 节,“KILL 语句”。

不能在存储程序中使用LOCK TABLESUNLOCK TABLES

不能使用LOCK TABLES锁定performance_schema数据库中的表,除了setup_*xxx*表。

LOCK TABLES生成的锁的范围是单个 MySQL 服务器。它与 NDB Cluster 不兼容,NDB Cluster 无法跨多个mysqld实例强制执行 SQL 级别的锁。您可以在 API 应用程序中强制执行锁定。有关更多信息,请参阅第 25.2.7.10 节,“与多个 NDB Cluster 节点相关的限制”。

LOCK TABLES语句生效期间,禁止以下语句:CREATE TABLECREATE TABLE ... LIKECREATE VIEWDROP VIEW以及对存储函数、存储过程和事件的 DDL 语句。

对于一些操作,必须访问mysql数据库中的系统表。例如,HELP语句需要服务器端帮助表的内容,而CONVERT_TZ()可能需要读取时区表。服务器会根据需要隐式锁定系统表以进行读取,因此您无需显式锁定它们。这些表被视为刚刚描述的方式:

mysql.help_category
mysql.help_keyword
mysql.help_relation
mysql.help_topic
mysql.time_zone
mysql.time_zone_leap_second
mysql.time_zone_name
mysql.time_zone_transition
mysql.time_zone_transition_type

如果您想要在任何表格上明确放置一个WRITE锁定,并使用LOCK TABLES语句,那么该表格必须是唯一被锁定的;没有其他表格可以与相同语句一起被锁定。

通常情况下,您不需要锁定表格,因为所有单个UPDATE语句都是原子的;没有其他会话可以干扰任何其他当前执行的 SQL 语句。然而,在一些情况下,锁定表格可能会带来优势:

  • 如果您将在一组MyISAM表上运行许多操作,锁定您将要使用的表格会更快。锁定MyISAM表格会加快在其上插入、更新或删除的速度,因为 MySQL 在调用UNLOCK TABLES之前不会刷新被锁定表格的键缓存。通常情况下,每个 SQL 语句后都会刷新键缓存。

    锁定表格的缺点是,没有会话可以更新一个READ-锁定的表格(包括持有锁的表格),也没有会话可以访问一个WRITE-锁定的表格,除了持有锁的表格。

  • 如果您正在使用非事务性存储引擎来创建表格,如果您希望确保在SELECTUPDATE之间没有其他会话修改表格,您必须使用LOCK TABLES。这里展示的示例需要使用LOCK TABLES来安全执行:

    LOCK TABLES trans READ, customer WRITE;
    SELECT SUM(value) FROM trans WHERE customer_id=*some_id*;
    UPDATE customer
      SET total_value=*sum_from_previous_statement*
      WHERE customer_id=*some_id*;
    UNLOCK TABLES;
    

    没有LOCK TABLES,可能会导致另一个会话在执行SELECTUPDATE语句之间插入新行到trans表中。

在许多情况下,您可以通过使用相对更新(UPDATE customer SET *value*=*value*+*new_value*)或LAST_INSERT_ID()函数来避免使用LOCK TABLES

在某些情况下,您也可以通过使用用户级别的咨询锁函数GET_LOCK()RELEASE_LOCK()来避免锁定表格。这些锁保存在服务器的哈希表中,并且使用pthread_mutex_lock()pthread_mutex_unlock()实现高速。请参见第 14.14 节,“锁定函数”。

有关锁定策略的更多信息,请参见第 10.11.1 节,“内部锁定方法”。

15.3.7 SET TRANSACTION 语句

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

SET [GLOBAL | SESSION] TRANSACTION
    *transaction_characteristic* [, *transaction_characteristic*] ...

*transaction_characteristic*: {
    ISOLATION LEVEL *level*
  | *access_mode*
}

*level*: {
     REPEATABLE READ
   | READ COMMITTED
   | READ UNCOMMITTED
   | SERIALIZABLE
}

*access_mode*: {
     READ WRITE
   | READ ONLY
}

此语句指定了事务 特性。它接受一个由逗号分隔的一个或多个特性值的列表。每个特性值设置事务的隔离级别 或访问模式。隔离级别用于对InnoDB 表进行操作。访问模式指定事务是以读/写模式还是只读模式运行。

此外,SET TRANSACTION 可以包括一个可选的GLOBALSESSION关键字,以指示语句的范围。

  • 事务隔离级别

  • 事务访问模式

  • 事务特性范围

事务隔离级别

要设置事务隔离级别,请使用ISOLATION LEVEL *level*子句。不允许在同一SET TRANSACTION 语句中指定多个ISOLATION LEVEL子句。

默认隔离级别是REPEATABLE READ。其他允许的值是READ COMMITTEDREAD UNCOMMITTEDSERIALIZABLE。有关这些隔离级别的信息,请参见第 17.7.2.1 节,“事务隔离级别”。

事务访问模式

要设置事务访问模式,请使用READ WRITEREAD ONLY子句。不允许在同一SET TRANSACTION 语句中指定多个访问模式子句。

默认情况下,事务以读/写模式进行,允许对事务中使用的表进行读取和写入。可以使用具有READ WRITE访问模式的SET TRANSACTION 明确指定此模式。

如果事务访问模式设置为READ ONLY,则禁止对表进行更改。这可能使存储引擎能够进行性能改进,这些改进在不允许写入时可能是可能的。

在只读模式下,仍然可以使用 DML 语句更改使用 TEMPORARY 关键字创建的表。使用 DDL 语句进行的更改是不允许的,就像永久表一样。

也可以使用 START TRANSACTION 语句为单个事务指定 READ WRITEREAD ONLY 访问模式。

事务特性范围

您可以全局设置事务特性,为当前会话或仅限于下一个事务:

  • 使用 GLOBAL 关键字:

    • 该语句全局适用于所有后续会话。

    • 现有会话不受影响。

  • 使用 SESSION 关键字:

    • 该语句适用于当前会话中执行的所有后续事务。

    • 该语句允许在事务内部执行,但不影响当前正在进行的事务。

    • 如果在事务之间执行,则该语句将覆盖设置下一个事务值的任何先前语句。

  • 没有任何 SESSIONGLOBAL 关键字:

    • 该语句仅适用于会话中执行的下一个单个事务。

    • 后续事务将恢复使用命名特性的会话值。

    • 该语句不允许在事务内部执行:

      mysql> START TRANSACTION;
      Query OK, 0 rows affected (0.02 sec)
      
      mysql> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
      ERROR 1568 (25001): Transaction characteristics can't be changed
      while a transaction is in progress
      

更改全局事务特性需要 CONNECTION_ADMIN 权限(或已弃用的 SUPER 权限)。任何会话都可以自由更改其会话特性(即使在事务中间),或者更改其下一个事务的特性(在该事务开始之前)。

要在服务器启动时设置全局隔离级别,请在命令行或选项文件中使用 --transaction-isolation=*level* 选项。此选项的 level 值使用破折号而不是空格,因此可接受的值为 READ-UNCOMMITTEDREAD-COMMITTEDREPEATABLE-READSERIALIZABLE

同样,要在服务器启动时设置全局事务访问模式,请使用 --transaction-read-only 选项。默认值为 OFF(读/写模式),但该值可以设置为 ON 以进行只读模式。

例如,要将隔离级别设置为 REPEATABLE READ 并将访问模式设置为 READ WRITE,请在选项文件的 [mysqld] 部分中使用以下行:

[mysqld]
transaction-isolation = REPEATABLE-READ
transaction-read-only = OFF

在运行时,可以间接地使用SET TRANSACTION语句设置全局、会话和下一个事务范围级别的特征,如前所述。也可以直接使用SET语句为transaction_isolationtransaction_read_only系统变量赋值来直接设置它们:

  • SET TRANSACTION允许在不同范围级别设置事务特征时使用可选的GLOBALSESSION关键字。

  • 用于为transaction_isolationtransaction_read_only系统变量赋值的SET语句具有在不同范围级别设置这些变量的语法。

以下表格显示了每个SET TRANSACTION和变量赋值语法设置的特征范围级别。

表 15.9 事务特征的 SET TRANSACTION 语法

语法 受影响的特征范围
SET GLOBAL TRANSACTION *transaction_characteristic* 全局
SET SESSION TRANSACTION *transaction_characteristic* 会话
SET TRANSACTION *transaction_characteristic* 仅适用于下一个事务

表 15.10 事务特征的 SET 语法

语法 受影响的特征范围
SET GLOBAL *var_name* = *value* 全局
SET @@GLOBAL.*var_name* = *value* 全局
SET PERSIST *var_name* = *value* 全局
SET @@PERSIST.*var_name* = *value* 全局
SET PERSIST_ONLY *var_name* = *value* 无运行时效果
SET @@PERSIST_ONLY.*var_name* = *value* 无运行时效果
SET SESSION *var_name* = *value* 会话
SET @@SESSION.*var_name* = *value* 会话
SET *var_name* = *value* 会话
SET @@*var_name* = *value* 仅适用于下一个事务
语法 受影响的特征范围

可以在运行时检查事务特征的全局和会话值:

SELECT @@GLOBAL.transaction_isolation, @@GLOBAL.transaction_read_only;
SELECT @@SESSION.transaction_isolation, @@SESSION.transaction_read_only;

15.3.8 XA 事务

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

15.3.8.1 XA 事务 SQL 语句

15.3.8.2 XA 事务状态

15.3.8.3 XA 事务的限制

InnoDB存储引擎支持 XA 事务。MySQL 的 XA 实现基于 X/Open CAE 文档Distributed Transaction Processing: The XA Specification。该文档由 The Open Group 发布,可在www.opengroup.org/public/pubs/catalog/c193.htm获取。当前 XA 实现的限制在第 15.3.8.3 节“XA 事务的限制”中描述。

在客户端方面,没有特殊要求。MySQL 服务器的 XA 接口由以XA关键字开头的 SQL 语句组成。MySQL 客户端程序必须能够发送 SQL 语句并理解 XA 语句接口的语义。它们不需要链接到最新的客户端库。旧的客户端库也可以工作。

在 MySQL Connectors 中,MySQL Connector/J 5.0.0 及更高版本直接支持 XA,通过一个处理 XA SQL 语句接口的类接口。

XA 支持分布式事务,即允许多个独立的事务资源参与全局事务。事务资源通常是关系型数据库,但也可能是其他类型的资源。

一个全局事务涉及几个本身是事务性的操作,但所有这些操作必须作为一个组要么全部成功完成,要么全部回滚。实质上,这将 ACID 属性“提升”到一个更高的级别,以便多个 ACID 事务可以作为全局操作的组成部分一起执行,并且也具有 ACID 属性。(与非分布式事务一样,如果您的应用程序对读现象敏感,可能更喜欢SERIALIZABLEREPEATABLE READ可能对分布式事务不够。)

一些分布式事务的例子:

  • 一个应用程序可以充当一个集成工具,将消息服务与关系型数据库结合在一起。该应用程序确保处理涉及消息发送、检索和处理的事务,同时涉及事务性数据库,都发生在一个全局事务中。你可以将其视为“事务性电子邮件”。

  • 一个应用程序执行涉及不同数据库服务器的操作,例如 MySQL 服务器和 Oracle 服务器(或多个 MySQL 服务器),其中涉及多个服务器的操作必须作为全局事务的一部分进行,而不是作为每个服务器本地的单独事务。

  • 一家银行在关系数据库管理系统(RDBMS)中保留账户信息,并通过自动取款机(ATM)分发和接收资金。必须确保 ATM 操作在账户中正确反映,但这不能仅通过 RDBMS 完成。全局事务管理器整合 ATM 和数据库资源,以确保金融交易的整体一致性。

使用全局事务的应用程序涉及一个或多个资源管理器和一个事务管理器:

  • 资源管理器(RM)提供对事务资源的访问。数据库服务器是资源管理器的一种。必须能够提交或回滚由 RM 管理的事务。

  • 事务管理器(TM)协调作为全局事务一部分的事务。它与处理每个事务的 RM 进行通信。全局事务及其分支由稍后描述的命名方案标识。

MySQL 对 XA 的实现使 MySQL 服务器能够充当处理全局事务中的 XA 事务的资源管理器。连接到 MySQL 服务器的客户端程序充当事务管理器。

要执行全局事务,必须知道涉及哪些组件,并使每个组件达到可以提交或回滚的状态。根据每个组件报告的关于其成功能力的情况,它们必须作为一个原子组提交或回滚。也就是说,要么所有组件都必须提交,要么所有组件都必须回滚。要管理全局事务,必须考虑到任何组件或连接网络可能会失败。

执行全局事务的过程使用两阶段提交(2PC)。这发生在执行全局事务的分支执行的操作之后。

  1. 在第一阶段,所有分支都被准备好了。也就是说,它们被 TM 告知准备好提交。通常,这意味着每个管理分支的 RM 在稳定存储中记录了分支的操作。这些分支指示它们是否能够这样做,这些结果将用于第二阶段。

  2. 在第二阶段,TM 告诉 RM 是否提交或回滚。如果所有分支在准备时指示它们能够提交,那么所有分支都被告知提交。如果任何分支在准备时指示它不能提交,那么所有分支都被告知回滚。

在某些情况下,全局事务可能会使用一阶段提交(1PC)。例如,当事务管理器发现全局事务仅包含一个事务资源(即单个分支)时,可以要求该资源同时准备和提交。

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

15.3.8.1 XA 事务 SQL 语句

要在 MySQL 中执行 XA 事务,请使用以下语句:

XA {START|BEGIN} *xid* [JOIN|RESUME]

XA END *xid* [SUSPEND [FOR MIGRATE]]

XA PREPARE *xid*

XA COMMIT *xid* [ONE PHASE]

XA ROLLBACK *xid*

XA RECOVER [CONVERT XID]

对于 XA STARTJOINRESUME 子句被识别但没有效果。

对于 XA ENDSUSPEND [FOR MIGRATE] 子句被识别但没有效果。

每个 XA 语句都以 XA 关键字开头,大多数需要一个 xid 值。xid 是一个 XA 事务标识符。它指示语句适用于哪个事务。xid 值由客户端提供,或由 MySQL 服务器生成。xid 值有一到三部分:

*xid*: *gtrid* [, *bqual* [, *formatID* ]]

gtrid 是全局事务标识符,bqual 是分支限定符,formatID 是标识 gtridbqual 值使用的格式的数字。如语法所示,bqualformatID 是可选的。如果未给出,默认 bqual 值为 ''。如果未给出,默认 formatID 值为 1。

gtridbqual 必须是字符串字面量,每个最多 64 字节(而非字符)长。gtridbqual 可以以几种方式指定。您可以使用带引号的字符串('ab')、十六进制字符串(X'6162'0x6162)或位值(b'*nnnn*')。

formatID 是一个无符号整数。

gtridbqual 值由 MySQL 服务器底层的 XA 支持程序解释为字节。然而,在解析包含 XA 语句的 SQL 语句时,服务器使用特定的字符集。为了安全起见,将 gtridbqual 写成十六进制字符串。

xid 值通常由事务管理器生成。一个事务管理器生成的值必须与其他事务管理器生成的值不同。给定的事务管理器必须能够在 XA RECOVER 语句返回的值列表中识别出自己的 xid 值。

XA START *xid* 使用给定的 xid 值启动一个 XA 事务。每个 XA 事务必须具有唯一的 xid 值,因此该值不能当前被另一个 XA 事务使用。使用 gtridbqual 值来评估唯一性。随后的所有 XA 语句必须使用与 XA START 语句中给定的 xid 值相同的 xid 值指定。如果您使用这些语句之一,但指定的 xid 值与某个现有 XA 事务不对应,则会发生错误。

从 MySQL 8.0.31 开始,当服务器运行时带有 --replicate-do-db--replicate-ignore-db 时,XA STARTXA BEGINXA ENDXA COMMITXA ROLLBACK 语句不会被默认数据库过滤。

一个或多个 XA 事务可以是同一个全局事务的一部分。给定全局事务中的所有 XA 事务必须在 xid 值中使用相同的 gtrid 值。因此,gtrid 值必须是全局唯一的,以确保对于给定 XA 事务是哪个全局事务的一部分没有歧义。 xid 值的 bqual 部分必须对于全局事务中的每个 XA 事务都不同。 (bqual 值必须不同的要求是当前 MySQL XA 实现的限制。它不是 XA 规范的一部分。)

XA RECOVER 语句返回 MySQL 服务器上处于PREPARED状态的 XA 事务的信息。(参见 Section 15.3.8.2, “XA 事务状态”.)输出包括服务器上每个这样的 XA 事务的行,无论是哪个客户端启动的。

XA RECOVER 需要 XA_RECOVER_ADMIN 权限。这个权限要求阻止用户发现除了自己之外的未完成准备好的 XA 事务的 XID 值。它不影响 XA 事务的正常提交或回滚,因为启动它的用户知道它的 XID。

XA RECOVER 输出行如下(对于由部分 'abc''def'7 组成的 xid 值的示例):

mysql> XA RECOVER;
+----------+--------------+--------------+--------+
| formatID | gtrid_length | bqual_length | data   |
+----------+--------------+--------------+--------+
|        7 |            3 |            3 | abcdef |
+----------+--------------+--------------+--------+

输出列的含义如下:

  • formatID 是事务 xidformatID 部分

  • gtrid_lengthxidgtrid 部分的字节长度

  • bqual_lengthxidbqual 部分的字节长度

  • dataxidgtridbqual 部分的串联

XID 值可能包含不可打印字符。XA RECOVER 允许一个可选的 CONVERT XID 子句,以便客户端可以请求十六进制的 XID 值。

原文:dev.mysql.com/doc/refman/8.0/en/xa-states.html

15.3.8.2 XA 事务状态

一个 XA 事务会经历以下状态:

  1. 使用 XA START 来启动一个 XA 事务并将其置于 ACTIVE 状态。

  2. 对于一个 ACTIVE 的 XA 事务,发出构成事务的 SQL 语句,然后发出 XA END 语句。XA END 将事务置于 IDLE 状态。

  3. 对于一个 IDLE 的 XA 事务,可以发出 XA PREPARE 语句或 XA COMMIT ... ONE PHASE 语句:

    • XA PREPARE 将事务置于 PREPARED 状态。此时,XA RECOVER 语句会在输出中包含事务的 xid 值,因为 XA RECOVER 列出所有处于 PREPARED 状态的 XA 事务。

    • XA COMMIT ... ONE PHASE 准备并提交事务。xid 值不会被 XA RECOVER 列出,因为事务已经终止。

  4. 对于一个 PREPARED 的 XA 事务,可以发出 XA COMMIT 语句来提交并终止事务,或者发出 XA ROLLBACK 来回滚并终止事务。

下面是一个简单的 XA 事务,作为全局事务的一部分向表中插入一行:

mysql> XA START 'xatest';
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO mytable (i) VALUES(10);
Query OK, 1 row affected (0.04 sec)

mysql> XA END 'xatest';
Query OK, 0 rows affected (0.00 sec)

mysql> XA PREPARE 'xatest';
Query OK, 0 rows affected (0.00 sec)

mysql> XA COMMIT 'xatest';
Query OK, 0 rows affected (0.00 sec)

在 MySQL 8.0.28 及更早版本中,在给定客户端连接的上下文中,XA 事务和本地(非 XA)事务是互斥的。例如,如果已经发出 XA START 来开始一个 XA 事务,那么在 XA 事务提交或回滚之前不能启动本地事务。反之,如果已经使用 START TRANSACTION 开始了本地事务,那么在事务提交或回滚之前不能使用 XA 语句。

MySQL 8.0.29 及更高版本支持分离的 XA 事务,通过xa_detach_on_prepare系统变量启用(默认为ON)。分离的事务在执行XA PREPARE后与当前会话断开连接(可以由另一个连接提交或回滚)。这意味着当前会话可以自由开始新的本地事务或 XA 事务,而无需等待准备的 XA 事务被提交或回滚。

当 XA 事务被分离时,一个连接对其准备的任何 XA 事务没有特殊知识。如果当前会话尝试提交或回滚给定的 XA 事务(即使是它准备的),在另一个连接已经这样做之后,请求将被拒绝,并显示无效的 XID 错误(ER_XAER_NOTA),因为请求的xid不再存在。

注意

分离的 XA 事务不能使用临时表。

当禁用分离的 XA 事务(xa_detach_on_prepare设置为OFF)时,一个 XA 事务会保持连接,直到由发起连接提交或回滚,就像之前描述的 MySQL 8.0.28 及更早版本一样。不建议为用于组复制的 MySQL 服务器实例禁用分离的 XA 事务;有关更多信息,请参阅服务器实例配置。

如果一个 XA 事务处于ACTIVE状态,你不能发出任何导致隐式提交的语句。这将违反 XA 协议,因为你无法回滚 XA 事务。尝试执行这样的语句会引发以下错误:

ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed
when global transaction is in the ACTIVE state

适用于前述备注的语句列在第 15.3.3 节,“导致隐式提交的语句”中。

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

15.3.8.3 XA 事务的限制

XA 事务支持仅限于InnoDB存储引擎。

对于“外部 XA”,一个 MySQL 服务器充当资源管理器,客户端程序充当事务管理器。对于“内部 XA”,MySQL 服务器内的存储引擎充当 RMs,服务器本身充当 TM。内部 XA 支持受到各个存储引擎能力的限制。处理涉及多个存储引擎的 XA 事务需要内部 XA。实现内部 XA 要求存储引擎在表处理程序级别支持两阶段提交,目前只有InnoDB符合这一要求。

对于XA START,识别JOINRESUME子句但不起作用。

对于XA END,识别SUSPEND [FOR MIGRATE]子句但不起作用。

要求xid值的bqual部分在全局事务中的每个 XA 事务中不同是当前 MySQL XA 实现的限制。这不是 XA 规范的一部分。

XA 事务分两部分写入二进制日志。当发出XA PREPARE时,事务的第一部分直到XA PREPARE使用初始 GTID 写入。XA_prepare_log_event用于在二进制日志中标识这类事务。当发出XA COMMITXA ROLLBACK时,包含仅XA COMMITXA ROLLBACK语句的事务的第二部分使用第二个 GTID 写入。请注意,由XA_prepare_log_event标识的事务的初始部分不一定会跟随其XA COMMITXA ROLLBACK,这可能导致任意两个 XA 事务的二进制日志交错记录。XA 事务的两部分甚至可以出现在不同的二进制日志文件中。这意味着处于PREPARED状态的 XA 事务现在持久存在,直到发出显式的XA COMMITXA ROLLBACK语句,确保 XA 事务与复制兼容。

在副本上,在 XA 事务准备完成后,它会从复制应用程序线程中分离,并可以由副本上的任何线程提交或回滚。这意味着相同的 XA 事务可以在不同线程上以不同状态出现在events_transactions_current表中。events_transactions_current表显示线程上最近监视的事务事件的当前状态,并在线程空闲时不更新此状态。因此,即使在被另一个线程处理后,XA 事务仍可能以PREPARED状态显示在原始应用程序线程中。要明确识别仍处于PREPARED状态且需要恢复的 XA 事务,请使用XA RECOVER语句,而不是性能模式事务表。

使用 XA 事务存在以下限制:

  • 在 MySQL 8.0.30 之前,XA 事务在意外停机时对于二进制日志不是完全具有弹性。如果在服务器正在执行XA PREPAREXA COMMITXA ROLLBACKXA COMMIT ... ONE PHASE语句时发生意外停机,服务器可能无法恢复到正确状态,导致服务器和二进制日志处于不一致状态。在这种情况下,二进制日志可能包含未应用的额外 XA 事务,或者缺少已应用的 XA 事务。此外,如果启用了 GTIDs,在恢复后,@@GLOBAL.GTID_EXECUTED可能无法正确描述已应用的事务。请注意,如果在XA PREPARE之前、在XA PREPAREXA COMMIT(或XA ROLLBACK)之间,或在XA COMMIT(或XA ROLLBACK)之后发生意外停机,则服务器和二进制日志将正确恢复并处于一致状态。

    从 MySQL 8.0.30 开始,这不再是一个问题;服务器将XA PREPARE实现为一个两阶段操作,它在存储引擎和服务器之间维护准备操作的状态,并在存储引擎和二进制日志之间强制执行执行顺序,以便在服务器节点上的状态在一致和持久之前不进行广播。

    请注意,当使用相同的事务 XID 依次执行 XA 事务并且在处理 XA COMMIT ... ONE PHASE 过程中发生中断时,可能无法再同步二进制日志和存储引擎之间的状态。如果在此事务在存储引擎中准备后发生了刚描述的一系列事件,而 XA COMMIT 语句仍在执行,则可能会发生这种情况。这是一个已知问题。

  • 不支持在 XA 事务中使用复制过滤器或二进制日志过滤器。对表的过滤可能导致副本上的 XA 事务为空,并且不支持空的 XA 事务。此外,副本的连接元数据存储库和应用程序元数据存储库存储在 InnoDB 表中,这在 MySQL 8.0 中成为默认设置,过滤的 XA 事务会改变数据引擎事务的内部状态,并可能与复制事务上下文状态不一致。

    当 XA 事务受到复制过滤器的影响时,无论事务是否为空,都会记录错误 ER_XA_REPLICATION_FILTERS。如果事务不为空,则副本可以继续运行,但您应该采取措施停止使用 XA 事务的复制过滤器以避免潜在问题。如果事务为空,则副本将停止。在这种情况下,副本可能处于一个不确定的状态,其中复制过程的一致性可能会受到损害。特别是,副本的 gtid_executed 集合可能与源端不一致。要解决这种情况,请隔离源端并停止所有复制,然后检查复制拓扑结构中的 GTID 一致性。撤消生成错误消息的 XA 事务,然后重新启动复制。

  • XA 事务被认为对于基于语句的复制是不安全的。如果在源端并行提交了两个 XA 事务,并且在副本上以相反的顺序准备这些事务,可能会发生无法安全解决的锁定依赖关系,并且复制可能会因副本上的死锁而失败。这种情况可能发生在单线程或多线程副本上。当设置 binlog_format=STATEMENT 时,对于 XA 事务中的 DML 语句会发出警告。当设置 binlog_format=MIXEDbinlog_format=ROW 时,XA 事务中的 DML 语句将使用基于行的复制进行记录,潜在问题将不再存在。

15.4 复制语句

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

15.4.1 控制源服务器的 SQL 语句

15.4.2 控制副本服务器的 SQL 语句

15.4.3 控制组复制的 SQL 语句

通过本节描述的语句,可以通过 SQL 接口控制复制。语句分为控制源服务器的组,控制副本服务器的组,以及可应用于任何复制服务器的组。

15.4.1 用于控制源服务器的 SQL 语句

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

15.4.1.1 清除二进制日志语句

15.4.1.2 重置主服务器语句

15.4.1.3 设置 sql_log_bin 语句

本节讨论了用于管理复制源服务器的语句。第 15.4.2 节,“用于控制副本服务器的 SQL 语句”,讨论了用于管理副本服务器的语句。

除了这里描述的语句外,以下SHOW语句用于在复制中与源服务器一起使用。有关这些语句的信息,请参阅第 15.7.7 节,“显示语句”。

  • SHOW BINARY LOGS

  • SHOW BINLOG EVENTS

  • SHOW MASTER STATUS

  • SHOW REPLICAS(或在 MySQL 8.0.22 之前,SHOW SLAVE HOSTS

原文:dev.mysql.com/doc/refman/8.0/en/purge-binary-logs.html

15.4.1.1 PURGE BINARY LOGS Statement

PURGE { BINARY | MASTER } LOGS {
    TO '*log_name*'
  | BEFORE *datetime_expr*
}

二进制日志是一组文件,其中包含 MySQL 服务器进行的数据修改的信息。该日志由一组二进制日志文件和一个索引文件组成(参见 第 7.4.4 节,“二进制日志”)。

PURGE BINARY LOGS 语句删除日志索引文件中指定日志文件名或日期之前列出的所有二进制日志文件。BINARYMASTER 是同义词。删除的日志文件也会从索引文件中记录的列表中移除,因此给定的日志文件将成为列表中的第一个。

运行 PURGE BINARY LOGS 需要 BINLOG_ADMIN 权限。如果服务器没有使用 --log-bin 选项启动以启用二进制日志记录,则此语句无效。

示例:

PURGE BINARY LOGS TO 'mysql-bin.010';
PURGE BINARY LOGS BEFORE '2019-04-02 22:46:26';

BEFORE 变体的 datetime_expr 参数应该评估为一个 DATETIME 值(一个以 '*YYYY-MM-DD hh:mm:ss*' 格式表示的值)。

当副本正在复制时,可以安全地运行 PURGE BINARY LOGS。你不需要停止它们。如果你有一个活动的副本,当前正在读取你尝试删除的日志文件之一,这个语句不会删除正在使用的日志文件或比它更晚的任何日志文件,但会删除任何更早的日志文件。在这种情况下会发出警告消息。然而,如果一个副本没有连接,并且你碰巧清除了它尚未读取的日志文件之一,那么在重新连接后,该副本无法复制。

在实例生效 LOCK INSTANCE FOR BACKUP 语句时,不应该执行 PURGE BINARY LOGS,因为这违反了备份锁的规则,会从服务器中删除文件。从 MySQL 8.0.28 开始,这是不允许的。

要安全地清除二进制日志文件,请按照以下步骤进行:

  1. 在每个副本上,使用 SHOW REPLICA STATUS 来检查它正在读取哪个日志文件。

  2. 使用 SHOW BINARY LOGS 在源上获取二进制日志文件的列表。

  3. 确定所有副本中最早的日志文件。这是目标文件。如果所有副本都是最新的,那么这是列表中的最后一个日志文件。

  4. 备份即将删除的所有日志文件。(此步骤是可选的,但始终建议执行。)

  5. 清除所有日志文件,但不包括目标文件。

PURGE BINARY LOGS TOPURGE BINARY LOGS BEFORE 在二进制日志文件列表中的文件已经通过其他方式(比如在 Linux 上使用rm)被移除系统时会出现错误(Bug #18199, Bug #18453)。为了处理这种错误,需要手动编辑.index文件(这是一个简单的文本文件),确保它只列出实际存在的二进制日志文件,然后重新运行失败的PURGE BINARY LOGS语句。

服务器的二进制日志到期后会自动删除二进制日志文件。文件的删除可以在启动时和二进制日志刷新时进行。默认的二进制日志到期时间为 30 天。您可以使用binlog_expire_logs_seconds系统变量指定替代的到期时间。如果您正在使用复制,应该指定一个到期时间,不低于从源头落后的最长时间。

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

15.4.1.2 RESET MASTER Statement

RESET MASTER [TO *binary_log_file_index_number*]

警告

谨慎使用此语句,以确保不会丢失任何需要的二进制日志文件数据和 GTID 执行历史记录。

RESET MASTER需要RELOAD权限。

对于启用了二进制日志记录的服务器(log_binON),RESET MASTER会删除所有现有的二进制日志文件并重置二进制日志索引文件,将服务器重置为启动二进制日志记录之前的状态。会创建一个新的空二进制日志文件,以便重新启动二进制日志记录。

对于使用 GTIDs 的服务器(gtid_modeON),发出RESET MASTER会重置 GTID 执行历史记录。gtid_purged系统变量的值设置为空字符串(''),gtid_executed系统变量的全局值(但不是会话值)设置为空字符串,并清除mysql.gtid_executed表(参见 mysql.gtid_executed Table)。如果启用了 GTID 的服务器启用了二进制日志记录,RESET MASTER也会像上面描述的那样重置二进制日志。请注意,即使启用了 GTID 的服务器是一个禁用了二进制日志记录的副本,RESET MASTER也是重置 GTID 执行历史记录的方法;RESET REPLICA对 GTID 执行历史记录没有影响。有关重置 GTID 执行历史记录的更多信息,请参阅重置 GTID 执行历史记录。

发出不带可选TO子句的RESET MASTER会删除索引文件中列出的所有二进制日志文件,将二进制日志索引文件重置为空,并创建一个从1开始的新二进制日志文件。在重置后,可以使用可选的TO子句从不是1的数字开始二进制日志文件索引。

确保您使用了合理的索引号值。如果输入了不正确的值,您可以通过发出带有或不带有TO子句的另一个RESET MASTER语句来更正。如果不更正超出范围的值,服务器将无法重新启动。

以下示例演示了TO子句的用法:

RESET MASTER TO 1234;

SHOW BINARY LOGS;
+-------------------+-----------+-----------+
| Log_name          | File_size | Encrypted |
+-------------------+-----------+-----------+
| source-bin.001234 |       154 | No        |
+-------------------+-----------+-----------+

重要提示

不带TO子句的RESET MASTER的效果与PURGE BINARY LOGS的效果有 2 个关键区别:

  1. RESET MASTER会删除索引文件中列出的所有二进制日志文件,只留下一个带有数字后缀.000001的空二进制日志文件,而编号不会被PURGE BINARY LOGS重置。

  2. 当任何副本正在运行时,应使用RESET MASTER。在副本正在运行时使用RESET MASTER的行为是未定义的(因此不受支持),而可以在副本正在运行时安全地使用PURGE BINARY LOGS

另请参阅 Section 15.4.1.1, “PURGE BINARY LOGS Statement”。

在首次设置源和副本时,执行不带TO子句的RESET MASTER可能会很有用,以便您可以验证设置如下:

  1. 启动源和副本,并开始复制(参见 Section 19.1.2, “Setting Up Binary Log File Position Based Replication”)。

  2. 在源上执行几个测试查询。

  3. 检查查询是否已被复制到副本。

  4. 当复制正常运行时,在副本上依次执行STOP REPLICA,然后执行RESET REPLICA,然后验证副本上不存在来自测试查询的不需要的数据。

  5. 从源中删除不需要的数据,然后执行RESET MASTER以清除与其关联的任何二进制日志条目和标识符。

在验证设置、重置源和副本并确保源或副本上没有任何不需要的数据或测试生成的二进制日志文件后,您可以启动副本并开始复制。

原文:dev.mysql.com/doc/refman/8.0/en/set-sql-log-bin.html

15.4.1.3 SET sql_log_bin 语句

SET sql_log_bin = {OFF|ON}

sql_log_bin变量控制当前会话是否启用二进制日志记录(假设二进制日志本身已启用)。默认值为ON。要为当前会话禁用或启用二进制日志记录,请将会话sql_log_bin变量设置为OFFON

将此变量设置为OFF,以便在对源进行更改时暂时禁用二进制日志记录,而这些更改不希望被复制到副本中。

设置此系统变量的会话值是受限制的操作。会话用户必须具有足够的特权来设置受限制的会话变量。请参阅第 7.1.9.1 节,“系统变量特权”。

不可能在事务或子查询中设置sql_log_bin的会话值。

将此变量设置为OFF会阻止新的 GTID 分配给二进制日志中的事务。如果您正在使用 GTID 进行复制,这意味着即使稍后重新启用二进制日志记录,从此时点开始写入日志的 GTID 也不会考虑期间发生的任何事务,因此实际上这些事务将丢失。

mysqldump向使用 GTID 的服务器的转储文件中添加SET @@SESSION.sql_log_bin=0语句,这会在重新加载转储文件时禁用二进制日志记录。该语句阻止生成新的 GTID 并将其分配给执行时的转储文件中的事务,以便使用事务的原始 GTID。

15.4.2 用于控制 REPLICA 服务器的 SQL 语句

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

15.4.2.1 更改主服务器 TO 语句

15.4.2.2 更改复制过滤器语句

15.4.2.3 更改复制源 TO 语句

15.4.2.4 重置 REPLICA 语句

15.4.2.5 重置 SLAVE 语句

15.4.2.6 启动 REPLICA 语句

15.4.2.7 启动 SLAVE 语句

15.4.2.8 停止 REPLICA 语句

15.4.2.9 停止 SLAVE 语句

本节讨论了用于管理 REPLICA 服务器的语句。第 15.4.1 节,“用于控制源服务器的 SQL 语句”,讨论了用于管理源服务器的语句。

除了这里描述的语句外,SHOW REPLICA STATUSSHOW RELAYLOG EVENTS 也与 REPLICA 一起使用。有关这些语句的信息,请参阅 第 15.7.7.35 节,“显示 REPLICA 状态语句” 和 第 15.7.7.32 节,“显示 RELAYLOG 事件语句”。

原文:dev.mysql.com/doc/refman/8.0/en/change-master-to.html

15.4.2.1 更改主服务器语句

CHANGE MASTER TO *option* [, *option*] ... [ *channel_option* ]

*option*: {
    MASTER_BIND = '*interface_name*'
  | MASTER_HOST = '*host_name*'
  | MASTER_USER = '*user_name*'
  | MASTER_PASSWORD = '*password*'
  | MASTER_PORT = *port_num*
  | PRIVILEGE_CHECKS_USER = {'*account*' | NULL}
  | REQUIRE_ROW_FORMAT = {0|1}
  | REQUIRE_TABLE_PRIMARY_KEY_CHECK = {STREAM | ON | OFF}
  | ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS = {OFF | LOCAL | *uuid*}
  | MASTER_LOG_FILE = '*source_log_name*'
  | MASTER_LOG_POS = *source_log_pos*
  | MASTER_AUTO_POSITION = {0|1}
  | RELAY_LOG_FILE = '*relay_log_name*'
  | RELAY_LOG_POS = *relay_log_pos*
  | MASTER_HEARTBEAT_PERIOD = *interval*
  | MASTER_CONNECT_RETRY = *interval*
  | MASTER_RETRY_COUNT = *count*
  | SOURCE_CONNECTION_AUTO_FAILOVER = {0|1}
  | MASTER_DELAY = *interval*
  | MASTER_COMPRESSION_ALGORITHMS = '*algorithm*[,*algorithm*][,*algorithm*]'
  | MASTER_ZSTD_COMPRESSION_LEVEL = *level*
  | MASTER_SSL = {0|1}
  | MASTER_SSL_CA = '*ca_file_name*'
  | MASTER_SSL_CAPATH = '*ca_directory_name*'
  | MASTER_SSL_CERT = '*cert_file_name*'
  | MASTER_SSL_CRL = '*crl_file_name*'
  | MASTER_SSL_CRLPATH = '*crl_directory_name*'
  | MASTER_SSL_KEY = '*key_file_name*'
  | MASTER_SSL_CIPHER = '*cipher_list*'
  | MASTER_SSL_VERIFY_SERVER_CERT = {0|1}
  | MASTER_TLS_VERSION = '*protocol_list*'
  | MASTER_TLS_CIPHERSUITES = '*ciphersuite_list*'
  | MASTER_PUBLIC_KEY_PATH = '*key_file_name*'
  | GET_MASTER_PUBLIC_KEY = {0|1}
  | NETWORK_NAMESPACE = '*namespace*'
  | IGNORE_SERVER_IDS = (*server_id_list*),
  | GTID_ONLY = {0|1}
}

*channel_option*:
    FOR CHANNEL *channel*

*server_id_list*:
    [*server_id* [, *server_id*] ... ]

更改主服务器为更改了副本服务器用于连接到源和从源读取数据的参数。它还更新了复制元数据存储库的内容(参见第 19.2.4 节,“中继日志和复制元数据存储库”)。从 MySQL 8.0.23 开始,请使用更改复制源为代替更改主服务器为,该语句从该版本开始已弃用。在 MySQL 8.0.23 之前的版本中,请使用更改主服务器为

更改主服务器为需要REPLICATION_SLAVE_ADMIN权限(或已弃用的SUPER权限)。

更改主服务器为语句中未指定的选项保留其值,除非在以下讨论中另有说明。因此,在大多数情况下,没有必要指定不更改的选项。

用于SOURCE_HOST和其他更改复制源为选项的值将检查换行符(\n0x0A)字符。这些值中存在这些字符会导致语句失败并显示错误。

可选的FOR CHANNEL *channel*子句使您能够命名语句适用于哪个复制通道。提供FOR CHANNEL *channel*子句将CHANGE MASTER TO语句应用于特定的复制通道,并用于添加新通道或修改现有通道。例如,要添加名为channel2的新通道:

CHANGE MASTER TO MASTER_HOST=host1, MASTER_PORT=3002 FOR CHANNEL 'channel2'

如果未命名任何子句且不存在额外通道,则更改主服务器为语句适用于默认通道,其名称为空字符串("")。当设置了多个复制通道时,每个更改主服务器为语句必须使用FOR CHANNEL *channel*子句命名通道。有关更多信息,请参见第 19.2.2 节,“复制通道”。

对于CHANGE MASTER TO语句的某些选项,您必须在发出CHANGE MASTER TO语句之前(以及之后发出START SLAVE语句)发出STOP SLAVE语句。有时,您只需要停止复制 SQL(应用程序)线程或复制 I/O(接收器)线程,而不是两者同时停止:

  • 当应用程序线程停止时,即使复制接收器线程正在运行,您也可以使用RELAY_LOG_FILERELAY_LOG_POSMASTER_DELAY选项的任何允许组合执行CHANGE MASTER TO。当接收器线程正在运行时,不得使用此语句的其他选项。

  • 当接收器线程停止时,即使应用程序线程正在运行,您也可以使用此语句的任何选项(以任何允许的组合)执行CHANGE MASTER TO除了 RELAY_LOG_FILERELAY_LOG_POSMASTER_DELAYMASTER_AUTO_POSITION = 1

  • 在发出使用MASTER_AUTO_POSITION = 1GTID_ONLY = 1ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONSCHANGE MASTER TO语句之前,必须停止接收线程和应用线程。

您可以使用SHOW SLAVE STATUS检查复制应用程序线程和复制接收器线程的当前状态。请注意,Group Replication 应用程序通道(group_replication_applier)没有接收器线程,只有一个应用程序线程。

CHANGE MASTER TO语句具有许多副作用和交互作用,您应该事先了解:

  • CHANGE MASTER TO导致正在进行的事务的隐式提交。请参阅第 15.3.3 节,“导致隐式提交的语句”。

  • CHANGE MASTER TO会将先前的MASTER_HOSTMASTER_PORTMASTER_LOG_FILEMASTER_LOG_POS值写入错误日志,以及在执行之前有关副本状态的其他信息。

  • 如果您正在使用基于语句的复制和临时表,可能会出现在STOP SLAVE语句后跟随CHANGE MASTER TO语句时在副本上留下临时表的情况。每当发生这种情况时,都会发出警告(ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO)。在这种情况下,您可以通过确保在执行此类CHANGE MASTER TO语句之前,Replica_open_temp_tablesSlave_open_temp_tables系统状态变量的值等于 0 来避免这种情况。

  • 当使用多线程副本(replica_parallel_workers > 0 或 slave_parallel_workers > 0)时,停止副本可能会导致已从中继日志执行的事务序列中存在间隙,无论副本是有意还是无意地停止。当存在这样的间隙时,发出CHANGE MASTER TO将失败。在这种情况下的解决方案是发出START SLAVE UNTIL SQL_AFTER_MTS_GAPS,以确保关闭这些间隙。从 MySQL 8.0.26 开始,在使用基于 GTID 的复制和 GTID 自动定位时,完全跳过检查事务序列中的间隙的过程,因为可以使用 GTID 自动定位解决事务间隙。在这种情况下,仍然可以使用CHANGE MASTER TO

CHANGE MASTER TO语句提供以下选项:

ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS = {OFF | LOCAL | *uuid*}

使复制通道为没有 GTID 的复制事务分配一个 GTID,从而实现从不使用基于 GTID 的复制的源到使用的副本的复制。对于多源副本,您可以同时使用使用ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS和不使用的通道。默认值为OFF,表示未使用该功能。

LOCAL分配一个包括副本自己的 UUID(server_uuid设置)的 GTID。*uuid*分配一个包括指定 UUID 的 GTID,例如用于复制源服务器的server_uuid设置。使用非本地 UUID 可以让您区分在副本上发起的事务和在源上发起的事务,以及对于多源副本,区分在不同源上发起的事务。您选择的 UUID 仅对副本自身的使用具有意义。如果源发送的任何事务已经具有 GTID,则保留该 GTID。

专用于组复制的通道不能使用ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS,但是服务器实例上另一个源的异步复制通道可以这样做。在这种情况下,不要将组复制组名称指定为用于创建 GTIDs 的 UUID。

要将ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS设置为LOCAL*uuid*,副本必须设置gtid_mode=ON,且此后不能更改。此选项用于与基于二进制日志文件位置的复制的源一起使用,因此不能为通道设置MASTER_AUTO_POSITION=1。在设置此选项之前,必须停止复制 SQL 线程和复制 I/O(接收器)线程。

重要提示

在任何通道上设置了ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS的副本都不能晋升为替换复制源服务器的副本,如果需要故障转移,则无法使用从副本获取的备份来恢复复制源服务器。相同的限制也适用于替换或恢复其他使用任何通道上的ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS的副本。

有关更多限制和信息,请参见第 19.1.3.6 节,“从没有 GTIDs 的源复制到具有 GTIDs 的副本”。

GET_MASTER_PUBLIC_KEY = {0|1}

通过从源请求公钥启用基于 RSA 密钥对的密码交换。此选项默认禁用。

此选项适用于使用caching_sha2_password认证插件进行身份验证的副本。对于使用此插件进行身份验证的帐户的连接,源不会发送公钥,除非请求,因此必须请求或在客户端中指定。如果给出MASTER_PUBLIC_KEY_PATH并指定有效的公钥文件,则它优先于GET_MASTER_PUBLIC_KEY。如果您使用使用caching_sha2_password插件进行身份验证的复制用户帐户(从 MySQL 8.0 开始为默认设置),并且不使用安全连接,则必须指定此选项或MASTER_PUBLIC_KEY_PATH选项之一以向副本提供 RSA 公钥。

GTID_ONLY = {0|1}

停止复制通道在复制元数据存储库中持久化文件名和文件位置。GTID_ONLY从 MySQL 8.0.27 版本开始提供。对于异步复制通道,默认情况下禁用GTID_ONLY选项,但对于 Group Replication 通道,默认情况下启用,并且无法禁用。

对于具有此设置的复制通道,仍然会跟踪内存中的文件位置,并且可以通过错误消息和诸如SHOW REPLICA STATUS语句等接口观察文件位置以进行调试(如果过时,则显示为无效)。但是,在 GTID 基础复制实际上不需要它们的情况下,包括事务排队和应用过程中,避免了持久化和检查文件位置所需的写入和读取。

仅当复制 SQL(applier)线程和复制 I/O(receiver)线程都停止时才能使用此选项。要为复制通道设置GTID_ONLY = 1,服务器必须使用 GTID(gtid_mode = ON),并且源必须使用基于行的二进制日志记录(不支持基于语句的复制)。必须为复制通道设置选项REQUIRE_ROW_FORMAT = 1SOURCE_AUTO_POSITION = 1

当设置GTID_ONLY = 1时,如果服务器的replica_parallel_workers=1系统变量设置为零,则副本使用单线程 applier,因此在技术上始终是多线程 applier。这是因为多线程 applier 使用保存的位置而不是复制元数据存储库来定位需要重新应用的事务的起始位置。

如果在设置后禁用GTID_ONLY,则会删除现有的中继日志,并持久化现有的已知二进制日志文件位置,即使它们已过时。复制元数据存储库中的二进制日志和中继日志的文件位置可能无效,如果是这种情况,则会返回警告。只要SOURCE_AUTO_POSITION仍然启用,就会使用 GTID 自动定位来提供正确的定位。

如果还禁用SOURCE_AUTO_POSITION,则在复制元数据存储库中,如果二进制日志和中继日志的文件位置有效,则用于定位。如果它们标记为无效,则必须提供有效的二进制日志文件名和位置(SOURCE_LOG_FILESOURCE_LOG_POS)。如果还提供中继日志文件名和位置(RELAY_LOG_FILERELAY_LOG_POS),则中继日志将被保留,并且应用程序位置将设置为指定位置。GTID 自动跳过确保任何已应用的事务即使最终的应用程序位置不正确也会被跳过。

IGNORE_SERVER_IDS = (*server_id_list*)

使复制品忽略来自指定服务器的事件。该选项接受一个逗号分隔的 0 或多个服务器 ID 的列表。来自服务器的日志轮换和删除事件不会被忽略,并记录在中继日志中。

在循环复制中,原始服务器通常充当其自己事件的终结者,以便它们不会被应用多次。因此,在循环复制中,当圈中的一个服务器被移除时,此选项很有用。假设您有一个带有服务器 ID 1、2、3 和 4 的 4 个服务器的循环复制设置,并且服务器 3 失效。在从服务器 2 开始到服务器 4 的复制中填补间隙时,您可以在服务器 4 上发出的CHANGE MASTER TO语句中包含IGNORE_SERVER_IDS = (3),告诉它使用服务器 2 而不是服务器 3 作为其源。这样做会导致它忽略并不传播任何源自不再使用的服务器的语句。

如果IGNORE_SERVER_IDS包含服务器自己的 ID,并且服务器启动时启用了--replicate-same-server-id选项,则会导致错误。

注意

当全局事务标识符(GTID)用于复制时,已经应用的事务会自动被忽略,因此不需要使用IGNORE_SERVER_IDS功能,该功能已被弃用。如果为服务器设置了gtid_mode=ON,则在CHANGE MASTER TO语句中包含IGNORE_SERVER_IDS选项时会发出弃用警告。

源元数据存储库和SHOW REPLICA STATUS的输出提供了当前被忽略的服务器列表。有关更多信息,请参阅 Section 19.2.4.2, “Replication Metadata Repositories”和 Section 15.7.7.35, “SHOW REPLICA STATUS Statement”。

如果发出不带任何IGNORE_SERVER_IDS选项的CHANGE MASTER TO语句,则会保留任何现有的忽略服务器列表。要清除忽略服务器列表,必须使用带有空列表的选项:

CHANGE MASTER TO IGNORE_SERVER_IDS = ();

RESET REPLICA ALL会清除IGNORE_SERVER_IDS

注意

如果在任何通道已设置带有IGNORE_SERVER_IDS的现有服务器 ID 时发出SET GTID_MODE=ON,则会发出弃用警告。在启动基于 GTID 的复制之前,请检查并清除涉及的服务器上的所有忽略服务器 ID 列表。SHOW REPLICA STATUS语句显示忽略 ID 列表(如果有)。如果收到弃用警告,您仍然可以通过发出包含带有空列表的IGNORE_SERVER_IDS选项的CHANGE MASTER TO语句来清除列表。

MASTER_AUTO_POSITION = {0|1}

使复制品尝试使用基于 GTID 的自动定位功能连接到源,而不是基于二进制日志文件的位置。此选项用于启动使用基于 GTID 的复制的复制品。默认值为 0,表示不使用 GTID 自动定位和基于 GTID 的复制。只有在停止复制 SQL(应用程序)线程和复制 I/O(接收器)线程时,才能将此选项与CHANGE MASTER TO一起使用。

复制品和源都必须启用 GTIDs(GTID_MODE=ONON_PERMISSIVEOFF_PERMISSIVE在复制品上,以及源上的GTID_MODE=ON)。MASTER_LOG_FILEMASTER_LOG_POSRELAY_LOG_FILERELAY_LOG_POS不能与MASTER_AUTO_POSITION = 1一起指定。如果在复制品上启用了多源复制,您需要为每个适用的复制通道设置MASTER_AUTO_POSITION = 1选项。

设置MASTER_AUTO_POSITION = 1后,在初始连接握手中,复制品发送包含其已接收、已提交或两者都有的事务的 GTID 集。源会响应通过发送其二进制日志中的所有事务,其 GTID 未包含在复制品发送的 GTID 集中。此交换确保源仅发送复制品尚未记录或提交的具有 GTID 的事务。如果复制品从多个源接收事务,如钻石拓扑结构的情况下,自动跳过功能确保不会应用事务两次。有关复制品发送的 GTID 集如何计算的详细信息,请参见 Section 19.1.3.3, “GTID Auto-Positioning”。

如果应该由源发送的任何事务已从源的二进制日志中清除,或者通过其他方法添加到gtid_purged系统变量的 GTID 集合中,源将向副本发送错误ER_MASTER_HAS_PURGED_REQUIRED_GTIDS,并且复制不会启动。缺失的已清除事务的 GTID 将在源的错误日志中被识别并列在警告消息ER_FOUND_MISSING_GTIDS中。此外,如果在交换事务期间发现副本已记录或提交具有 GTID 中源 UUID 的事务,但源本身尚未提交它们,则源将向副本发送错误ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER,并且复制不会启动。有关如何处理这些情况的信息,请参见第 19.1.3.3 节,“GTID 自动定位”。

您可以通过检查 Performance Schema replication_connection_status表或SHOW REPLICA STATUS的输出来查看是否启用了 GTID 自动定位的复制正在运行。再次禁用MASTER_AUTO_POSITION选项会使副本恢复到基于文件的复制。

MASTER_BIND = '*interface_name*'

确定副本的哪个网络接口被选择用于连接到源,用于具有多个网络接口的副本。指定网络接口的 IP 地址。字符串值的最大长度为 255 个字符。

配置此选项的 IP 地址(如果有)可以在SHOW REPLICA STATUS输出的Master_Bind列中看到。在源元数据存储库表mysql.slave_master_info中,该值可以在Master_bind列中看到。将副本绑定到特定网络接口的能力也受到 NDB 集群的支持。

MASTER_COMPRESSION_ALGORITHMS = '*algorithm*[,*algorithm*][,*algorithm*]'

指定连接到复制源服务器的允许压缩算法中的一个、两个或三个,用逗号分隔。字符串值的最大长度为 99 个字符。默认值为uncompressed

可用的算法是zlibzstduncompressed,与protocol_compression_algorithms系统变量相同。算法可以以任何顺序指定,但这不是优先顺序 - 算法协商过程尝试使用zlib,然后zstd,然后uncompressed,如果它们被指定。MASTER_COMPRESSION_ALGORITHMS自 MySQL 8.0.18 起可用。

MASTER_COMPRESSION_ALGORITHMS的值仅在replica_compressed_protocolslave_compressed_protocol系统变量被禁用时适用。如果replica_compressed_protocolslave_compressed_protocol被启用,则优先于MASTER_COMPRESSION_ALGORITHMS,并且如果源和副本都支持该算法,则连接到源使用zlib压缩。有关更多信息,请参见第 6.2.8 节,“连接压缩控制”。

二进制日志事务压缩(自 MySQL 8.0.20 起可用),由binlog_transaction_compression系统变量激活,也可用于节省带宽。如果与连接压缩结合使用,则连接压缩的机会减少作用于数据,但仍可以压缩标头以及未压缩的事件和事务有效载荷。有关二进制日志事务压缩的更多信息,请参见第 7.4.4.5 节,“二进制日志事务压缩”。

MASTER_CONNECT_RETRY = *interval*

指定副本在连接到源超时后进行重新连接尝试之间的秒数间隔。默认间隔为 60 秒。

尝试次数受MASTER_RETRY_COUNT选项限制。如果使用默认设置,副本在重新连接尝试之间等待 60 秒(MASTER_CONNECT_RETRY=60),并以这个速率继续尝试重新连接 60 天(MASTER_RETRY_COUNT=86400)。这些值记录在源元数据存储库中,并显示在replication_connection_configuration性能模式表中。

MASTER_DELAY = *interval*

指定副本必须滞后源多少秒。从源接收的事件要在至少interval秒后才会在副本上执行。interval必须是一个非负整数,范围从 0 到 2³¹−1。默认值为 0。更多信息,请参阅 Section 19.4.11, “Delayed Replication”。

当复制 SQL 线程停止时,可以在运行中的副本上执行使用MASTER_DELAY选项的CHANGE MASTER TO语句。

MASTER_HEARTBEAT_PERIOD = *interval*

控制心跳间隔,防止在连接仍然良好的情况下发生连接超时。在经过那么多秒后,会向副本发送一个心跳信号,并且每当源的二进制日志更新为一个事件时,等待时间就会被重置。因此,只有在二进制日志文件中有一段时间没有发送的事件时,源才会发送心跳。

心跳间隔interval是一个十进制值,范围为 0 到 4294967 秒,分辨率为毫秒;最小的非零值为 0.001。将interval设置为 0 会完全禁用心跳。心跳间隔默认为replica_net_timeoutslave_net_timeout系统变量值的一半。它记录在源元数据存储库中,并显示在replication_connection_configuration性能模式表中。

系统变量replica_net_timeout(从 MySQL 8.0.26 开始)或slave_net_timeout(在 MySQL 8.0.26 之前)指定了复制等待源端发送更多数据或心跳信号的秒数,超过这个时间,复制将认为连接已断开,中止读取,并尝试重新连接。默认值为 60 秒(一分钟)。请注意,对replica_net_timeoutslave_net_timeout的值或默认设置的更改不会自动更改心跳间隔,无论是显式设置还是使用先前计算的默认值。如果将全局值replica_net_timeoutslave_net_timeout设置为小于当前心跳间隔的值,将发出警告。如果更改了replica_net_timeoutslave_net_timeout,还必须发出CHANGE MASTER TO以将心跳间隔调整为适当的值,以便在连接超时之前发生心跳信号。如果不这样做,心跳信号将不起作用,如果没有从源端接收到数据,复制可能会进行重复的重新连接尝试,创建僵尸转储线程。

MASTER_HOST = '*host_name*'

复制源服务器的主机名或 IP 地址。复制使用此信息连接到源端。字符串值的最大长度为 255 个字符。在 MySQL 8.0.17 之前为 60 个字符。

如果指定了MASTER_HOSTMASTER_PORT,则复制将假定源服务器与以前不同(即使选项值与当前值相同)。在这种情况下,源端二进制日志文件名和位置的旧值被认为不再适用,因此如果在语句中不指定MASTER_LOG_FILEMASTER_LOG_POS,则会自动附加MASTER_LOG_FILE=''MASTER_LOG_POS=4

MASTER_HOST=''设置为(即将其值显式设置为空字符串)等同于根本不设置MASTER_HOST。尝试将MASTER_HOST设置为空字符串将导致错误。

MASTER_LOG_FILE = '*source_log_name*'MASTER_LOG_POS = *source_log_pos*

二进制日志文件名以及复制 I/O(接收器)线程从源端二进制日志中读取的位置,下次线程启动时开始读取。如果使用基于二进制日志文件位置的复制,请指定这些选项。

MASTER_LOG_FILE必须包括源服务器上可用的特定二进制日志文件的数字后缀,例如,MASTER_LOG_FILE='binlog.000145'。字符串值的最大长度为 511 个字符。

MASTER_LOG_POS是副本在该文件中开始读取的数字位置。MASTER_LOG_POS=4表示二进制日志文件中事件的开始。

如果指定了MASTER_LOG_FILEMASTER_LOG_POS中的任一项,则不能指定MASTER_AUTO_POSITION = 1,该选项用于基于 GTID 的复制。

如果未指定MASTER_LOG_FILEMASTER_LOG_POS中的任一项,则副本使用复制 SQL(应用程序)线程在发出CHANGE MASTER TO之前的最后坐标。这确保即使复制 SQL(应用程序)线程相对于复制 I/O(接收器)线程较晚,也不会在复制中出现不连续。

MASTER_PASSWORD = '*password*'

用于连接到复制源服务器的复制用户帐户的密码。字符串值的最大长度为 32 个字符。如果指定了MASTER_PASSWORD,则还需要MASTER_USER

CHANGE MASTER TO语句中用于复制用户帐户的密码长度限制为 32 个字符。尝试使用超过 32 个字符的密码会导致CHANGE MASTER TO失败。

密码在 MySQL 服务器的日志、性能模式表和SHOW PROCESSLIST语句中被掩码。

MASTER_PORT = *port_num*

副本用于连接到复制源服务器的 TCP/IP 端口号。

注意

复制不能使用 Unix 套接字文件。您必须能够使用 TCP/IP 连接到复制源服务器。

如果指定了MASTER_HOSTMASTER_PORT,则副本会假定源服务器与之前不同(即使选项值与当前值相同)。在这种情况下,源二进制日志文件名和位置的旧值被视为不再适用,因此如果在语句中未指定MASTER_LOG_FILEMASTER_LOG_POS,则会自动附加MASTER_LOG_FILE=''MASTER_LOG_POS=4

MASTER_PUBLIC_KEY_PATH = '*key_file_name*'

通过提供包含源端所需的公钥副本的文件的路径名,启用基于 RSA 密钥对的密码交换。该文件必须采用 PEM 格式。字符串值的最大长度为 511 个字符。

此选项适用于使用sha256_passwordcaching_sha2_password认证插件进行身份验证的复制品。(对于sha256_password,只有在使用 OpenSSL 构建 MySQL 时才能使用MASTER_PUBLIC_KEY_PATH。)如果您使用的是使用caching_sha2_password插件进行身份验证的复制用户帐户(这是 MySQL 8.0 的默认设置),并且您没有使用安全连接,则必须指定此选项或GET_MASTER_PUBLIC_KEY=1选项以向复制品提供 RSA 公钥。

MASTER_RETRY_COUNT = *count*

设置复制品在连接到源之后超时的最大重连尝试次数,由replica_net_timeoutslave_net_timeout系统变量确定。如果复制品确实需要重新连接,则第一次重试会在超时后立即发生。默认值为 86400 次尝试。

尝试之间的间隔由MASTER_CONNECT_RETRY选项指定。如果使用默认设置,复制品在重新连接尝试之间等待 60 秒(MASTER_CONNECT_RETRY=60),并以此速率继续尝试重新连接 60 天(MASTER_RETRY_COUNT=86400)。将MASTER_RETRY_COUNT设置为 0 意味着没有限制重新连接尝试次数,因此复制品将无限尝试重新连接。

MASTER_CONNECT_RETRYMASTER_RETRY_COUNT的值记录在源元数据存储库中,并显示在replication_connection_configuration性能模式表中。MASTER_RETRY_COUNT取代了--master-retry-count服务器启动选项。

MASTER_SSL = {0|1}

指定复制品是否加密复制连接。默认值为 0,表示复制品不加密复制连接。如果设置MASTER_SSL=1,则可以使用MASTER_SSL_*xxx*MASTER_TLS_*xxx*选项配置加密。

为复制连接设置MASTER_SSL=1,然后不设置更多的MASTER_SSL_*xxx*选项相当于为客户端设置--ssl-mode=REQUIRED,如加密连接的命令选项中所述。使用MASTER_SSL=1,连接尝试仅在可以建立加密连接时成功。复制连接不会退回到未加密连接,因此没有与复制相对应的--ssl-mode=PREFERRED设置。如果设置了MASTER_SSL=0,则相当于--ssl-mode=DISABLED

重要

为了帮助防止复杂的中间人攻击,重要的是副本要验证服务器的身份。您可以指定额外的MASTER_SSL_*xxx*选项,对应设置--ssl-mode=VERIFY_CA--ssl-mode=VERIFY_IDENTITY,这比默认设置更好,有助于防止这种类型的攻击。使用这些设置,副本会检查服务器的证书是否有效,并检查副本正在使用的主机名是否与服务器证书中的身份匹配。要实施这些验证级别之一,必须首先确保服务器的 CA 证书可靠地可用于副本,否则会导致可用性问题。因此,它们不是默认设置。

MASTER_SSL_*xxx*MASTER_TLS_*xxx*

指定副本如何使用加密和密码来保护复制连接。即使在没有 SSL 支持的副本上也可以更改这些选项。它们保存在源元数据存储库中,但如果副本没有启用 SSL 支持,则会被忽略。字符串值的MASTER_SSL_*xxx*MASTER_TLS_*xxx*选项的值的最大长度为 511 个字符,MASTER_TLS_CIPHERSUITES除外,其长度为 4000 个字符。

MASTER_SSL_*xxx*MASTER_TLS_*xxx*选项执行与加密连接的命令选项中描述的--ssl-*xxx*--tls-*xxx*客户端选项相同的功能。两组选项之间的对应关系,以及使用MASTER_SSL_*xxx*MASTER_TLS_*xxx*选项建立安全连接的方法,在第 19.3.1 节,“设置复制使用加密连接”中有解释。

MASTER_USER = '*user_name*'

用于连接到复制源服务器的复制用户帐户的用户名。字符串值的最大长度为 96 个字符。

对于组复制,此帐户必须存在于复制组的每个成员中。如果 XCom 通信堆栈用于组,则用于分布式恢复,如果 MySQL 通信堆栈用于组,则用于组通信连接。对于 MySQL 通信堆栈,帐户必须具有GROUP_REPLICATION_STREAM权限。

可以通过指定MASTER_USER=''来设置空用户名称,但不能使用空用户名称启动复制通道。在 MySQL 8.0.21 之前的版本中,只有在出于安全目的需要清除先前使用的凭据时才设置空的MASTER_USER用户名称。不要在之后使用通道,因为在这些版本中存在一个错误,如果从存储库中读取到空用户名称(例如,在自动重新启动组复制通道期间),则可能会替换默认用户名称。从 MySQL 8.0.21 开始,可以设置空的MASTER_USER用户名称,并在之后使用通道,只要始终使用START REPLICA语句或START GROUP_REPLICATION语句提供用户凭据启动复制通道。这种方法意味着复制通道始终需要操作员干预才能重新启动,但用户凭据不会记录在复制元数据存储库中。

重要

要使用使用caching_sha2_password插件进行身份验证的复制用户帐户连接到源,必须设置安全连接,如 Section 19.3.1, “Setting Up Replication to Use Encrypted Connections”中所述,或启用不加密的连接以支持使用 RSA 密钥对进行密码交换。caching_sha2_password身份验证插件是从 MySQL 8.0 开始新用户的默认插件(有关详细信息,请参见 Section 8.4.1.2, “Caching SHA-2 Pluggable Authentication”)。如果您创建或使用用于复制的用户帐户使用此身份验证插件,并且未使用安全连接,则必须启用基于 RSA 密钥对的密码交换才能成功连接。您可以使用此语句的MASTER_PUBLIC_KEY_PATH选项或GET_MASTER_PUBLIC_KEY=1选项来执行此操作。

MASTER_ZSTD_COMPRESSION_LEVEL = *level*

用于使用zstd压缩算法的与复制源服务器连接的压缩级别。允许的级别为 1 到 22,较大的值表示较高级别的压缩。默认级别为 3。MASTER_ZSTD_COMPRESSION_LEVEL自 MySQL 8.0.18 起可用。

压缩级别设置对不使用zstd压缩的连接没有影响。有关更多信息,请参见 Section 6.2.8, “Connection Compression Control”。

NETWORK_NAMESPACE = '*namespace*'

用于 TCP/IP 连接到复制源服务器或者如果使用 MySQL 通信堆栈,则用于 Group Replication 的组通信连接的网络命名空间。字符串值的最大长度为 64 个字符。如果省略此选项,则副本的连接将使用默认(全局)命名空间。在不实现网络命名空间支持的平台上,当副本尝试连接到源时会发生故障。有关网络命名空间的信息,请参见 第 7.1.14 节,“网络命名空间支持”。NETWORK_NAMESPACE 自 MySQL 8.0.22 版本起可用。

PRIVILEGE_CHECKS_USER = {NULL | '*account*'}

为指定通道提供安全上下文的用户帐户名称。NULL 是默认值,表示不使用安全上下文。PRIVILEGE_CHECKS_USER 自 MySQL 8.0.18 版本起可用。

用户帐户的用户名和主机名必须遵循 第 8.2.4 节,“指定帐户名称” 中描述的语法,并且用户不能是匿名用户(具有空白用户名)或 CURRENT_USER。该帐户必须具有 REPLICATION_APPLIER 权限,以及执行在通道上复制的事务所需的权限。有关帐户所需权限的详细信息,请参见 第 19.3.3 节,“复制权限检查”。重新启动复制通道时,权限检查将从那一点开始应用。如果您不指定通道且没有其他通道存在,则该语句将应用于默认通道。

当设置了 PRIVILEGE_CHECKS_USER 时,强烈建议使用基于行的二进制日志记录,并且您可以设置 REQUIRE_ROW_FORMAT 来强制执行此操作。例如,要在运行中的副本上启动通道 channel_1 上的权限检查,请执行以下语句:

mysql> STOP REPLICA FOR CHANNEL 'channel_1';
mysql> CHANGE MASTER TO
         PRIVILEGE_CHECKS_USER = '*priv_repl*'@'*%.example.com*',
         REQUIRE_ROW_FORMAT = 1,
         FOR CHANNEL 'channel_1';
mysql> START REPLICA FOR CHANNEL 'channel_1';

对于 MySQL 8.0.22 版本及更高版本,请使用 START REPLICASTOP REPLICA,对于 MySQL 8.0.22 版本之前的版本,请使用 START SLAVESTOP SLAVE。这些语句的工作方式相同,只是术语发生了变化。

RELAY_LOG_FILE = '*relay_log_file*'RELAY_LOG_POS = '*relay_log_pos*'

中继日志文件名以及在该文件中的位置,复制 SQL 线程在下次启动时从副本的中继日志中开始读取的位置。RELAY_LOG_FILE 可以使用绝对路径或相对路径,并且使用与 MASTER_LOG_FILE 相同的基本名称。字符串值的最大长度为 511 个字符。

在运行副本时,可以在停止复制 SQL 线程时执行使用RELAY_LOG_FILERELAY_LOG_POS或两个选项的CHANGE MASTER TO语句。如果至少有一个复制 SQL(应用程序)线程和复制 I/O(接收器)线程正在运行,则保留中继日志。如果两个线程都停止,则除非至少指定RELAY_LOG_FILERELAY_LOG_POS中的一个,否则所有中继日志文件都将被删除。对于仅具有应用程序线程而没有接收器线程的 Group Replication 应用程序通道(group_replication_applier),如果应用程序线程停止,那么这种情况就是,但是对于该通道,您不能使用RELAY_LOG_FILERELAY_LOG_POS选项。

REQUIRE_ROW_FORMAT = {0|1}

仅允许通过复制通道处理基于行的复制事件。此选项防止复制应用程序执行诸如创建临时表和执行LOAD DATA INFILE请求等操作,从而增加通道的安全性。默认情况下,异步复制通道禁用REQUIRE_ROW_FORMAT选项,但默认情况下启用 Group Replication 通道,并且不能禁用它们。有关更多信息,请参见第 19.3.3 节,“复制权限检查”。REQUIRE_ROW_FORMAT自 MySQL 8.0.19 起可用。

REQUIRE_TABLE_PRIMARY_KEY_CHECK = {STREAM | ON | OFF}

启用副本选择自己的主键检查策略。默认值为STREAMREQUIRE_TABLE_PRIMARY_KEY_CHECK自 MySQL 8.0.20 起可用。

当为复制通道设置选项为ON时,副本始终在复制操作中使用sql_require_primary_key系统变量的值为ON,需要主键。当选项设置为OFF时,副本始终在复制操作中使用sql_require_primary_key系统变量的值为OFF,因此即使源需要主键,也不需要主键。当REQUIRE_TABLE_PRIMARY_KEY_CHECK选项设置为STREAM时,这是默认值,副本使用从源复制的每个事务的任何值。

对于多源复制,将REQUIRE_TABLE_PRIMARY_KEY_CHECK设置为ONOFF使副本能够规范不同源的复制通道的行为,并保持sql_require_primary_key系统变量的一致设置。使用ON可以防止在多个源更新相同的表集时意外丢失主键。使用OFF允许可以操作主键的源与不能操作主键的源一起工作。

当设置PRIVILEGE_CHECKS_USER时,将REQUIRE_TABLE_PRIMARY_KEY_CHECK设置为ONOFF意味着用户帐户无需会话管理级别权限即可设置受限制的会话变量,这些变量是必需的,以便更改sql_require_primary_key的值以匹配每个事务的源设置。有关更多信息,请参见第 19.3.3 节,“复制权限检查”。

SOURCE_CONNECTION_AUTO_FAILOVER = {0|1}

如果有一个或多个备用复制源服务器可用(即有多个共享复制数据的 MySQL 服务器或服务器组),则为复制通道激活异步连接故障转移机制。SOURCE_CONNECTION_AUTO_FAILOVER从 MySQL 8.0.22 版本开始可用。默认值为 0,表示未激活该机制。有关完整信息和设置此功能的说明,请参见第 19.4.9.2 节,“副本的异步连接故障转移”。

异步连接故障转移机制在由MASTER_CONNECT_RETRYMASTER_RETRY_COUNT控制的重连尝试耗尽后接管。它会将副本重新连接到一个从指定源列表中选择的备用源,您可以使用asynchronous_connection_failover_add_sourceasynchronous_connection_failover_delete_source函数来管理这些源。要添加和删除受管理的服务器组,请改用asynchronous_connection_failover_add_managedasynchronous_connection_failover_delete_managed函数。有关更多信息,请参见第 19.4.9 节,“使用异步连接故障转移切换源和副本”。

重要提示

  1. 只有在使用 GTID 自动定位(MASTER_AUTO_POSITION = 1)时才能设置SOURCE_CONNECTION_AUTO_FAILOVER = 1

  2. 当你设置SOURCE_CONNECTION_AUTO_FAILOVER = 1时,将MASTER_RETRY_COUNTMASTER_CONNECT_RETRY设置为最小值,只允许在短时间内对同一源进行几次重试尝试,以防连接故障是由瞬时网络中断引起的。否则,异步连接故障转移机制无法及时激活。适当的值为MASTER_RETRY_COUNT=3MASTER_CONNECT_RETRY=10,这使得副本在每次连接尝试之间间隔 10 秒的情况下重试连接 3 次。

  3. 当你设置SOURCE_CONNECTION_AUTO_FAILOVER = 1时,复制元数据存储库必须包含用于连接到复制通道源列表上的所有服务器的复制用户帐户的凭据。可以使用带有MASTER_USERMASTER_PASSWORD选项的CHANGE REPLICATION SOURCE TO语句来设置这些凭据。更多信息,请参见第 19.4.9 节,“使用异步连接故障转移切换源和副本”。

  4. 从 MySQL 8.0.27 开始,当你设置SOURCE_CONNECTION_AUTO_FAILOVER = 1时,如果此复制通道位于单主模式中的组复制主服务器上,则自动激活副本的异步连接故障转移。启用此功能后,如果正在复制的主服务器下线或进入错误状态,新的主服务器在选举时会在同一通道上开始复制。如果要使用此功能,此复制通道还必须在复制组中的所有辅助服务器上设置,并在任何新加入的成员上设置。(如果使用 MySQL 的克隆功能进行服务器配置,则所有这些都会自动完成。)如果不想使用此功能,请使用group_replication_disable_member_action函数来禁用默认启用的 Group Replication 成员操作mysql_start_failover_channels_if_primary。更多信息,请参见第 19.4.9.2 节,“副本的异步连接故障转移”。

示例

当您拥有源的快照并记录了与快照时间对应的源二进制日志坐标时,CHANGE MASTER TO对于设置副本非常有用。在将快照加载到副本以与源同步之后,您可以在副本上运行CHANGE MASTER TO MASTER_LOG_FILE='*log_name*', MASTER_LOG_POS=*log_pos*来指定副本应开始读取源二进制日志的坐标。以下示例更改了副本使用的源服务器,并建立了副本开始读取源二进制日志的源二进制日志坐标:

CHANGE MASTER TO
  MASTER_HOST='source2.example.com',
  MASTER_USER='replication',
  MASTER_PASSWORD='*password*',
  MASTER_PORT=3306,
  MASTER_LOG_FILE='source2-bin.001',
  MASTER_LOG_POS=4,
  MASTER_CONNECT_RETRY=10;

要在故障切换期间将现有副本切换到新源的过程,请参见第 19.4.8 节,“故障切换期间切换源”。

当源和副本上使用 GTID 时,请指定 GTID 自动定位,而不是给出二进制日志文件位置,如下例所示。有关在新服务器、已停止服务器、在线服务器或其他副本上配置和启动基于 GTID 的复制的完整说明,请参见第 19.1.3 节,“具有全局事务标识符的复制”。

CHANGE MASTER TO
  MASTER_HOST='source3.example.com',
  MASTER_USER='replication',
  MASTER_PASSWORD='*password*',
  MASTER_PORT=3306,
  MASTER_AUTO_POSITION = 1,
  FOR CHANNEL "source_3";

在此示例中,使用了多源复制,并且CHANGE MASTER TO语句应用于连接副本与指定主机的复制通道"source_3"。有关设置多源复制的指导,请参见第 19.1.5 节,“MySQL 多源复制”。

下一个示例显示了如何使副本应用要重复的中继日志文件中的事务。为此,源不需要可达。您可以使用CHANGE MASTER TO来定位副本应开始重新应用事务的中继日志位置,然后启动 SQL 线程:

CHANGE MASTER TO
  RELAY_LOG_FILE='replica-relay-bin.006',
  RELAY_LOG_POS=4025;
START SLAVE SQL_THREAD;

CHANGE MASTER TO也可用于跳过导致复制停止的二进制日志中的事务。执行此操作的适当方法取决于是否使用 GTID。有关使用CHANGE MASTER TO或其他方法跳过事务的说明,请参见第 19.1.7.3 节,“跳过事务”。

原文:dev.mysql.com/doc/refman/8.0/en/change-replication-filter.html

15.4.2.2 CHANGE REPLICATION FILTER Statement

CHANGE REPLICATION FILTER *filter*[, *filter*]
	[, ...] [FOR CHANNEL *channel*]

*filter*: {
    REPLICATE_DO_DB = (*db_list*)
  | REPLICATE_IGNORE_DB = (*db_list*)
  | REPLICATE_DO_TABLE = (*tbl_list*)
  | REPLICATE_IGNORE_TABLE = (*tbl_list*)
  | REPLICATE_WILD_DO_TABLE = (*wild_tbl_list*)
  | REPLICATE_WILD_IGNORE_TABLE = (*wild_tbl_list*)
  | REPLICATE_REWRITE_DB = (*db_pair_list*)
}

*db_list*:
    *db_name*[, *db_name*][, ...]

*tbl_list*:
    *db_name.table_name*[, *db_name.table_name*][, ...]
*wild_tbl_list*:
    '*db_pattern.table_pattern*'[, '*db_pattern.table_pattern*'][, ...]

*db_pair_list*:
    (*db_pair*)[, (*db_pair*)][, ...]

*db_pair*:
    *from_db*, *to_db*

CHANGE REPLICATION FILTER设置一个或多个复制过滤规则,与使用复制过滤选项(如--replicate-do-db--replicate-wild-ignore-table的方式相同。使用此语句设置的过滤器与使用服务器选项设置的过滤器在两个关键方面有所不同:

  1. 该语句不需要重新启动服务器才能生效,只需首先停止复制 SQL 线程使用STOP REPLICA SQL_THREAD(然后使用START REPLICA SQL_THREAD重新启动)。

  2. 该语句的效果不是持久的;使用CHANGE REPLICATION FILTER设置的任何过滤器在重启复制mysqld后会丢失。

CHANGE REPLICATION FILTER需要REPLICATION_SLAVE_ADMIN权限(或已弃用的SUPER权限)。

使用FOR CHANNEL *channel*子句使复制过滤器特定于复制通道,例如在多源副本上。未附加特定FOR CHANNEL子句应用的过滤器被视为全局过滤器,这意味着它们适用于所有复制通道。

注意

不能在配置为组复制的 MySQL 服务器实例上设置全局复制过滤器,因为在某些服务器上过滤事务会使组无法达成一致状态。可以在未直接涉及组复制的复制通道上设置特定通道的复制过滤器,例如,其中一个组成员同时充当到组外源的副本。不能在group_replication_appliergroup_replication_recovery通道上设置。

以下列表显示了CHANGE REPLICATION FILTER选项及其与--replicate-*服务器选项的关系:

  • REPLICATE_DO_DB: 根据数据库名称包含更新。等同于--replicate-do-db

  • REPLICATE_IGNORE_DB: 根据数据库名称排除更新。等同于--replicate-ignore-db

  • REPLICATE_DO_TABLE: 根据表名包含更新。等同于--replicate-do-table

  • REPLICATE_IGNORE_TABLE: 根据表名排除更新。等同于--replicate-ignore-table

  • REPLICATE_WILD_DO_TABLE: 根据通配符模式匹配表名包含更新。等同于--replicate-wild-do-table

  • REPLICATE_WILD_IGNORE_TABLE: 根据通配符模式匹配表名排除更新。等同于--replicate-wild-ignore-table

  • REPLICATE_REWRITE_DB: 在源端替换指定数据库的名称后,在副本上执行更新。等同于--replicate-rewrite-db

REPLICATE_DO_DBREPLICATE_IGNORE_DB过滤器的确切效果取决于是基于语句还是基于行的复制。有关更多信息,请参见第 19.2.5 节,“服务器如何评估复制过滤规则”。

可以通过在单个CHANGE REPLICATION FILTER语句中用逗号分隔规则来创建多个复制过滤规则,如下所示:

CHANGE REPLICATION FILTER
    REPLICATE_DO_DB = (d1), REPLICATE_IGNORE_DB = (d2);

发出刚才显示的语句等同于使用选项--replicate-do-db=d1 --replicate-ignore-db=d2启动副本mysqld

在使用多个复制通道处理来自不同源的事务的多源复制中,使用FOR CHANNEL *channel*子句在复制通道上设置复制过滤器:

CHANGE REPLICATION FILTER REPLICATE_DO_DB = (d1) FOR CHANNEL channel_1;

这使您可以创建特定通道的复制过滤器,以从源中筛选出选定的数据。提供FOR CHANNEL子句时,复制过滤器语句将作用于该复制通道,删除具有与指定复制过滤器相同过滤器类型的任何现有复制过滤器,并用指定的过滤器替换它们。未在语句中明确列出的过滤器类型不会被修改。如果针对未配置的复制通道发出该语句,则该语句将失败,并显示 ER_SLAVE_CONFIGURATION 错误。如果针对 Group Replication 通道发出该语句,则该语句将失败,并显示 ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED 错误。

在配置了多个复制通道的副本中,发出不带FOR CHANNEL子句的CHANGE REPLICATION FILTER会为每个配置的复制通道以及全局复制过滤器配置复制过滤器。对于每种过滤器类型,如果语句中列出了过滤器类型,则该类型的任何现有过滤规则都将被最近发出的语句中指定的过滤规则替换,否则过滤器类型的旧值将被保留。有关更多信息,请参见 Section 19.2.5.4, “Replication Channel Based Filters”。

如果同一过滤规则被指定多次,则实际上只使用最后一条规则。例如,这里显示的两个语句具有完全相同的效果,因为第一个语句中的第一条REPLICATE_DO_DB规则被忽略:

CHANGE REPLICATION FILTER
    REPLICATE_DO_DB = (db1, db2), REPLICATE_DO_DB = (db3, db4);

CHANGE REPLICATION FILTER
    REPLICATE_DO_DB = (db3, db4);

注意

这种行为与--replicate-*过滤选项的行为不同,其中多次指定相同选项会导致创建多个过滤规则。

不包含任何特殊字符的表和数据库名称无需加引号。与REPLICATION_WILD_TABLEREPLICATION_WILD_IGNORE_TABLE一起使用的值是字符串表达式,可能包含(特殊)通配符字符,因此必须加引号。以下示例语句中显示了这一点:

CHANGE REPLICATION FILTER
    REPLICATE_WILD_DO_TABLE = ('db1.old%');

CHANGE REPLICATION FILTER
    REPLICATE_WILD_IGNORE_TABLE = ('db1.new%', 'db2.new%');

REPLICATE_REWRITE_DB一起使用的值表示数据库名称的;每个这样的值必须用括号括起来。以下语句将源数据库db1上发生的语句重写为副本数据库db2

CHANGE REPLICATION FILTER REPLICATE_REWRITE_DB = ((db1, db2));

刚刚显示的语句包含两组括号,一组括住数据库名称的对,另一组括住整个列表。这在下面的示例中可能更容易看到,该示例创建了两个rewrite-db规则,一个将数据库dbA重写为dbB,另一个将数据库dbC重写为dbD

CHANGE REPLICATION FILTER
  REPLICATE_REWRITE_DB = ((dbA, dbB), (dbC, dbD));

CHANGE REPLICATION FILTER语句仅替换受语句影响的过滤类型和复制通道的复制过滤规则,而保持其他规则和通道不变。如果要取消给定类型的所有过滤器,请将过滤器的值设置为显式空列表,如此示例所示,它删除了所有现有的REPLICATE_DO_DBREPLICATE_IGNORE_DB规则:

CHANGE REPLICATION FILTER
    REPLICATE_DO_DB = (), REPLICATE_IGNORE_DB = ();

以这种方式将过滤器设置为空会删除所有现有规则,不会创建任何新规则,并且不会恢复在启动时使用--replicate-*选项在命令行或配置文件中设置的任何规则。

RESET REPLICA ALL语句会移除在被语句删除的通道上设置的特定通道复制过滤器。当被删除的通道或通道重新创建时,任何为副本指定的全局复制过滤器都会被复制到它们,而不会应用任何特定通道的复制过滤器。

欲了解更多信息,请参阅第 19.2.5 节,“服务器如何评估复制过滤规则”。

原文:dev.mysql.com/doc/refman/8.0/en/change-replication-source-to.html

15.4.2.3 CHANGE REPLICATION SOURCE TO 语句

CHANGE REPLICATION SOURCE TO *option* [, *option*] ... [ *channel_option* ]

*option*: {
    SOURCE_BIND = '*interface_name*'
  | SOURCE_HOST = '*host_name*'
  | SOURCE_USER = '*user_name*'
  | SOURCE_PASSWORD = '*password*'
  | SOURCE_PORT = *port_num*
  | PRIVILEGE_CHECKS_USER = {NULL | '*account*'}
  | REQUIRE_ROW_FORMAT = {0|1}
  | REQUIRE_TABLE_PRIMARY_KEY_CHECK = {STREAM | ON | OFF | GENERATE}
  | ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS = {OFF | LOCAL | *uuid*}
  | SOURCE_LOG_FILE = '*source_log_name*'
  | SOURCE_LOG_POS = *source_log_pos*
  | SOURCE_AUTO_POSITION = {0|1}
  | RELAY_LOG_FILE = '*relay_log_name*'
  | RELAY_LOG_POS = *relay_log_pos*
  | SOURCE_HEARTBEAT_PERIOD = *interval*
  | SOURCE_CONNECT_RETRY = *interval*
  | SOURCE_RETRY_COUNT = *count*
  | SOURCE_CONNECTION_AUTO_FAILOVER = {0|1}
  | SOURCE_DELAY = *interval*
  | SOURCE_COMPRESSION_ALGORITHMS = '*algorithm*[,*algorithm*][,*algorithm*]'
  | SOURCE_ZSTD_COMPRESSION_LEVEL = *level*
  | SOURCE_SSL = {0|1}
  | SOURCE_SSL_CA = '*ca_file_name*'
  | SOURCE_SSL_CAPATH = '*ca_directory_name*'
  | SOURCE_SSL_CERT = '*cert_file_name*'
  | SOURCE_SSL_CRL = '*crl_file_name*'
  | SOURCE_SSL_CRLPATH = '*crl_directory_name*'
  | SOURCE_SSL_KEY = '*key_file_name*'
  | SOURCE_SSL_CIPHER = '*cipher_list*'
  | SOURCE_SSL_VERIFY_SERVER_CERT = {0|1}
  | SOURCE_TLS_VERSION = '*protocol_list*'
  | SOURCE_TLS_CIPHERSUITES = '*ciphersuite_list*'
  | SOURCE_PUBLIC_KEY_PATH = '*key_file_name*'
  | GET_SOURCE_PUBLIC_KEY = {0|1}
  | NETWORK_NAMESPACE = '*namespace*'
  | IGNORE_SERVER_IDS = (*server_id_list*),
  | GTID_ONLY = {0|1}
}

*channel_option*:
    FOR CHANNEL *channel*

*server_id_list*:
    [*server_id* [, *server_id*] ... ]

CHANGE REPLICATION SOURCE TO更改副本服务器用于连接到源并从源读取数据的参数。它还更新了复制元数据存储库的内容(参见Section 19.2.4, “Relay Log and Replication Metadata Repositories”)。在 MySQL 8.0.23 及更高版本中,请使用CHANGE REPLICATION SOURCE TO代替已弃用的CHANGE MASTER TO语句。

CHANGE REPLICATION SOURCE TO需要REPLICATION_SLAVE_ADMIN权限(或已弃用的SUPER权限)。

CHANGE REPLICATION SOURCE TO语句中未指定的选项保留其值,除非在以下讨论中另有说明。因此,在大多数情况下,无需指定不更改的选项。

用于SOURCE_HOST和其他CHANGE REPLICATION SOURCE TO选项的值会检查换行符(\n0x0A)。这些值中存在这些字符会导致语句失败并出现错误。

可选的FOR CHANNEL *channel*子句允许您指定语句适用于哪个复制通道。提供FOR CHANNEL *channel*子句将CHANGE REPLICATION SOURCE TO语句应用于特定的复制通道,并用于添加新通道或修改现有通道。例如,要添加一个名为channel2的新通道:

CHANGE REPLICATION SOURCE TO SOURCE_HOST=host1, SOURCE_PORT=3002 FOR CHANNEL 'channel2';

如果未命名子句且不存在额外通道,则CHANGE REPLICATION SOURCE TO语句适用于默认通道,其名称为空字符串("")。当设置了多个复制通道时,每个CHANGE REPLICATION SOURCE TO语句必须使用FOR CHANNEL *channel*子句命名通道。有关更多信息,请参见Section 19.2.2, “Replication Channels”

对于CHANGE REPLICATION SOURCE TO语句的某些选项,您必须在发出CHANGE REPLICATION SOURCE TO语句之前发出STOP REPLICA语句(以及之后发出START REPLICA语句)。有时,您只需要停止复制 SQL(应用程序)线程或复制 I/O(接收)线程,而不是两者都要停止:

  • 当应用程序线程停止时,您可以使用RELAY_LOG_FILERELAY_LOG_POSSOURCE_DELAY选项的任何允许组合执行CHANGE REPLICATION SOURCE TO,即使复制接收线程正在运行。当接收线程正在运行时,不得使用此语句的其他选项。

  • 当接收线程停止时,您可以使用此语句的任何选项(以任何允许的组合)执行CHANGE REPLICATION SOURCE TO除了 RELAY_LOG_FILERELAY_LOG_POSSOURCE_DELAYSOURCE_AUTO_POSITION = 1,即使应用程序线程正在运行。

  • 在发出使用SOURCE_AUTO_POSITION = 1GTID_ONLY = 1ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONSCHANGE REPLICATION SOURCE TO语句之前,必须停止接收线程和应用程序线程。

您可以使用SHOW REPLICA STATUS检查复制应用程序线程和复制接收线程的当前状态。请注意,Group Replication 应用程序通道(group_replication_applier)没有接收线程,只有一个应用程序线程。

CHANGE REPLICATION SOURCE TO语句具有许多您在执行之前应该了解的副作用和交互作用:

  • CHANGE REPLICATION SOURCE TO会导致正在进行的事务隐式提交。请参阅第 15.3.3 节,“导致隐式提交的语句”。

  • CHANGE REPLICATION SOURCE TO会将SOURCE_HOSTSOURCE_PORTSOURCE_LOG_FILESOURCE_LOG_POS的先前值写入错误日志,以及在执行之前有关副本状态的其他信息。

  • 如果您正在使用基于语句的复制和临时表,可能会出现在CHANGE REPLICATION SOURCE TO语句之后的STOP REPLICA语句在副本上留下临时表的情况。每当发生这种情况时,都会发出警告(ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO)。在这种情况下,您可以通过确保在执行CHANGE REPLICATION SOURCE TO语句之前,Replica_open_temp_tablesSlave_open_temp_tables系统状态变量的值等于 0 来避免这种情况。

  • 当使用多线程副本(replica_parallel_workers > 0)时,停止副本可能导致已从中继日志执行的事务序列中存在间隙,无论副本是有意停止还是其他情况。当存在这样的间隙时,发出CHANGE REPLICATION SOURCE TO将失败。在这种情况下的解决方案是发出START REPLICA UNTIL SQL_AFTER_MTS_GAPS,以确保关闭这些间隙。从 MySQL 8.0.26 开始,当使用基于 GTID 的复制和 GTID 自动定位时,完全跳过检查事务序列中的间隙的过程,因为可以使用 GTID 自动定位解决事务间隙。在这种情况下,仍然可以使用CHANGE REPLICATION SOURCE TO

下列选项适用于CHANGE REPLICATION SOURCE TO语句:

  • ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS = {OFF | LOCAL | *uuid*}

    使复制通道为没有 GTID 的复制事务分配一个 GTID,从不使用基于 GTID 的复制的源复制到使用基于 GTID 的副本。对于多源副本,您可以同时使用ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS和不使用该选项的通道。默认值为OFF,表示不使用该功能。

    LOCAL分配一个包括副本自身 UUID(server_uuid设置)的 GTID。*uuid*分配一个包括指定 UUID 的 GTID,例如复制源服务器的server_uuid设置。使用非本地 UUID 可以区分在副本上发起的事务和在源上发起的事务,对于多源副本,还可以区分在不同源上发起的事务。您选择的 UUID 仅对副本自身使用有意义。如果源发送的任何事务已经具有 GTID,则保留该 GTID。

    专用于组复制的通道不能使用ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS,但是在作为组复制组成员的服务器实例上的另一个源的异步复制通道可以这样做。在这种情况下,不要将组复制组名称指定为创建 GTID 的 UUID。

    要将ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS设置为LOCAL*uuid*,副本必须设置gtid_mode=ON,并且之后不能更改。此选项用于与具有基于二进制日志文件位置的复制的源一起使用,因此不能为通道设置SOURCE_AUTO_POSITION=1。在设置此选项之前,必须停止复制 SQL 线程和复制 I/O(接收器)线程。

    重要提示

    使用任何通道上的ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS设置的副本不能在需要故障转移时晋升为替换复制源服务器,并且不能使用从副本中获取的备份来恢复复制源服务器。相同的限制也适用于替换或恢复其他使用任何通道上的ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS的副本。

    有关更多限制和信息,请参阅第 19.1.3.6 节,“从没有 GTID 的源复制到具有 GTID 的副本”。

  • GET_SOURCE_PUBLIC_KEY = {0|1}

    通过请求源的公钥启用基于 RSA 密钥对的密码交换。此选项默认禁用。

    此选项适用于使用caching_sha2_password认证插件进行身份验证的复制品。对于使用此插件进行身份验证的帐户的连接,源不会发送公钥,除非请求,因此必须请求或在客户端中指定。如果给出SOURCE_PUBLIC_KEY_PATH并指定有效的公钥文件,则它优先于GET_SOURCE_PUBLIC_KEY。如果您正在使用使用caching_sha2_password插件进行身份验证的复制用户帐户(这是从 MySQL 8.0 开始的默认设置),并且您没有使用安全连接,则必须指定此选项或SOURCE_PUBLIC_KEY_PATH选项以向复制品提供 RSA 公钥。

  • GTID_ONLY = {0|1}

    停止复制通道在复制元数据存储库中持久化文件名和文件位置。GTID_ONLY从 MySQL 8.0.27 版本开始提供。GTID_ONLY选项在异步复制通道中默认禁用,但在组复制通道中默认启用,并且无法禁用。

    对于具有此设置的复制通道,仍会跟踪内存中的文件位置,并且出于调试目的,文件位置仍然可以在错误消息和通过诸如SHOW REPLICA STATUS语句(如果它们过时,则显示为无效)等接口中观察到。但是,在 GTID 基础复制实际上不需要它们的情况下,包括事务排队和应用程序过程中,避免了持久化和检查文件位置所需的写入和读取。

    只有在停止复制 SQL(应用程序)线程和复制 I/O(接收器)线程时才能使用此选项。要为复制通道设置GTID_ONLY = 1,必须在服务器上使用 GTID(gtid_mode = ON),并且源上必须使用基于行的二进制日志记录(不支持基于语句的复制)。必须为复制通道设置选项REQUIRE_ROW_FORMAT = 1SOURCE_AUTO_POSITION = 1

    当设置GTID_ONLY = 1时,如果服务器上的系统变量replica_parallel_workers设置为零,则复制品会使用replica_parallel_workers=1,因此它在技术上始终是一个多线程应用程序。这是因为多线程应用程序使用保存的位置而不是复制元数据存储库来定位需要重新应用的事务的起始位置。

    如果在设置GTID_ONLY后禁用它,现有的中继日志将被删除,并且现有的已知的二进制日志文件位置将被保留,即使它们已经过时。在复制元数据存储库中,二进制日志和中继日志的文件位置可能是无效的,如果是这种情况,则会返回警告。只要SOURCE_AUTO_POSITION仍然启用,GTID 自动定位将被用来提供正确的定位。

    如果你也禁用了SOURCE_AUTO_POSITION,则在复制元数据存储库中使用二进制日志和中继日志的文件位置进行定位,如果它们是有效的。如果它们被标记为无效,你必须提供一个有效的二进制日志文件名和位置(SOURCE_LOG_FILESOURCE_LOG_POS)。如果你还提供了一个中继日志文件名和位置(RELAY_LOG_FILERELAY_LOG_POS),则中继日志将被保留,而应用程序位置将被设置为指定位置。 GTID 自动跳过确保任何已经应用的事务都会被跳过,即使最终的应用程序位置不正确。

  • IGNORE_SERVER_IDS = (*server_id_list*)

    使复制忽略来自指定服务器的事件。该选项接受一个逗号分隔的 0 个或多个服务器 ID 的列表。来自服务器的日志旋转和删除事件不会被忽略,并且会记录在中继日志中。

    在循环复制中,原始服务器通常充当其自己事件的终结者,以便它们不会被应用多次。因此,在循环复制中,当圈中的一个服务器被移除时,这个选项是有用的。假设你有一个带有服务器 ID 为 1、2、3 和 4 的 4 台服务器的循环复制设置,并且服务器 3 失败了。在通过从服务器 2 开始到服务器 4 启动复制来填补这个空白时,你可以在服务器 4 上发出的CHANGE REPLICATION SOURCE TO语句中包含IGNORE_SERVER_IDS = (3),告诉��使用服务器 2 作为其源,而不是服务器 3。这样做会导致它忽略并不传播任何源自不再使用的服务器的语句。

    如果IGNORE_SERVER_IDS包含服务器自己的 ID,并且服务器是使用--replicate-same-server-id选项启动的,则会导致错误。

    注意

    当全局事务标识符(GTID)用于复制时,已经应用的事务会自动被忽略,因此不需要使用IGNORE_SERVER_IDS函数,该函数已被弃用。如果为服务器设置了gtid_mode=ON,则如果在CHANGE REPLICATION SOURCE TO语句中包含IGNORE_SERVER_IDS选项,则会发出弃用警告。

    源元数据存储库和SHOW REPLICA STATUS的输出提供当前被忽略的服务器列表。有关更多信息,请参见第 19.2.4.2 节,“复制元数据存储库”和第 15.7.7.35 节,“显示 REPLICA 状态语句”。

    如果发出不带任何IGNORE_SERVER_IDS选项的CHANGE REPLICATION SOURCE TO语句,则会保留任何现有列表。要清除被忽略服务器的列表,必须使用带有空列表的选项:

    CHANGE REPLICATION SOURCE TO IGNORE_SERVER_IDS = ();
    

    RESET REPLICA ALL清除IGNORE_SERVER_IDS

    注意

    如果在任何通道存在使用IGNORE_SERVER_IDS设置的现有服务器 ID 时发出SET GTID_MODE=ON,则会发出弃用警告。在启动基于 GTID 的复制之前,请检查并清除涉及服务器上的所有被忽略的服务器 ID 列表。SHOW REPLICA STATUS语句显示被忽略的 ID 列表(如果有)。如果收到弃用警告,仍然可以通过发出包含带有空列表的IGNORE_SERVER_IDS选项的CHANGE REPLICATION SOURCE TO语句来清除列表。

  • NETWORK_NAMESPACE = '*namespace*'

    用于 TCP/IP 连接到复制源服务器的网络命名空间,或者如果使用 MySQL 通信堆栈,则用于 Group Replication 的组通信连接。字符串值的最大长度为 64 个字符。如果省略此选项,则副本的连接将使用默认(全局)命名空间。在不实现网络命名空间支持的平台上,当副本尝试连接到源时会发生故障。有关网络命名空间的信息,请参见第 7.1.14 节,“网络命名空间支持”。NETWORK_NAMESPACE在 MySQL 8.0.22 中可用。

  • PRIVILEGE_CHECKS_USER = {NULL | '*account*'}

    为指定通道提供安全上下文的用户帐户名称。NULL是默认值,表示不使用安全上下文。PRIVILEGE_CHECKS_USER在 MySQL 8.0.18 中可用。

    用户帐户的用户名和主机名必须遵循 第 8.2.4 节“指定帐户名称” 中描述的语法,并且用户不能是匿名用户(具有空白用户名)或 CURRENT_USER。帐户必须具有 REPLICATION_APPLIER 权限,以及执行在通道上复制的事务所需的权限。有关帐户所需权限的详细信息,请参见 第 19.3.3 节“复制权限检查”。当重新启动复制通道时,权限检查将从那一点开始应用。如果您不指定通道且没有其他通道存在,则该语句将应用于默认通道。

    当设置了 PRIVILEGE_CHECKS_USER 时,强烈建议使用基于行的二进制日志记录,并且您可以设置 REQUIRE_ROW_FORMAT 来强制执行此操作。例如,要在运行中的副本上启动通道 channel_1 上的权限检查,请发出以下语句:

    STOP REPLICA FOR CHANNEL 'channel_1';
    
    CHANGE REPLICATION SOURCE TO
        PRIVILEGE_CHECKS_USER = '*user*'@'*host*',
        REQUIRE_ROW_FORMAT = 1,
        FOR CHANNEL 'channel_1';
    
    START REPLICA FOR CHANNEL 'channel_1';
    
  • RELAY_LOG_FILE = '*relay_log_file*'RELAY_LOG_POS = '*relay_log_pos*'

    中继日志文件名及文件中的位置,在该位置,复制 SQL 线程在下次启动时开始从副本的中继日志中读取的位置。RELAY_LOG_FILE 可以使用绝对路径或相对路径,并且使用与 SOURCE_LOG_FILE 相同的基本名称。字符串值的最大长度为 511 个字符。

    当复制 SQL(应用程序)线程停止时,可以在运行中的副本上执行一个使用 RELAY_LOG_FILERELAY_LOG_POS 或两者选项的 CHANGE REPLICATION SOURCE TO 语句。如果至少有一个复制应用程序线程和复制 I/O(接收器)线程正在运行,则中继日志将被保留。如果两个线程都停止,则除非至少指定一个 RELAY_LOG_FILERELAY_LOG_POS,否则所有中继日志文件都将被删除。对于仅具有应用程序线程而没有接收器线程的 Group Replication 应用程序通道(group_replication_applier),如果应用程序线程停止,但是对于该通道,您不能使用 RELAY_LOG_FILERELAY_LOG_POS 选项。

  • REQUIRE_ROW_FORMAT = {0|1}

    仅允许处理基于行的复制事件的复制通道。此选项阻止复制应用程序执行诸如创建临时表和执行LOAD DATA INFILE请求等操作,从而增加了通道的安全性。对于异步复制通道,默认情况下禁用REQUIRE_ROW_FORMAT选项,但对于组复制通道,默认情况下启用,并且无法禁用。有关更多信息,请参见第 19.3.3 节,“复制权限检查”。REQUIRE_ROW_FORMAT从 MySQL 8.0.19 开始提供。

  • REQUIRE_TABLE_PRIMARY_KEY_CHECK = {STREAM | ON | OFF | GENERATE}

    从 MySQL 8.0.20 开始提供,此选项允许复制品设置自己的主键检查策略,如下:

    • ON:复制品设置sql_require_primary_key = ON;任何复制的CREATE TABLEALTER TABLE语句必须生成包含主键的表。

    • OFF:复制品设置sql_require_primary_key = OFF;不会检查任何复制的CREATE TABLEALTER TABLE语句是否存在主键。

    • STREAM:复制品使用从源复制的sql_require_primary_key的任何值来处理每个事务。这是默认值和默认行为。

    • GENERATE:在 MySQL 8.0.32 中添加,这会导致复制品为任何缺少主键的InnoDB表生成一个不可见的主键。有关更多信息,请参见第 15.1.20.11 节,“生成的不可见主键”。

      GENERATE与组复制不兼容;您可以使用ONOFFSTREAM

    基于源或复制品表上仅存在生成的不可见主键的差异得到 MySQL 复制支持,只要源支持 GIPKs(MySQL 8.0.30 及更高版本),而复制品使用 MySQL 版本 8.0.32 或更高版本。如果在复制品上使用 GIPKs 并从使用 MySQL 8.0.29 或更早版本的源进行复制,则应注意,在这种情况下,除了复制品上的额外 GIPK 之外,不支持模式中的这种差异,并且可能导致复制错误。

    对于多源复制,将REQUIRE_TABLE_PRIMARY_KEY_CHECK设置为ONOFF可以使副本在不同源的复制通道之间规范化行为,并为sql_require_primary_key保持一致的设置。使用ON可以防止在多个源更新相同的表集时意外丢失主键。使用OFF可以让可以操作主键的源与不能操作主键的源一起工作。

    在多个副本的情况下,当REQUIRE_TABLE_PRIMARY_KEY_CHECK设置为GENERATE时,给定副本添加的生成的不可见主键与其他副本上添加的任何此类键是独立的。这意味着,如果使用生成的不可见主键,不同副本上生成的主键列中的值不能保证相同。当故障转移到这样的副本时,这可能是一个问题。

    PRIVILEGE_CHECKS_USERNULL(默认值)时,用户帐户不需要管理级别权限来设置受限制的会话变量。将此选项设置为NULL以外的值意味着,当REQUIRE_TABLE_PRIMARY_KEY_CHECKONOFFGENERATE时,用户帐户不需要会话管理级别权限来设置受限制的会话变量,如sql_require_primary_key,避免需要授予帐户此类权限。有关更多信息,请参见第 19.3.3 节,“复制权限检查”。

  • SOURCE_AUTO_POSITION = {0|1}

    使副本尝试使用基于 GTID 的复制的自动定位功能连接到源,而不是基于二进制日志文件的位置。此选项用于启动使用基于 GTID 的复制的副本。默认值为 0,表示不使用 GTID 自动定位和基于 GTID 的复制。只有在停止复制 SQL(应用程序)线程和复制 I/O(接收器)线程时,才能将此选项与CHANGE REPLICATION SOURCE TO一起使用。

    副本和源都必须启用 GTIDs(GTID_MODE=ON,在副本上为ON_PERMISSIVEOFF_PERMISSIVE,在源上为GTID_MODE=ON)。SOURCE_LOG_FILESOURCE_LOG_POSRELAY_LOG_FILERELAY_LOG_POS不能与SOURCE_AUTO_POSITION = 1一起指定。如果在副本上启用了多源复制,则需要为每个适用的复制通道设置SOURCE_AUTO_POSITION = 1选项。

    设置SOURCE_AUTO_POSITION = 1后,在初始连接握手中,副本发送一个包含其已接收、已提交或两者都包含的事务的 GTID 集。源端通过发送其二进制日志中所有 GTID 不包含在副本发送的 GTID 集中的事务来做出响应。这种交换确保源端只发送副本尚未记录或提交的具有 GTID 的事务。如果副本从多个源接收事务,如钻石拓扑结构的情况,自动跳过功能确保事务不会被应用两次。有关副本发送的 GTID 集如何计算的详细信息,请参见第 19.1.3.3 节,“GTID 自动定位”。

    如果应该由源端发送的任何事务已从源端的二进制日志中清除,或者通过其他方法添加到gtid_purged系统变量的 GTID 集中,源端将向副本发送错误ER_MASTER_HAS_PURGED_REQUIRED_GTIDS,并且复制不会启动。缺失的已清除事务的 GTID 将在源端的错误日志中被识别并列在警告消息ER_FOUND_MISSING_GTIDS中。此外,如果在事务交换过程中发现副本已记录或提交具有源端 UUID 的 GTID 的事务,但源端本身尚未提交它们,则源端将向副本发送错误ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER,并且复制不会启动。有关如何处理这些情况的信息,请参见第 19.1.3.3 节,“GTID 自动定位”。

    您可以通过检查性能模式replication_connection_status表或SHOW REPLICA STATUS语句的输出来查看是否启用了 GTID 自动定位的复制。再次禁用SOURCE_AUTO_POSITION选项会使副本恢复到基于文件的复制。

  • SOURCE_BIND = '*interface_name*'

    确定副本的哪个网络接口被选择用于连接到源端,适用于具有多个网络接口的副本。指定网络接口的 IP 地址。字符串值的最大长度为 255 个字符。

    配置此选项的 IP 地址(如果有)可以在SHOW REPLICA STATUS输出的Source_Bind列中看到。在源元数据存储库表mysql.slave_master_info中,该值可以在Source_bind列中看到。将副本绑定到特定网络接口的能力也受到 NDB Cluster 的支持。

  • SOURCE_COMPRESSION_ALGORITHMS = '*algorithm*[,*algorithm*][,*algorithm*]'

    指定连接到复制源服务器的允许压缩算法之一、两个或三个,用逗号分隔。字符串值的最大长度为 99 个字符。默认值为uncompressed

    可用的算法是zlibzstduncompressed,与protocol_compression_algorithms系统变量相同。算法可以以任何顺序指定,但这不是优先顺序 - 算法协商过程尝试使用zlib,然后zstd,然后uncompressed,如果它们被指定。SOURCE_COMPRESSION_ALGORITHMS自 MySQL 8.0.18 起可用。

    SOURCE_COMPRESSION_ALGORITHMS的值仅在禁用replica_compressed_protocolslave_compressed_protocol系统变量时适用。如果启用了replica_compressed_protocolslave_compressed_protocol,它优先于SOURCE_COMPRESSION_ALGORITHMS,并且如果源和副本都支持该算法,则连接到源使用zlib压缩。有关更多信息,请参见第 6.2.8 节,“连接压缩控制”。

    二进制日志事务压缩(自 MySQL 8.0.20 起可用),由binlog_transaction_compression系统变量激活,也可用于节省带宽。如果与连接压缩结合使用,连接压缩的机会减少,但仍可压缩标头以及未压缩的事件和事务有效载荷。有关二进制日志事务压缩的更多信息,请参见第 7.4.4.5 节,“二进制日志事务压缩”。

  • SOURCE_CONNECT_RETRY = *interval*

    指定副本在连接到源超时后重新连接尝试之间的秒数间隔。默认间隔为 60。

    尝试次数受SOURCE_RETRY_COUNT选项限制。如果使用默认设置,副本在重新连接尝试之间等待 60 秒(SOURCE_CONNECT_RETRY=60),并以这个速率持续尝试重新连接 60 天(SOURCE_RETRY_COUNT=86400)。这些值记录在源元数据存储库中,并显示在replication_connection_configuration性能模式表中。

  • SOURCE_CONNECTION_AUTO_FAILOVER = {0|1}

    如果有一个或多个备用复制源服务器可用(即有多个共享复制数据的 MySQL 服务器或服务器组),则为复制通道激活异步连接故障转移机制。SOURCE_CONNECTION_AUTO_FAILOVER从 MySQL 8.0.22 版本开始提供。默认值为 0,表示未激活该机制。有关完整信息和设置此功能的说明,请参阅第 19.4.9.2 节,“副本的异步连接故障转移”。

    在由SOURCE_CONNECT_RETRYSOURCE_RETRY_COUNT控制的重新连接尝试耗尽后,异步连接故障转移机制接管。它将副本重新连接到从指定源列表中选择的备用源,您可以使用asynchronous_connection_failover_add_sourceasynchronous_connection_failover_delete_source函数进行管理。要添加和删除受管理的服务器组,请改用asynchronous_connection_failover_add_managedasynchronous_connection_failover_delete_managed函数。有关更多信息,请参阅第 19.4.9 节,“使用异步连接故障转移切换源和副本”。

    重要提示

    1. 只有在使用 GTID 自动定位时(SOURCE_AUTO_POSITION = 1)才能设置SOURCE_CONNECTION_AUTO_FAILOVER = 1

    2. 当你设置 SOURCE_CONNECTION_AUTO_FAILOVER = 1 时,将 SOURCE_RETRY_COUNTSOURCE_CONNECT_RETRY 设置为最小值,只允许几次重试尝试与相同源进行连接,以防连接失败是由瞬时网络中断引起的。否则,异步连接故障转移机制无法及时激活。适当的值是 SOURCE_RETRY_COUNT=3SOURCE_CONNECT_RETRY=10,这使得副本在每次连接尝试之间间隔 10 秒的情况下重试连接 3 次。

    3. 当你设置 SOURCE_CONNECTION_AUTO_FAILOVER = 1 时,复制元数据存储库必须包含用于连接到复制通道源列表中所有服务器的复制用户帐户的凭据。该帐户还必须对性能模式表具有 SELECT 权限。这些凭据可以使用带有 SOURCE_USERSOURCE_PASSWORD 选项的 CHANGE REPLICATION SOURCE TO 语句进行设置。有关更多信息,请参见 第 19.4.9 节,“使用异步连接故障转移切换源和副本”。

    4. 从 MySQL 8.0.27 开始,当你设置 SOURCE_CONNECTION_AUTO_FAILOVER = 1 时,如果这个复制通道在单主模式下的组复制主节点上,副本的异步连接故障转移将自动激活。启用此功能后,如果正在复制的主节点下线或进入错误状态,新的主节点在选举时将在同一通道上开始复制。如果要使用此功能,这个复制通道还必须在复制组中的所有辅助服务器上设置,并在任何新加入的成员上设置。(如果使用 MySQL 的克隆功能进行服务器配置,则所有这些都会自动发生。)如果不想使用此功能,请使用 group_replication_disable_member_action() 函数禁用默认启用的 Group Replication 成员操作 mysql_start_failover_channels_if_primary。有关更多信息,请参见 第 19.4.9.2 节,“副本的异步连接故障转移”。

  • SOURCE_DELAY = *interval*

    指定副本必须滞后源多少秒。从源接收的事件直到至少延迟interval秒后才执行。interval必须是 0 到 2³¹−1 范围内的非负整数。默认值为 0。更多信息,请参见 Section 19.4.11, “Delayed Replication”。

    使用SOURCE_DELAY选项的CHANGE REPLICATION SOURCE TO语句可以在运行中的副本上执行,当复制 SQL 线程停止时。

  • SOURCE_HEARTBEAT_PERIOD = *interval*

    控制心跳间隔,防止在没有数据的情况下发生连接超时,如果连接仍然良好。在那么多秒后向副本发送心跳信号,并且每当源的二进制日志更新为一个事件时,等待时间就会重置。因此,只有在二进制日志文件中有一段时间没有发送的事件时,源才会发送心跳。

    心跳间隔interval是一个十进制值,范围为 0 到 4294967 秒,分辨率为毫秒;最小的非零值为 0.001。将interval设置为 0 会完全禁用心跳。心跳间隔默认为replica_net_timeoutslave_net_timeout系统变量值的一半。它记录在源元数据存储库中,并显示在replication_connection_configuration性能模式表中。

    系统变量replica_net_timeout(从 MySQL 8.0.26 开始)或slave_net_timeout(MySQL 8.0.26 之前)指定副本等待来自源的更多数据或心跳信号的秒数,超过该时间副本将认为连接已中断,中止读取并尝试重新连接。默认值为 60 秒(一分钟)。请注意,对replica_net_timeoutslave_net_timeout的值或默认设置的更改不会自动更改心跳间隔,无论是明确设置还是使用先前计算的默认值。如果将全局值replica_net_timeoutslave_net_timeout设置为小于当前心跳间隔的值,将发出警告。如果更改了replica_net_timeoutslave_net_timeout,还必须发出CHANGE REPLICATION SOURCE TO以将心跳间隔调整为适当的值,以便心跳信号在连接超时之前发生。如果不这样做,心跳信号将不起作用,如果未从源接收到数据,则副本可能会进行重复的重新连接尝试,从而创建僵尸转储线程。

  • SOURCE_HOST = '*host_name*'

    复制源服务器的主机名或 IP 地址。副本使用此信息连接到源。字符串值的最大长度为 255 个字符。

    如果您指定了SOURCE_HOSTSOURCE_PORT,则副本会假定源服务器与之前不同(即使选项值与当前值相同)。在这种情况下,源的二进制日志文件名和位置的旧值被认为不再适用,因此如果您在语句中未指定SOURCE_LOG_FILESOURCE_LOG_POS,则会自动追加SOURCE_LOG_FILE=''SOURCE_LOG_POS=4

    SOURCE_HOST=''设置为(即,将其值明确设置为空字符串)等同于根本不设置SOURCE_HOST。尝试将SOURCE_HOST设置为空字符串会导致错误。

  • SOURCE_LOG_FILE = '*source_log_name*', SOURCE_LOG_POS = *source_log_pos*

    二进制日志文件名以及在该文件中的位置,复制 I/O(接收器)线程在下次启动时从源二进制日志中开始读取的位置。如果使用基于二进制日志文件位置的复制,请指定这些选项。

    SOURCE_LOG_FILE必须包括源服务器上可用的特定二进制日志文件的数字后缀,例如,SOURCE_LOG_FILE='binlog.000145'。字符串值的最大长度为 511 个字符。

    SOURCE_LOG_POS是副本在该文件中开始读取的数字位置。SOURCE_LOG_POS=4表示二进制日志文件中事件的开始。

    如果指定了SOURCE_LOG_FILESOURCE_LOG_POS中的任一个,则不能指定SOURCE_AUTO_POSITION = 1,该选项用于基于 GTID 的复制。

    如果未指定SOURCE_LOG_FILESOURCE_LOG_POS中的任何一个,则副本使用复制 SQL 线程在发出CHANGE REPLICATION SOURCE TO之前的最后坐标。这确保了即使复制 SQL(应用程序)线程比复制 I/O(接收器)线程晚,复制也不会出现不连续。

  • SOURCE_PASSWORD = '*password*'

    用于连接到复制源服务器的复制用户帐户的密码。字符串值的最大长度为 32 个字符。如果指定了SOURCE_PASSWORD,则还需要SOURCE_USER

    CHANGE REPLICATION SOURCE TO语句中用于复制用户帐户的密码长度限制为 32 个字符。尝试使用超过 32 个字符的密码会导致CHANGE REPLICATION SOURCE TO失败。

    密码在 MySQL Server 的日志、性能模式表和SHOW PROCESSLIST语句中被掩盖。

  • SOURCE_PORT = *port_num*

    副本用于连接到复制源服务器的 TCP/IP 端口号。

    注意

    复制不能使用 Unix 套接字文件。您必须能够使用 TCP/IP 连接到复制源服务器。

    如果指定了SOURCE_HOSTSOURCE_PORT,则副本会假定源服务器与以前不同(即使选项值与当前值相同)。在这种情况下,源二进制日志文件名和位置的旧值被认为不再适用,因此如果在语句中不指定SOURCE_LOG_FILESOURCE_LOG_POS,则会自动附加SOURCE_LOG_FILE=''SOURCE_LOG_POS=4

  • SOURCE_PUBLIC_KEY_PATH = '*key_file_name*'

    通过提供包含源端所需的公钥副本的文件路径名,启用基于 RSA 密钥对的密码交换。文件必须采用 PEM 格式。字符串值的最大长度为 511 个字符。

    此选项适用于使用sha256_passwordcaching_sha2_password认证插件进行身份验证的副本。(对于sha256_password,只有在使用 OpenSSL 构建 MySQL 时才能使用SOURCE_PUBLIC_KEY_PATH。)如果您正在使用使用caching_sha2_password插件进行身份验证的复制用户帐户(这是 MySQL 8.0 的默认设置),并且您没有使用安全连接,则必须指定此选项或GET_SOURCE_PUBLIC_KEY=1选项之一以向副本提供 RSA 公钥。

  • SOURCE_RETRY_COUNT = *count*

    设置副本在与源的连接超时后进行的重新连接尝试的最大次数,由replica_net_timeoutslave_net_timeout系统变量确定。如果副本确实需要重新连接,则第一次重试会在超时后立即发生。默认值为 86400 次尝试。

    尝试之间的间隔由SOURCE_CONNECT_RETRY选项指定。如果使用默认设置,副本在重新连接尝试之间等待 60 秒(SOURCE_CONNECT_RETRY=60),并以此速率继续尝试重新连接 60 天(SOURCE_RETRY_COUNT=86400)。将SOURCE_RETRY_COUNT设置为 0 意味着没有重新连接尝试次数限制,因此副本将无限尝试重新连接。

    SOURCE_CONNECT_RETRYSOURCE_RETRY_COUNT的值记录在源元数据存储库中,并显示在replication_connection_configuration性能模式表中。SOURCE_RETRY_COUNT取代了--master-retry-count服务器启动选项。

  • SOURCE_SSL = {0|1}

    指定副本是否加密复制连接。默认值为 0,表示副本不加密复制连接。如果设置SOURCE_SSL=1,则可以使用SOURCE_SSL_*xxx*SOURCE_TLS_*xxx*选项配置加密。

    为复制连接设置SOURCE_SSL=1,然后不设置更多的SOURCE_SSL_*xxx*选项相当于为客户端设置--ssl-mode=REQUIRED,如加密连接的命令选项中所述。使用SOURCE_SSL=1,只有在可以建立加密连接时连接尝试才会成功。复制连接不会退回到未加密连接,因此没有与复制的--ssl-mode=PREFERRED设置相对应的设置。如果设置SOURCE_SSL=0,则相当于--ssl-mode=DISABLED

    重要

    为了帮助防止复杂的中间人攻击,副本验证服务器的身份非常重要。您可以指定额外的SOURCE_SSL_*xxx*选项对应于设置--ssl-mode=VERIFY_CA--ssl-mode=VERIFY_IDENTITY,这比默认设置更好地帮助防止这种类型的攻击。使用这些设置,副本会检查服务器的证书是否有效,并检查副本正在使用的主机名是否与服务器证书中的标识匹配。要实施这些验证级别之一,您必须首先确保服务器的 CA 证书可靠地可用于副本,否则将导致可用性问题。因此,它们不是默认设置。

  • SOURCE_SSL_*xxx*, SOURCE_TLS_*xxx*

    指定副本如何使用加密和密码来保护复制连接。即使在没有 SSL 支持的编译副本上也可以更改这些选项。它们保存在源元数据存储库中,但如果副本没有启用 SSL 支持,则会被忽略。字符串值为SOURCE_SSL_*xxx*SOURCE_TLS_*xxx*选项的最大长度为 511 个字符,例外是SOURCE_TLS_CIPHERSUITES,其长度为 4000 个字符。

    SOURCE_SSL_*xxx*SOURCE_TLS_*xxx*选项执行与加密连接的命令选项中描述的--ssl-*xxx*--tls-*xxx*客户端选项相同的功能。两组选项之间的对应关系,以及使用SOURCE_SSL_*xxx*SOURCE_TLS_*xxx*选项建立安全连接的方法,在第 19.3.1 节,“设置复制使用加密连接”中有解释。

  • SOURCE_USER = '*user_name*'

    复制用户帐户的用户名,用于连接到复制源服务器。字符串值的最大长度为 96 个字符。

    对于组复制,此帐户必须存在于复制组的每个成员中。如果 XCom 通信堆栈用于组,则用于分布式恢复,如果 MySQL 通信堆栈用于组,则用于组通信连接。对于 MySQL 通信堆栈,帐户必须具有GROUP_REPLICATION_STREAM权限。

    可以通过指定SOURCE_USER=''来设置空用户名称,但不能使用空用户名称启动复制通道。在 MySQL 8.0.21 之前的版本中,仅在需要出于安全目的清除先前使用的凭据时才设置空的SOURCE_USER用户名称。不要在此之后使用通道,因为在这些版本中存在一个错误,如果从存储库中读取空用户名称(例如,在组复制通道的自动重启期间),则可能会替换默认用户名称。从 MySQL 8.0.21 开始,可以设置空的SOURCE_USER用户名称,并且可以在之后使用通道,如果始终使用START REPLICA语句或START GROUP_REPLICATION语句提供用户凭据以启动复制通道。这种方法意味着复制通道始终需要操作员干预才能重新启动,但用户凭据不会记录在复制元数据存储库中。

    重要提示

    要使用使用caching_sha2_password插件进行身份验证的复制用户帐户连接到源,您必须设置安全连接,如 Section 19.3.1, “Setting Up Replication to Use Encrypted Connections”中所述,或启用不加密的连接以支持使用 RSA 密钥对进行密码交换。caching_sha2_password身份验证插件是从 MySQL 8.0 开始的新用户的默认插件(请参阅 Section 8.4.1.2, “Caching SHA-2 Pluggable Authentication”)。如果您创建或使用用于复制的用户帐户使用此身份验证插件,并且未使用安全连接,则必须启用基于 RSA 密钥对的密码交换以成功连接。您可以使用此语句的SOURCE_PUBLIC_KEY_PATH选项或GET_SOURCE_PUBLIC_KEY=1选项来执行此操作。

  • SOURCE_ZSTD_COMPRESSION_LEVEL = *level*

    用于使用zstd压缩算法的连接到复制源服务器的压缩级别。SOURCE_ZSTD_COMPRESSION_LEVEL自 MySQL 8.0.18 起可用。允许的级别为 1 到 22,较大的值表示较高级别的压缩。默认级别为 3。

    压缩级别设置对不使用zstd压缩的连接没有影响。有关更多信息,请参见第 6.2.8 节,“连接压缩控制”。

示例

CHANGE REPLICATION SOURCE TO对于在具有源快照并记录了快照时间对应的源二进制日志坐标的情况下设置副本非常有用。在将快照加载到副本以与源同步之后,您可以在副本上运行CHANGE REPLICATION SOURCE TO SOURCE_LOG_FILE='*log_name*', SOURCE_LOG_POS=*log_pos*来指定副本应开始读取源二进制日志的坐标。以下示例更改了副本使用的源服务器并建立了副本开始读取的源二进制日志坐标:

CHANGE REPLICATION SOURCE TO
  SOURCE_HOST='source2.example.com',
  SOURCE_USER='replication',
  SOURCE_PASSWORD='*password*',
  SOURCE_PORT=3306,
  SOURCE_LOG_FILE='source2-bin.001',
  SOURCE_LOG_POS=4,
  SOURCE_CONNECT_RETRY=10;

有关在故障切换期间将现有副本切换到新源的过程,请参见第 19.4.8 节,“故障切换期间切换源”。

当源和副本上使用 GTID 时,请指定 GTID 自动定位,而不是提供二进制日志文件位置,如下例所示。有关在新服务器或停止的服务器、在线服务器或其他副本上配置和启动基于 GTID 的复制的完整说明,请参见第 19.1.3 节,“具有全局事务标识符的复制”。

CHANGE REPLICATION SOURCE TO
  SOURCE_HOST='source3.example.com',
  SOURCE_USER='replication',
  SOURCE_PASSWORD='*password*',
  SOURCE_PORT=3306,
  SOURCE_AUTO_POSITION = 1,
  FOR CHANNEL "source_3";

在此示例中,使用了多源复制,并且CHANGE REPLICATION SOURCE TO语句应用于连接副本到指定主机的复制通道"source_3"。有关设置多源复制的指导,请参见第 19.1.5 节,“MySQL 多源复制”。

下一个示例显示了如何使副本应用您想要重复的中继日志文件中的事务。为此,源不需要可达。您可以使用CHANGE REPLICATION SOURCE TO来定位您希望副本开始重新应用事务的中继日志位置,然后启动 SQL 线程:

CHANGE REPLICATION SOURCE TO
  RELAY_LOG_FILE='replica-relay-bin.006',
  RELAY_LOG_POS=4025;
START REPLICA SQL_THREAD;

更改复制源为也可以用于跳过导致复制停止的二进制日志中的事务。执行此操作的适当方法取决于是否使用 GTID。有关使用更改复制源为或其他方法跳过事务的说明,请参见第 19.1.7.3 节,“跳过事务”。

posted @ 2024-06-23 00:40  绝不原创的飞龙  阅读(4)  评论(0编辑  收藏  举报