#### 5.6.6.3 使用版本令牌

[](<>)

在使用版本令牌之前，请根据提供的说明安装它[第 5.6.6.2 节，“安装或卸载版本令牌”](version-tokens-installation.html).

版本令牌可能有用的场景是访问 MySQL 服务器集合但需要通过监视它们并根据负载变化调整服务器分配来管理它们以实现负载平衡的系统。这样的系统包括以下要素：

-   要管理的 MySQL 服务器的集合。

-   与服务器通信并将它们组织成高可用性组的管理或管理应用程序。组服务于不同的目的，每个组内的服务器可能有不同的分配。某个组内的服务器分配可以随时更改。

-   访问服务器以检索和更新数据的客户端应用程序，根据分配给它们的目的选择服务器。例如，客户端不应向只读服务器发送更新。

    版本令牌允许根据分配管理服务器访问，而无需客户端重复查询服务器有关其分配的信息：

-   管理应用程序执行服务器分配并在每个服务器上建立版本令牌以反映其分配。应用程序缓存此信息以提供对其的中央访问点。

    如果在某些时候管理应用程序需要更改服务器分配（例如，将其从允许写入更改为只读），它会更改服务器的版本令牌列表并更新其缓存。

-   为了提高性能，客户端应用程序从管理应用程序中获取缓存信息，从而使它们不必为每个语句检索有关服务器分配的信息。根据它发出的语句类型（例如，读取与写入），客户端选择合适的服务器并连接到它。

-   此外，客户端向服务器发送它自己的客户端特定版本令牌，以注册它需要的服务器分配。对于客户端发送给服务器的每一条语句，服务器都会将自己的令牌列表与客户端令牌列表进行比较。如果服务器令牌列表包含客户端令牌列表中存在的具有相同值的所有令牌，则存在匹配并且服务器执行该语句。

    另一方面，管理应用程序可能更改了服务器分配及其版本令牌列表。在这种情况下，新的服务器分配现在可能与客户端要求不兼容。服务器和客户端令牌列表之间发生令牌不匹配，并且服务器在回复语句时返回错误。这指示客户端从管理应用程序缓存中刷新其版本令牌信息，并选择一个新的服务器进行通信。

    用于检测版本令牌错误和选择新服务器的客户端逻辑可以通过不同的方式实现：

-   客户端可以自己处理所有版本令牌注册、不匹配检测和连接切换。

-   这些操作的逻辑可以在管理客户端和 MySQL 服务器之间的连接的连接器中实现。这样的连接器可能会处理不匹配错误检测和语句重新发送本身，或者它可能会将错误传递给应用程序并将其留给应用程序重新发送语句。

    以下示例以更具体的形式说明了前面的讨论。

    当版本令牌在给定服务器上初始化时，服务器的版本令牌列表为空。令牌列表维护是通过调用函数来执行的。这[`VERSION_TOKEN_ADMIN`](privileges-provided.html#priv_version-token-admin)特权（或已弃用的[`极好的`](privileges-provided.html#priv_super)权限）是调用任何版本令牌功能所必需的，因此令牌列表修改预计将由具有该权限的管理或管理应用程序完成。

    假设管理应用程序与一组服务器通信，客户端查询这些服务器以访问员工和产品数据库（名为`雇员`和`产品`， 分别）。允许所有服务器处理数据检索语句，但只允许其中一些服务器进行数据库更新。为了在特定于数据库的基础上处理这个问题，管理应用程序在每个服务器上建立一个版本令牌列表。在给定服务器的令牌列表中，令牌名称代表数据库名称，令牌值是`读`要么`写`取决于数据库是否必须以只读方式使用，或者它是否可以读取和写入。

    客户端应用程序通过设置系统变量注册它们需要服务器匹配的版本令牌列表。变量设置发生在客户特定的基础上，因此不同的客户可以注册不同的要求。默认情况下，客户端令牌列表为空，匹配任何服务器令牌列表。当客户端将其令牌列表设置为非空值时，匹配可能成功或失败，具体取决于服务器版本令牌列表。

    为了定义服务器的版本令牌列表，管理应用程序调用[`version_tokens_set()`](version-tokens-reference.html#function_version-tokens-set)功能。（还有用于修改和显示令牌列表的功能，稍后将介绍。）例如，应用程序可能会将这些语句发送到一组三个服务器：

    服务器 1：


```
mysql> SELECT version_tokens_set('emp=read;prod=read');
+------------------------------------------+
| version_tokens_set('emp=read;prod=read') |
+------------------------------------------+
| 2 version tokens set.                    |
+------------------------------------------+
```

服务器 2：

```
mysql> SELECT version_tokens_set('emp=write;prod=read');
+-------------------------------------------+
| version_tokens_set('emp=write;prod=read') |
+-------------------------------------------+
| 2 version tokens set.                     |
+-------------------------------------------+
```

服务器 3：

```
mysql> SELECT version_tokens_set('emp=read;prod=write');
+-------------------------------------------+
| version_tokens_set('emp=read;prod=write') |
+-------------------------------------------+
| 2 version tokens set.                     |
+-------------------------------------------+
```

每种情况下的记号列表都被指定为一个以分号分隔的列表`*`姓名`*=*`价值`*`对。生成的令牌列表值会导致这些服务器分配：

-   任何服务器都接受任一数据库的读取。

-   只有服务器 2 接受更新`雇员`数据库。

-   只有服务器 3 接受更新`产品`数据库。

    除了为每个服务器分配一个版本令牌列表之外，管理应用程序还维护一个反映服务器分配的缓存。

    在与服务器通信之前，客户端应用程序联系管理应用程序并检索有关服务器分配的信息。然后客户端根据这些分配选择服务器。假设客户端想要在`雇员`数据库根据前面的分配，只有服务器2符合条件。客户端连接到服务器2，并通过设置[`版本\u令牌\u会话`](version-tokens-reference.html#sysvar_version_tokens_session)系统变量：


```
mysql> SET @@SESSION.version_tokens_session = 'emp=write';
```

对于客户端发送到服务器2的后续语句，服务器将自己的版本令牌列表与客户端列表进行比较，以检查它们是否匹配。如果是这样，语句将正常执行：

```
mysql> UPDATE emp.employee SET salary = salary * 1.1 WHERE id = 4981;
Query OK, 1 row affected (0.07 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT last_name, first_name FROM emp.employee WHERE id = 4981;
+-----------+------------+
| last_name | first_name |
+-----------+------------+
| Smith     | Abe        |
+-----------+------------+
1 row in set (0.01 sec)
```

服务器和客户端版本令牌列表之间的差异可能以两种方式出现：

-   表中的标记名[`版本\u令牌\u会话`](version-tokens-reference.html#sysvar_version_tokens_session)值不在服务器令牌列表中。在这种情况下[`ER_VTOKEN_插件_令牌_未找到`](https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_vtoken_plugin_token_not_found)发生错误。

-   表中的标记值[`版本\u令牌\u会话`](version-tokens-reference.html#sysvar_version_tokens_session)值与服务器令牌列表中相应令牌的值不同。在这种情况下[`ER\u VTOKEN\u插件\u令牌\u不匹配`](https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_vtoken_plugin_token_mismatch)发生错误。

    只要服务器2的分配没有改变，客户端就会继续使用它进行读写。但假设管理应用程序希望更改服务器分配，以便为`电磁脉冲`数据库必须发送到服务器1而不是服务器2。为此，它使用[`版本_令牌_编辑（）`](version-tokens-reference.html#function_version-tokens-edit)修改`电磁脉冲`两台服务器上的令牌值（并更新其服务器分配缓存）：

    服务器1：


```
mysql> SELECT version_tokens_edit('emp=write');
+----------------------------------+
| version_tokens_edit('emp=write') |
+----------------------------------+
| 1 version tokens updated.        |
+----------------------------------+
```

服务器2：

```
mysql> SELECT version_tokens_edit('emp=read');
+---------------------------------+
| version_tokens_edit('emp=read') |
+---------------------------------+
| 1 version tokens updated.       |
+---------------------------------+
```

[`版本_令牌_编辑（）`](version-tokens-reference.html#function_version-tokens-edit)修改服务器令牌列表中的命名令牌，并保留其他令牌不变。

客户机下次向服务器2发送语句时，其自己的令牌列表不再与服务器令牌列表匹配，并发生错误：

```
mysql> UPDATE emp.employee SET salary = salary * 1.1 WHERE id = 4982;
ERROR 3136 (42000): Version token mismatch for emp. Correct value read
```

在这种情况下，客户机应该联系管理应用程序以获取有关服务器分配的更新信息，选择一个新服务器，并将失败的语句发送到新服务器。

笔记

每个客户机必须与版本令牌配合，只发送符合其在给定服务器上注册的令牌列表的语句。例如，如果客户机注册了`“emp=read”`，版本令牌中没有任何内容可以阻止客户端发送更新`电磁脉冲`数据库客户本身必须避免这样做。

对于从客户端接收的每条语句，服务器隐式地使用锁定，如下所示：

-   为客户端令牌列表（即[`版本\u令牌\u会话`](version-tokens-reference.html#sysvar_version_tokens_session)（价值）

-   在服务器和客户端令牌列表之间执行比较

-   根据比较结果执行语句或产生错误

-   松开锁

    服务器使用共享锁，以便在不阻塞的情况下对多个会话进行比较，同时防止在操作服务器令牌列表中相同名称的令牌之前尝试获取独占锁的任何会话的令牌发生更改。

    前面的示例仅使用Version Tokens插件库中包含的一些函数，但还有其他函数。一组函数允许操作和检查服务器的版本令牌列表。另一组功能允许锁定和解锁版本令牌。

    这些功能允许创建、更改、删除和检查服务器的版本令牌列表：

-   [`版本_令牌_集合（）`](version-tokens-reference.html#function_version-tokens-set)完全替换当前列表并指定新列表。参数是一个以分号分隔的`*`名称`*=*`价值`*`对。

-   [`版本_令牌_编辑（）`](version-tokens-reference.html#function_version-tokens-edit)启用对当前列表的部分修改。它可以添加新令牌或更改现有令牌的值。参数是一个以分号分隔的`*`名称`*=*`价值`*`对。

-   [`版本_令牌_删除（）`](version-tokens-reference.html#function_version-tokens-delete)从当前列表中删除令牌。参数是一个以分号分隔的标记名列表。

-   [`版本_代币_show（）`](version-tokens-reference.html#function_version-tokens-show)显示当前令牌列表。这不需要争论。

    如果成功，每个函数都会返回一个二进制字符串，指示发生了什么操作。以下示例建立服务器令牌列表，通过添加新令牌对其进行修改，删除一些令牌，并显示生成的令牌列表：


```
mysql> SELECT version_tokens_set('tok1=a;tok2=b');
+-------------------------------------+
| version_tokens_set('tok1=a;tok2=b') |
+-------------------------------------+
| 2 version tokens set.               |
+-------------------------------------+
mysql> SELECT version_tokens_edit('tok3=c');
+-------------------------------+
| version_tokens_edit('tok3=c') |
+-------------------------------+
| 1 version tokens updated.     |
+-------------------------------+
mysql> SELECT version_tokens_delete('tok2;tok1');
+------------------------------------+
| version_tokens_delete('tok2;tok1') |
+------------------------------------+
| 2 version tokens deleted.          |
+------------------------------------+
mysql> SELECT version_tokens_show();
+-----------------------+
| version_tokens_show() |
+-----------------------+
| tok3=c;               |
+-----------------------+
```

如果令牌列表格式不正确，则会出现警告：

```
mysql> SELECT version_tokens_set('tok1=a; =c');
+----------------------------------+
| version_tokens_set('tok1=a; =c') |
+----------------------------------+
| 1 version tokens set.            |
+----------------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
  Level: Warning
   Code: 42000
Message: Invalid version token pair encountered. The list provided
         is only partially updated.
1 row in set (0.00 sec)
```

如前所述，版本标记是使用分号分隔的`*`名称`*=*`价值`*`对。考虑一下这个调用[`版本_令牌_集合（）`](version-tokens-reference.html#function_version-tokens-set):

```
mysql> SELECT version_tokens_set('tok1=b;;; tok2= a = b ; tok1 = 1\'2 3"4')
+---------------------------------------------------------------+
| version_tokens_set('tok1=b;;; tok2= a = b ; tok1 = 1\'2 3"4') |
+---------------------------------------------------------------+
| 3 version tokens set.                                         |
+---------------------------------------------------------------+
```

版本标记对参数的解释如下：

-   忽略名称和值周围的空白。允许在名称和值中使用空格。（用于[`版本_令牌_删除（）`](version-tokens-reference.html#function_version-tokens-delete)，它获取一个没有值的名称列表，名称周围的空格将被忽略。）

-   没有报价机制。

-   令牌的顺序并不重要，除非令牌列表包含给定令牌名称的多个实例，最后一个值优先于之前的值。

    鉴于这些规则，前面的[`版本_令牌_集合（）`](version-tokens-reference.html#function_version-tokens-set)调用会产生一个包含两个令牌的令牌列表：`tok1`有价值吗`1'2 3"4`和`tok2`有价值吗`a=b`.要验证这一点，请致电[`版本_代币_show（）`](version-tokens-reference.html#function_version-tokens-show):


```
mysql> SELECT version_tokens_show();
+--------------------------+
| version_tokens_show()    |
+--------------------------+
| tok2=a = b;tok1=1'2 3"4; |
+--------------------------+
```

如果令牌列表包含两个令牌，为什么[`版本_令牌_集合（）`](version-tokens-reference.html#function_version-tokens-set)返回值`3个版本令牌集`? 发生这种情况是因为原始令牌列表包含两个`tok1`，第二个定义取代了第一个定义。

Version Tokens token manipulation函数对令牌名称和值设置以下约束：

-   令牌名称不能包含`=`或`;`和的最大长度为64个字符。

-   令牌值不能包含`;`角色。值的长度受[`最大允许包数`](server-system-variables.html#sysvar_max_allowed_packet)系统变量。

-   版本标记将标记名称和值视为二进制字符串，因此比较区分大小写。

    版本令牌还包括一组功能，可以锁定和解锁令牌：

-   [`版本_代币_锁_独占（）`](version-tokens-reference.html#function_version-tokens-lock-exclusive)获取独占版本令牌锁。它需要一个包含一个或多个锁名和超时值的列表。

-   [`版本\令牌\锁定\共享（）`](version-tokens-reference.html#function_version-tokens-lock-shared)获取共享版本令牌锁。它需要一个包含一个或多个锁名和超时值的列表。

-   [`版本_代币_解锁（）`](version-tokens-reference.html#function_version-tokens-unlock)释放版本令牌锁（独占和共享）。这不需要争论。

    每个锁定函数返回非零表示成功。否则，会发生错误：


```
mysql> SELECT version_tokens_lock_shared('lock1', 'lock2', 0);
+-------------------------------------------------+
| version_tokens_lock_shared('lock1', 'lock2', 0) |
+-------------------------------------------------+
|                                               1 |
+-------------------------------------------------+

mysql> SELECT version_tokens_lock_shared(NULL, 0);
ERROR 3131 (42000): Incorrect locking service lock name '(null)'.
```

使用版本令牌锁定功能是建议性的；申请者必须同意合作。

可以锁定不存在的令牌名称。这不会创建令牌。

笔记

版本标记锁定功能基于中描述的锁定服务[第5.6.9.1节“锁定服务”](locking-service.html)，因此对共享锁和独占锁具有相同的语义。（版本令牌使用服务器内置的锁定服务例程，而不是锁定服务功能接口，因此不需要安装这些功能来使用版本令牌。）版本令牌获取的锁使用的锁定服务命名空间为`版本\u令牌\u锁`.锁定服务锁可以使用性能模式进行监控，因此版本令牌锁也是如此。有关详细信息，请参阅[锁定服务监控](locking-service.html#locking-service-monitoring).

对于版本令牌锁定函数，令牌名称参数完全按照指定的方式使用。周围的空白不会被忽略，也不会被忽略`=`和`;`允许使用字符。这是因为版本令牌只是将要锁定的令牌名称按原样传递给锁定服务。
