MySQL8-文档存储入门指南-二-

94 阅读59分钟

MySQL8 文档存储入门指南(二)

原文:Introducing the MySQL 8 Document Store

协议:CC BY-NC-SA 4.0

四、MySQL Shell

旧的 mysql 客户端(MySQL)最大的缺失之一是没有任何形式的脚本功能。但是,可以使用旧客户端处理一批 SQL 命令,并且客户端对编写存储例程(过程和函数)的支持有限。对于那些想要创建和使用脚本来管理他们的数据库(和服务器)的人来说,到目前为止已经有了外部工具选项,包括 MySQL Workbench 和 MySQL Utilities,但是没有专门用于合并多种脚本语言的工具。

MySQL Workbench 是 Oracle 的一款非常受欢迎的产品。MySQL Workbench 是一个 GUI 工具,设计为基于工作站的管理工具。它提供了许多功能,包括数据库设计和建模工具、SQL 开发、数据库管理、数据库迁移和 Python 脚本支持。有关 MySQL Workbench 的更多信息,请参见 http://dev.mysql.com/doc/workbench/en/

另一方面,MySQL Utilities 是一组 Python 工具,用于帮助维护和管理 MySQL 服务器,只需一个命令就可以完成许多步骤或复杂的脚本编写。有用于管理服务器、使用复制等的工具。对于那些想要编写自己的 Python 脚本的人,包含了一个 Python 类库。有关 MySQL 实用程序的更多信息,请参见 https://dev.mysql.com/doc/mysql-utilities/1.6/en/

Note

MySQL 实用程序目前仅限用于 MySQL 5.7。没有适用于 MySQL 8.0 或文档存储的版本。

除了这些产品之外,向 MySQL 客户端添加脚本语言的请求还没有得到回应。也就是说,直到现在。然而,Oracle 并没有重组现有的(并且相当长寿的)MySQL 客户端工具,而是发布了一个名为 MySQL Shell 的新客户端,它支持脚本语言、X DevAPI 以及 SQL 命令等等。但是新的 Shell 远不止这些。

在这一章中,我们将更详细地探讨 MySQL Shell。我们在第 3 章中看到了 shell 的运行,但在这一章中,我们将了解更多关于它的主要特性和选项,以及如何使用新的 shell 来交互式地执行脚本。正如您将看到的,MySQL Shell 是 MySQL 未来的另一个关键元素。

我建议在自己尝试 MySQL Shell 之前,至少通读一遍本章中的示例部分。所提供的信息将帮助您适应使用新的命令和连接,在您理解这些概念之前,这些命令和连接有时会有点混乱。

Note

我使用术语 shell 来指代 MySQL Shell 支持的特性或对象。我用 MySQL Shell 来指代产品本身。

入门指南

MySQL Shell 是 MySQL 产品组合中令人激动的新成员。MySQL Shell 代表了第一个连接到 MySQL 并与之交互的现代高级客户端。shell 可以用作脚本环境,用于开发处理数据的新工具和应用。尽管它支持 SQL 模式,但它的主要目的是允许用 JavaScript 和 Python 语言访问数据。没错;您可以编写 Python 脚本,并在 shell 中以交互方式或批处理方式执行它们。酷!

回想一下第 1 章,MySQL Shell 被设计成使用新的 X 协议通过 X 插件与服务器通信。然而,shell 也可以使用旧的协议连接到服务器,尽管在脚本模式下功能有限。这意味着,shell 允许您使用关系型(SQL)和/或 JSON 文档(NoSQL)。

SQL 模式的加入为学习如何使用脚本管理数据提供了一个很好的跳板。也就是说,您可以继续使用您的 SQL 命令(或批处理),直到您将它们转换成 JavaScript 或 Python。此外,您可以使用这两者来确保您的迁移是完整的。图 4-1 展示了一个启动 MySQL Shell 的例子。请注意显示 MySQL 徽标、连接信息和模式的漂亮提示符。很好!

A432285_1_En_4_Fig1_HTML.jpg

图 4-1

The MySQL Shell

以下各节从较高的层面介绍了 shell 的主要特性。我们不会探究每个特性或选项的每个细节,相反,本章提供了一个广泛的概述,以便您可以快速入门,更重要的是,了解关于 shell 的足够信息,以便您可以按照本书中的示例进行操作。

有关 MySQL Shell 的更多信息,请参见在线 MySQL 参考手册中标题为“MySQL Shell 用户指南”的部分。

特征

MySQL Shell 有许多特性,包括支持传统的 SQL 命令处理、脚本原型,甚至支持定制 Shell。下面我列出了 shell 的一些主要特性。大多数功能都可以通过命令行选项或特殊的 shell 命令来控制。在后面的章节中,我将深入探讨一些更重要的特性。

  • 日志记录:您可以创建一个会话日志,供以后分析或保存消息记录。您可以使用--log-level选项设置详细程度,范围从 1(无记录)到 8(最大调试)。
  • 输出格式:shell 支持三种格式选项:table ( --table),这是您从旧客户端习惯的传统网格格式;选项卡式,使用制表符分隔显示信息,用于批处理执行;以及 JSON ( --json),它以更易于阅读的方式格式化 JSON 文档。这些是您在启动 shell 时指定的命令行选项。
  • 交互式代码执行:使用 shell 的默认模式是交互式模式,它像传统的客户机一样工作,在这里输入命令并获得响应。
  • 批处理代码执行:如果您想在没有交互式会话的情况下运行脚本,您可以使用 shell 以批处理模式运行脚本。但是,输出仅限于非格式化输出(但可以用--interactive选项覆盖)。
  • 脚本语言:shell 支持 JavaScript 和 Python,尽管您一次只能使用一种。
  • 会话:会话本质上是到服务器的连接。shell 允许您存储和删除会话。我们将在后面的章节中看到更多关于会话的内容。
  • 启动脚本:您可以定义一个在 shell 启动时执行的脚本。您可以用 JavaScript 或 Python 编写脚本。
  • 命令历史和命令完成:shell 保存您输入的命令,允许您使用上下箭头键调用它们。shell 还为已知的关键字、API 函数和 SQL 关键字提供代码补全。
  • 全局变量:shell 提供了一些在交互模式下可以访问的全局变量。其中包括以下内容:
    • session:全局会话对象(如果已建立)
    • db:通过连接建立的模式
    • dba:用于使用 InnoDB 集群的 AdminAPI 对象
    • shell:使用 Shell 的通用功能
    • util:与服务器一起工作的实用功能
  • 定制提示:您还可以通过使用特殊格式更新名为~/.mysqlsh/prompt.json的配置文件或者定义名为MYSQLSH_PROMPT_THEME的环境变量来更改默认提示。请参阅 MySQL Shell 参考手册,了解有关更改提示符的更多详细信息。
  • 自动完成:从 8.0.4 开始,shell 允许用户在 SQL 模式下按 TAB 键自动完成关键字,在 JavaScript 和 Python 模式下自动完成主要的类和方法。

Shell 命令

与最初的 MySQL 客户端一样,有一些特殊的命令控制应用本身,而不是与数据交互(通过 SQL 或 X DevAPI)。要执行 shell 命令,请发出带斜杠()的命令。例如,\help打印所有 shell 命令的帮助。表 4-1 列出了一些更常用的 shell 命令。

表 4-1

Shell Commands

| 命令 | 捷径 | 描述 | | :-- | :-- | :-- | | `\` |   | 开始多行输入(仅限 SQL 模式) | | `\connect` | (`\c`) | 连接到服务器 | | `\help` | (`\?`,`\h`) | 打印帮助文本 | | `\js` |   | 切换到 JavaScript 模式 | | `\nowarnings` | (`\w`) | 不显示警告 | | `\py` |   | 切换到 Python 模式 | | `\quit` | (`\q`,`\exit`) | 放弃 | | `\source` | (`\.`) | 执行指定的脚本文件 | | `\sql` |   | 切换到 SQL 模式 | | `\status` | (`\s`) | 打印有关连接的信息 | | `\use` | (`\u`) | 设置会话的模式 | | `\warnings` | (`\W`) | 在每个语句后显示警告 |

注意,您可以使用\sql\js\py shell 命令来动态切换模式。这使得处理 SQL 和 NoSQL 数据更加容易,因为您不必退出应用来切换模式。此外,即使使用了启动选项来设置模式,也可以使用这些 shell 命令。

Tip

要获得任何 shell 命令的帮助,请使用\help命令。例如,要了解更多关于\connect命令的信息,请输入\help connect

最后,注意您退出 shell 的方式(\q\quit)。如果您像以前一样在旧客户端中键入 quit,shell 将根据您所处的模式做出不同的响应。以下是每种模式下发生的情况的示例。

MySQL  SQL > quit;
ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'quit' at line 1
 MySQL  SQL > \js
Switching to JavaScript mode...
 MySQL  JS > quit
ReferenceError: quit is not defined
 MySQL  JS > \py
Switching to Python mode...
 MySQL  Py > quit
Use quit() or Ctrl-D (i.e. EOF) to exit
 MySQL  Py > \q
Bye!

如果您习惯了旧的 MySQL 客户端,并且不小心使用了旧的客户端命令,您可能会看到类似的奇怪现象,但是只需要经常使用它就可以提醒您要使用的正确命令。现在,让我们看看 shell 的启动选项。

Note

与需要服务器连接才能启动的旧客户端不同,当您在没有指定服务器连接的情况下启动 shell 时,shell 将会运行,但它没有连接到服务器。您必须使用\connect shell 命令来连接到服务器。

选择

可以使用几个控制模式、连接、行为等的启动选项来启动 shell。本节介绍一些您可能想要使用的更常用的选项。我们将在后面的章节中看到更多关于连接选项的内容。表 4-2 显示了常见 Shell 选项的列表。

表 4-2

Common MySQL Shell Options

| [计]选项 | 描述 | | :-- | :-- | | `-f, --file=file` | 处理要执行的文件 | | `-e, --execute=` | 执行命令并退出 | | `--uri` | 通过统一资源标识符(URI)连接 | | `-h, --host=name` | 用于连接的主机名 | | `-P, --port=#` | 用于连接的端口号 | | `-S, --socket=sock` | UNIX 中用于连接的套接字名称或 Windows 中的命名管道名称(仅限经典会话) | | `-u, --dbuser=name` | 用于连接的用户 | | `--user=name` | dbuser 的别名 | | `--dbpassword=name` | 连接到服务器时使用的密码 | | `--password=name` | dbpassword 的别名 | | `-p` | 请求密码提示以设置密码 | | `-D --schema=name` | 要使用的架构 | | `--database=name` | `--schema`的别名 | | `--sql` | 以 SQL 模式启动 | | `--sqlc` | 使用经典会话在 SQL 模式下启动 | | `--sqlx` | 使用创建 X 协议会话在 SQL 模式下启动 | | `--js` | 以 JavaScript 模式启动 | | `--py` | 以 Python 模式启动 | | `--json` | 以 JSON 格式生成输出 | | `--table` | 以表格格式生成输出(默认为交互模式) | | `-i, --interactive[=full]` | 为了在批处理模式下使用,它强制模拟交互模式处理。批处理中的每一行都像在交互模式下一样进行处理。 | | `--log-level=value` | 日志级别;值必须是 1 到 8 之间的整数或[`none`、`internal`、`error`、`warning`、`info`、`debug`、`debug2`、`debug3`中的任意一个] | | `--mx --mysqlx` | 创建一个 X 协议会话(简称为会话) | | `--mc --mysql` | 创建经典(旧协议)会话 | | `--ma` | 使用自动协议选择创建会话 | | `--nw, --no-wizard` | 禁用执行脚本的向导模式(非交互式)。 | | `--ssl-mode` | 为连接启用 SSL(使用其他标志自动启用) | | `--ssl-key=name` | PEM 格式的 X509 密钥 | | `--ssl-cert=name` | PEM 格式的 X509 证书 | | `--ssl-ca=name` | PEM 格式的 CA 文件(查看 OpenSSL 文档) | | `--ssl-capath=dir` | CA 目录。 | | `--ssl-cipher=name` | 要使用的 SSL 密码。 | | `--ssl-crl=name` | 证书吊销列表。 | | `--ssl-crlpath=dir` | 证书吊销列表路径。 | | `--tls-version=version` | 要使用的 TLS 版本,允许的值为:TLSv1,TLSv1.1。 | | `--auth-method=method` | 要使用的身份验证方法。 | | `--dba=enableXProtocol` | 在连接到的服务器中启用 X 协议。必须和`--mysql`一起使用。 |

请注意,有些选项的别名与原始客户端的用途相同。如果您有启动客户机来执行操作的脚本,这使得切换到 shell 变得更容易一些。请注意,还有一组使用安全套接字层(SSL)连接的选项。

其中大部分都是不言自明的,我们以前也见过一些。现在让我们看看可用的会话和连接以及如何使用它们。

要获得选项的完整列表,请使用- help 选项执行 shell,如下所示。

$ mysqlsh --help
MySQL Shell 8.0.11

Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners.

Usage: mysqlsh [OPTIONS] [URI]
       mysqlsh [OPTIONS] [URI] -f <path> [script args...]
       mysqlsh [OPTIONS] [URI] --dba [command]
       mysqlsh [OPTIONS] [URI] --cluster

  -?, --help                  Display this help and exit.
  -e, --execute=<cmd>         Execute command and quit.
  -f, --file=file             Process file.
  --uri=value                 Connect to Uniform Resource Identifier. Format:
                              [user[:pass]@]host[:port][/db]
  -h, --host=name             Connect to host.
  -P, --port=#                Port number to use for connection.
  -S, --socket=sock           Socket name to use in UNIX, pipe name to use in
                              Windows (only classic sessions).
  -u, --dbuser=name           User for the connection to the server.
  --user=name                 see above
  -p, --password[=name]       Password to use when connecting to server.
  --dbpassword[=name]         see above
  -p                          Request password prompt to set the password
  -D, --schema=name           Schema to use.
  --database=name             see above
  --recreate-schema           Drop and recreate the specified schema.Schema
                              will be deleted if it exists!
  -mx, --mysqlx               Uses connection data to create Creating an X
                              protocol session.
  -mc, --mysql                Uses connection data to create a Classic Session.
  -ma                         Uses the connection data to create the session
                              withautomatic protocol detection.
...

会话和模式

与最初的客户机以及实际上大多数 MySQL 客户机应用一样,您需要连接到 MySQL 服务器,以便可以运行命令。MySQL Shell 支持多种连接 MySQL 服务器的方式和多种与服务器交互的选项(称为会话)。在会话中,您可以更改 shell 接受命令的方式(称为模式),以包括 SQL、JavaScript 或 Python 命令。

考虑到使用服务器的所有不同的新概念,那些初学使用 shell 的人可能会发现其中的细微差别,甚至有时会感到困惑。事实上,在线 MySQL Shell 参考手册和各种博客及其他报告有时会互换使用模式和会话,但正如您将看到的,它们是不同的(无论多么微妙)。下面几节阐明了每个主要概念,包括会话、模式和连接,以便您可以更快地适应新方法。我首先用一些简单的例子介绍概念,然后用例子详细讨论如何建立连接。让我们从查看可用的会话对象开始。

会话对象

关于会话,首先要理解的是,会话是到单个服务器的连接。第二件要理解的事情是,每个会话可以使用两个会话对象中的一个来启动,这两个会话对象公开了一个特定的对象,用于使用特定的通信协议与 MySQL 服务器一起工作。也就是说,会话是到服务器的连接(定义了所有参数),会话对象是 shell 用来以几种方式之一与服务器进行交互的对象。更具体地说,MySQL Shell 会话对象简单地定义了如何与服务器交互,包括支持什么模式,甚至 Shell 如何与服务器通信。shell 支持如下两个会话对象:

  • 会话:X 协议会话用于应用开发,支持 JavaScript、Python 和 SQL 模式。通常用于开发脚本或执行脚本。要使用该选项启动 shell,请使用--mx ( --mysqlx)选项。
  • 经典会话:使用旧的服务器通信协议,对 DevAPI 的支持非常有限。对没有 X 插件或不支持 X 协议的旧服务器使用这种模式。通常用于旧服务器的 SQL 模式。要使用该选项启动 shell,请使用--mc ( --mysqlc)选项。

Note

经典会话仅在 MySQL Shell 中可用。它不是 X DevAPI 的一部分。通过 X DevAPI,只有通过 X 协议的会话连接是可用的。

当您使用\connect shell 命令时,您可以通过指定-mc用于经典会话、-mx用于 X 协议会话或-ma用于自动协议选择来指定要使用的会话对象(协议)。下面依次展示了其中的每一个。注意指定了一个统一的资源标识符。

  • \connect -mx <URI>:使用 X 协议(会话)
  • \connect -mc <URI>:使用经典协议(经典会话)
  • \connect -ma <URI>:使用自动协议选择

召回会话大致等同于连接。但是,会话不仅仅是一个连接,因为它包含了用于建立连接的所有设置(包括会话对象)以及服务器使用的通信协议。因此,我们有时会遇到描述会话的术语“协议”。我们将在后面的章节中看到更多使用会话的例子。

Wait, What Session Was That???

您可能会看到使用多个名称描述的会话。特别地,正常的、默认的会话被称为会话,X 协议会话,或者更少见的,X 会话。这些是指通过 X 协议与 MySQL 通信的会话对象(连接)。较旧的服务器通信协议在称为经典会话的会话中受支持,经典,或更罕见地,旧协议。这些是指通过旧协议与 MySQL 服务器通信的会话对象(连接)。可悲的是,这些多重名字会使阅读不同的文本成为一种挑战。每当使用这些替代术语时,您应该努力阅读会话和经典会话。

有关以编程方式使用会话的更多信息,请参见在线 MySQL Shell 参考手册。

支持的模式

shell 支持三种模式(也称为语言支持或简称为活动语言);SQL、JavaScript 和 Python。回想一下,我们可以通过使用 shell 命令来启动这些模式中的任何一种。你可以随时切换模式(语言),每次都不会断线。下面列出了三种模式以及如何切换到每种模式。

  • \sql:切换到 SQL 语言
  • \js:切换到 JavaScript 语言(默认模式)
  • \py:切换到 Python 语言

现在我们已经了解了会话、会话对象、模式,我们可以看看如何连接 MySQL 服务器。

连接

在 shell 中建立连接可能需要一些时间来适应与最初的 MySQL 客户端不同的做法。 1 你可以使用一个特殊格式的 URI 字符串或者通过名字使用单独的选项连接到一个服务器(像旧的客户端)。也支持 SSL 连接。可以通过启动选项、shell 命令和脚本来建立连接。但是,所有连接都需要使用密码。因此,除非您另外声明,否则如果没有给出密码,shell 将提示您输入密码。

Note

如果您想使用没有密码的连接(不推荐),您必须使用--password选项,或者,如果使用 URI,包括一个额外的冒号来代替密码。

下面不是讨论所有可用的连接方式和选项,而是在下面的部分中给出每种连接方式的一个示例。

使用 URI

在 MySQL Shell 连接的情况下,URI 是使用以下格式编码的特殊字符串:<dbuser>[:<dbpassword>]@host[:port][/schema/]其中< >表示各种参数的字符串值。请注意,密码、端口和模式是可选的,但用户和主机是必需的。在这种情况下,Schema 是连接时要使用的默认模式(数据库)。

Note

X 协议的默认端口是 33060。

要在启动 shell 时使用命令行上的 URI 连接到服务器,请使用如下的--uri选项指定它。

$ mysqlsh --uri root:secret@localhost:33060

shell 假定所有连接都需要密码,如果没有提供密码,它将提示输入密码。 2 清单 4-1 显示了先前在没有密码的情况下进行的相同连接。注意 shell 是如何提示输入密码的。

Tip

world_x 数据库是一个示例数据库,您可以从 https://dev.mysql.com/doc/index-other.html 下载。

$ mysqlsh --uri root@localhost:33060/world_x
Creating a session to 'root@localhost:33060/world_x'
Enter password:
Fetching schema names for autocompletion... Press ^C to stop.
Your MySQL connection id is 13 (X protocol)
Server version: 8.0.11 MySQL Community Server (GPL)
Default schema `world_x` accessible through db.
MySQL Shell 8.0.11

Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type '\help' or '\?' for help; '\quit' to exit.

 MySQL  localhost:33060+  world_x  JS >

Listing 4-1Connecting with a URI

注意,我还在 URI 中用/schema选项指定了默认模式(world_x)。

使用单个选项

您还可以使用单独的选项在 shell 命令行上指定连接。可用的连接选项如表 4-1 所示。为了向后兼容(并使向 MySQL Shell 的过渡更容易),Shell 还支持用--user代替--dbuser,用--password代替--dbpassword,用--database代替--schema。清单 4-2 展示了如何使用单独的选项连接到 MySQL 服务器。

$ mysqlsh --dbuser root --host localhost --port 33060 --schema world_x --py -mx
Creating an X protocol session to 'root@localhost:33060/world_x'
Enter password:
Fetching schema names for autocompletion... Press ^C to stop.
Your MySQL connection id is 14 (X protocol)
Server version: 8.0.11 MySQL Community Server (GPL)
Default schema `world_x` accessible through db.
MySQL Shell 8.0.11

Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type '\help' or '\?' for help; '\quit' to exit.

 MySQL  localhost:33060+  world_x  Py >

Listing 4-2Connecting Using Individual Options

注意,我用--py选项将模式(语言)改为 Python。

在脚本中使用连接

如果您计划使用 shell 来创建脚本或者仅仅作为一个原型工具,那么您也将希望在脚本中使用会话。在这种情况下,我们将创建一个变量来包含获取的会话。以这种方式创建的会话称为全局会话,因为一旦创建,它就可用于任何模式。

然而,根据我们使用的会话对象(回想一下这是经典或 X 协议),我们将使用不同的方法创建一个 X 或经典会话。我们对 X 协议会话对象使用getSession()方法,对经典会话对象使用getClassicSession()方法。

Tip

如果你想知道更多关于 MySQL Shell 的内部信息,包括更多关于mysqlmysqlx模块的信息,请看 http://dev.mysql.com/doc/dev/mysqlsh-devapi/

下面演示了在 JavaScript 中获取 X 协议会话对象。注意,我在 URI 中将密码指定为方法参数。

 MySQL  JS > var js_session = mysqlx.getSession('root@localhost:33060', 'secret')
 MySQL  JS > print(js_session)
<Session:root@localhost:33060>

下面演示了如何在 JavaScript 中获取经典会话对象。

 MySQL  JS > var js_session = mysql.getClassicSession('root@localhost:3306', 'secret')
 MySQL  JS > print(js_session)
<ClassicSession:root@localhost:3306>

What Happened To Port 3306?

如果您一直关注本节中的示例,您可能已经注意到我们使用的端口是 33060。这不是印刷错误。默认情况下,X 插件监听端口 33060,而不是服务器原来默认的端口 3306。事实上,端口 3306 仍然是旧协议的默认端口,您可以使用端口 3306 连接到服务器,但是您必须使用经典会话(mysqlsh-classic-ur oot-hlocalhost-port = 3306)。虽然这表明您可以使用旧的协议连接到服务器,但是回想一下,它确实限制了您可以做的事情,因为 DevAPI 在经典会话对象中并不完全受支持。

使用 SSL 连接

您还可以创建 SSL 连接,以便安全地连接到您的服务器。要使用 SSL,您必须将服务器配置为使用 SSL。要在运行 MySQL 的同一台机器上使用 SSL,可以使用--ssl-mode=REQUIRED选项。您也可以指定 SSL 选项,如表 4-1 所示。您可以使用命令行选项在命令行上指定它们,或者将其作为\connect shell 命令的扩展。下面显示了如何使用 SSL 和命令行选项连接到服务器。

$ mysqlsh -uroot -h127.0.0.1 --port=33060 --ssl-mode=REQUIRED

Tip

有关加密连接的更多详细信息,请参见在线 MySQL Shell 参考手册中的“使用加密连接”一节。

现在我们知道了如何连接到我们的服务器,让我们回顾一下如何设置和安装 shell,更重要的是,确保 X 插件设置正确。

设置和安装

回想一下第 2 章,我们需要将 MySQL Shell 作为独立于服务器的产品进行安装。我们还必须在服务器中启用 X 插件。以下部分演示了安装 MySQL Shell 所需的步骤,以及如何配置 X 插件以供使用。虽然我们在第二章中看到了一个关于如何安装 X 插件的简短例子,但是这一节将会更详细地介绍如何使用 MySQL Shell 自动安装 X 插件。

Caution

如果您正在安装 MySQL Shell 8 . 0 . 4 版或更高版本,以便与 MySQL Server 8 . 0 . 4 版或更高版本一起使用,您将使用新的caching_sha2_password身份验证插件来使用 SSL 连接。默认情况下,这通常在安装过程中完成,但是如果您安装的服务器没有自动安装,或者您使用的是旧版本的服务器,则可能需要将服务器配置为使用 SSL 连接。更多信息请参见在线 MySQL 参考手册,或者更多关于认证默认值变更的信息,请阅读 https://mysqlserverteam.com/mysql-8-0-4-new-default-authentication-plugin-caching_sha2_password 的工程博客。

安装 MySQL Shell

安装 MySQL Shell 遵循与安装 MySQL 服务器相同的模式。也就是说,您可以简单地下载适用于您的平台的安装程序,并通过单击对话框面板来安装它。然而,有一个例外。在撰写本文时,MySQL Shell 的最新版本不是 MySQL Windows Installer 的一部分。

你可以在 http://dev.mysql.com/downloads/shell/ 上找到安装包。只需为您的平台选择最新的版本和包(在本例中是 macOS)并安装 shell。

当您启动installer (.pkg or .dmg)时,会出现一个欢迎对话框,其中包含您要安装的产品的名称和版本。图 4-2 显示了 MySQL Shell 安装程序的欢迎面板。

A432285_1_En_4_Fig2_HTML.jpg

图 4-2

Installer welcome panel

请注意,在图 4-2 中,我正在安装 MySQL Shell 的发布候选版本,即版本 8.0.11。您应该安装适用于您的平台的最新版本的 shell,以确保您拥有最新的特性。

准备好后,点按“继续”。然后,您将看到如图 4-3 所示的最终用户许可协议。

A432285_1_En_4_Fig3_HTML.jpg

图 4-3

License panel

阅读完许可证后, 3 点击继续。您将被要求接受如图 4-4 所示的许可。单击同意继续。

A432285_1_En_4_Fig4_HTML.jpg

图 4-4

Destination folder panel

一旦您接受了许可,并同意安装在默认位置(对于 macOS 来说,这总是一个好主意),请点按“继续”。将要求您批准安装,如图 4-5 所示。准备好开始安装时,单击安装。

Tip

在 Windows 上安装时,Windows 可能会要求您批准升级安装。

A432285_1_En_4_Fig5_HTML.jpg

图 4-5

Installation panel

这将开始将文件以及系统上的设置复制到目标位置,以确保您可以正确启动应用。根据您系统的速度,最多只需要 2 到 3 分钟就可以完成。

一旦安装完成,您将看到一个如图 4-6 所示的完成对话框。准备就绪后,单击关闭以完成安装。如果您选择启动 shell,您将看到一个新的命令窗口打开,shell 将启动。

回想一下,您可以在不指定服务器的情况下启动 shell,shell 将会运行,但它不会连接到任何 MySQL 服务器。如果您没有在命令行上指定服务器连接(URI 或单个选项),您必须使用\connect shell 命令来连接到服务器。

A432285_1_En_4_Fig6_HTML.jpg

图 4-6

Installation complete

现在 MySQL Shell 已经安装好了,我们需要配置 X 插件。

安装 X 插件

如果您在系统上安装了 MySQL 8.0.11 或更高版本,那么您已经安装并启用了 X 插件。然而,默认情况下,一些较旧的安装不会设置或启用 X 插件。因此,您可能需要启用插件来使用 shell 连接到您的服务器。尽管您仍然可以使用 shell 通过经典会话对象进行连接,但是在启用 X 插件之前,您将无法使用 X 协议会话对象。

此外,如果您使用 Windows Installer 在 Windows 上安装了服务器,则可以在安装过程中通过选中启用 X 协议/MySQL 作为文档存储复选框来启用 X 插件。如果你没有这样做或者安装在不同的平台上,至少有两种方法可以启用 X 插件;您可以使用新的 MySQL Shell,也可以使用旧的客户端。下面演示了每个选项。

Tip

如果在新安装的 MySQL 上连接到 MySQL 服务器有问题,请确保启用 X 插件,如本节所示。

使用 MySQL Shell 启用 X 插件

要使用 MySQL Shell 启用 X 插件,使用用户和主机的单独选项启动一个经典会话,并指定如下所示的--mysql--dba enableXProtocol选项。我们使用经典的会话对象,因为我们还没有启用 X 协议。

$ mysqlsh -uroot -hlocalhost --mysql --dba enableXProtocol
Creating a Classic session to 'root@localhost'
Enter password:
Fetching schema names for autocompletion... Press ^C to stop.
Your MySQL connection id is 285
Server version: 8.0.11 MySQL Community Server (GPL)
No default schema selected; type \use <schema> to set one.
enableXProtocol: Installing plugin mysqlx...
enableXProtocol: done

使用 MySQL 客户端启用 X 插件

要使用旧的 MySQL 客户端启用 X 插件,您必须连接到服务器并手动安装插件。也就是没有新的魔法命令选项为你开启。这包括使用清单 4-3 中所示的INSTALL PLUGIN SQL 命令。

$ mysql -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 343
Server version: 8.0.11 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> INSTALL PLUGIN mysqlx SONAME 'mysqlx.so';
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW PLUGINS \G
*************************** 1\. row ***************************
   Name: keyring_file
 Status: ACTIVE
   Type: KEYRING
Library: keyring_file.so
License: GPL

...

*************************** 43\. row ***************************
   Name: mysqlx
 Status: ACTIVE
   Type: DAEMON
Library: mysqlx.so
License: GPL
43 rows in set (0.00 sec)

Listing 4-3Enabling the X Plugin Using the MySQL Client

注意,我使用了SHOW PLUGINS SQL 命令来列出该命令前后安装的插件。为了清楚起见,我省略了一些冗长的输出。

Tip

您可以使用经典会话对象在 shell 中执行这些操作。对于习惯使用旧客户机的读者,我展示了使用旧客户机的命令。

有趣的是,您也可以使用如下的UNINSTALL PLUGIN SQL 命令卸载插件。如果您需要使用 X 协议来诊断连接,或者想要使用 MySQL Shell 仅使用经典会话对象来测试脚本,这可能会很有帮助。

mysql> UNINSTALL PLUGIN mysqlx;
Query OK, 0 rows affected (0.80 sec)

现在,让我们通过在三种模式(SQL、JavaScript 和 Python)下执行一个简单任务的演示来看看 MySQL Shell 的运行情况。

教程:MySQL Shell 示例

下面几节演示了如何在这三种模式下使用 MySQL shell。这个例子是在world_x数据库中插入新数据。将简要概述通过 shell 内置的 X DevAPI 对象,以及如何开始安装示例数据库。

本教程旨在提供一个完整的示例,展示如何使用 MySQL Shell 在所有支持的模式(语言)下解决一个任务。因此,我们将看到使用 SQL、JavaScript 和 Python 命令执行相同的任务。

任务是在数据库中插入新数据,然后进行搜索以检索满足包含新数据的条件的行。我使用关系表来说明这些概念,因为这对我们这些熟悉“普通”数据库操作的人来说更容易。然而,我们将在后面的章节中看到如何在文档存储中处理纯文档(集合)。

每个讲座都以一个示例开始,介绍如何连接到服务器,了解服务器支持什么(存在什么数据库),如何插入新数据,以及如何查询数据。正如您将看到的,有些命令非常不同,但它们都产生相同的结果。虽然所示的 SQL 命令对大多数读者来说都很熟悉,但是我在这里包含它们是为了说明如何将这些命令与您选择的脚本语言等同起来。

Note

回想一下从第 3 章开始,在 shell 中开始写脚本并不是需要成为一个 JavaScript 高手甚至是一个 python ista4T5。事实上,你需要做的大部分事情都可以通过本书和在线 MySQL Shell 参考手册中的例子找到。

我们将看到 JavaScript 和 Python 的操作与关系表上的 CRUD 操作一起工作。因此,我们不使用集合;相反,我们使用一个包含 JSON 数据类型列的关系表。我们将看到插入数据(创建)、选择数据(读取)、更新数据(更新)和删除数据(删除)的示例。

在开始我们的旅程之前,让我们花点时间安装我们将需要的示例数据库,Oracle 的world_x示例 MySQL 数据库。

安装示例数据库

Oracle 提供了几个示例数据库,供您在测试和开发应用时使用。样本数据库可以从 http://dev.mysql.com/doc/index-other.html 下载。我们想要使用的示例数据库被命名为world_x,以表明它包含 JSON 文档,并打算用 X DevAPI、shell 等进行测试。继续前进,导航到该页面并下载数据库。

示例数据库包含几个关系表(countrycitycountrylanguage)以及一个集合(countryinfo)。在本章中我们将只使用关系表,但是在后面的章节中将会看到更多使用集合的例子。

下载完文件后,解压缩并记下文件的位置。我们进口的时候你会需要的。接下来,启动 MySQL Shell 并连接到您的服务器。使用\sql shell 命令切换到 SQL 模式,然后使用\source shell 命令读取world_x.sql文件并处理其所有语句。

清单 4-4 显示了您应该看到的命令和响应的摘录。我在输出中突出显示了命令和一行,以表明这个 world 数据库确实允许在一个表中存储 JSON 文档。

 MySQL  JS > \connect root@localhost:33060
Creating a session to 'root@localhost:33060'
Enter password:
Your MySQL connection id is 9 (X protocol)
Server version: 8.0.11 MySQL Community Server (GPL)
No default schema selected; type \use <schema> to set one.
 MySQL  localhost:33060+ ssl  JS > \sql
Switching to SQL mode... Commands end with ;
 MySQL  localhost:33060+ ssl  SQL > \source /Users/cbell/Downloads/world_x-db/world_x.sql

...

Query OK, 0 rows affected (0.00 sec)

MySQL  localhost:33060+ ssl  SQL > SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| animals            |
| contact_list1      |
| contact_list2      |
| contact_list3      |
| greenhouse         |
| information_schema |
| library_v1         |
| library_v2         |
| library_v3         |
| mysql              |
| performance_schema |
| rolodex            |
| sys                |
| test               |
| world_x            |
+--------------------+
15 rows in set (0.00 sec)
 MySQL  localhost:33060+ ssl  SQL > USE world_x;
Query OK, 0 rows affected (0.00 sec)
 MySQL  localhost:33060+ ssl  SQL > SHOW TABLES;
+-------------------+
| Tables_in_world_x |
+-------------------+
| city              |
| country           |
| countryinfo       |
| countrylanguage   |
+-------------------+
4 rows in set (0.00 sec)
MySQL  localhost:33060+ ssl  SQL > EXPLAIN city;
+-------------+----------+------+-----+---------+----------------+
| Field       | Type     | Null | Key | Default | Extra          |
+-------------+----------+------+-----+---------+----------------+
| ID          | int(11)  | NO   | PRI | NULL    | auto_increment |
| Name        | char(35) | NO   |     |         |                |
| CountryCode | char(3)  | NO   |     |         |                |
| District    | char(20) | NO   |     |         |                |
| Info        | json     | YES  |     | NULL    |                |
+-------------+----------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

Listing 4-4Installing the world_x Database in SQL Mode

注意,\source shell 命令是一种加载文件并批量执行命令的方式。这是一种非常流行的重放常用命令序列的方法,它也适用于 JavaScript 和 Python 命令。

Tip

如果文件的路径中有空格,应该用双引号将路径括起来。

还可以在命令行上使用- recreate-schema 选项安装示例数据库,如下所示。请注意,如果数据库已经存在,这将删除并重新创建数据库。这是批处理运行 SQL 命令的另一个例子。

$ mysqlsh -uroot -hlocalhost --sql --recreate-schema --schema=world_x < ~/Downloads/world_x-db/world_x.sql
Enter password:
Recreating schema world_x...

当然,您可以使用 similar source 命令在旧客户机上安装 sample 数据库,但是这有什么意思呢?

现在,让我们看看 SQL 模式下的示例任务。

结构化查询语言

我们要做的任务是在 city 表中插入两行,在每一行中添加一个 JSON 文档,然后从表中读取数据,只读取那些有额外数据的行。更具体地说,我们将向表中添加一个名胜古迹列表,以便我们稍后可以询问哪些城市有名胜古迹。你可以把它当作一种方式,来添加你自己对那些你觉得有趣并会推荐给他人的城市中你去过的地方的评论。

因为本练习是一个示例,所以我们还将看到如何删除我们添加的数据,以便将数据库恢复到其原始状态。如果您计划按照这些示例进行操作,以便完成一个示例不会影响下一个示例的尝试,那么这样做也是有帮助的。

让我们首先列出服务器上的数据库,然后列出world_x数据库中的表。清单 4-5 显示了完成这些步骤的熟悉的 SQL 命令的副本。为了简洁起见,我省略了一些消息。请注意,我使用命令选项在 SQL 模式下启动了 shell。

$ mysqlsh -uroot -hlocalhost --sql
Creating a session to 'root@localhost'
Enter password:
Your MySQL connection id is 13 (X protocol)
Server version: 8.0.11 MySQL Community Server (GPL)
No default schema selected; type \use <schema> to set one.
MySQL Shell 8.0.11

Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type '\help' or '\?' for help; '\quit' to exit.

 MySQL  localhost:33060+ ssl  SQL > SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| animals            |
| contact_list1      |
| contact_list2      |
| contact_list3      |
| greenhouse         |
| information_schema |
| library_v1         |
| library_v2         |
| library_v3         |
| mysql              |
| performance_schema |
| rolodex            |
| sys                |
| test               |
| world_x            |
+--------------------+
15 rows in set (0.00 sec)
 MySQL  localhost:33060+ ssl  SQL > USE world_x;
Query OK, 0 rows affected (0.00 sec) 

 MySQL  localhost:33060+ ssl  SQL > SHOW TABLES;
+-------------------+
| Tables_in_world_x |
+-------------------+
| city              |
| country           |
| countryinfo       |
| countrylanguage   |
+-------------------+
4 rows in set (0.00 sec)                                ... ;
Query OK, 0 rows affected (0.00 sec)
 MySQL  localhost:33060+ ssl  SQL > SHOW TABLES;
+-------------------+
| Tables_in_rolodex |
+-------------------+
| contacts          |
+-------------------+
1 row in set (0.00 sec)

Listing 4-5Listing and Using Databases—SQL Mode

接下来,我们插入一些数据。我们将在表中插入两行;我最近去过的每个城市都有一个(北卡罗来纳州的夏洛特和佛罗里达州的代托纳)。在这一步中,我们将使用INSERT SQL 命令来插入数据。回想一下前面的内容,我们需要仔细格式化 JSON 文档,这样我们就不会遇到错误。特别是,我们希望添加包括姓名、国家代码和地区的结构化数据,但是我们还希望添加一个 JSON 文档,其中包含人口和名胜古迹的列表(数组)。回想一下第 1 章,我们可以在INSERT语句中通过内联创建 JSON 文档来做到这一点。下面显示了我们用来插入行的每个命令。

INSERT INTO world_x.city (Name, CountryCode, District, Info) VALUES ('Charlotte', 'USA', 'North Carolina', '{"Population": 792862, "Places_of_interest": [{"name": "NASCAR Hall of Fame"}, {"name": "Charlotte Motor Speedway"}]}');

INSERT INTO world_x.city (Name, CountryCode, District, Info) VALUES ('Daytona', 'USA', 'Florida', '{"Population": 590280, "Places_of_interest": [{"name": "Daytona Beach"}, {"name": "Motorsports Hall of Fame of America"}, {"name": "Daytona Motor Speedway"}]}');

Caution

不要在 JSON 文档的键名中使用空格。SQL 函数无法正确识别包含空格的键。

尽管这看起来有点混乱(确实如此),但是如果您仔细阅读这些语句,您会看到 JSON 文档被编码为一个字符串。例如,第一次插入的 JSON 文档的格式良好的版本如下所示。很明显,这更容易阅读。您可以使用这样的格式输入语句,但是显示的结果没有额外的格式。

注意,我们保留了表中其他行的 population 键(选择一些并查看),我们还添加了一个名为Places_of_interest的数组来列出我们可能想去的地方。

{
  "Population": 792862,
  "Places_of_interest": [
    {
      "name": "NASCAR Hall of Fame"
    },
    {
      "name": "Charlotte Motor Speedway"
    }
  ]
}

Note

为了简洁起见,我从示例中截断了表格格式行(虚线)。

现在,让我们看看如果使用一个SELECT SQL 语句,数据会是什么样子。在这种情况下,我们将只按城市名选择两行,因为它们在表中是唯一的。以下是结果摘录。

MySQL  localhost:33060+ ssl  SQL > SELECT * FROM city WHERE Name in ('Charlotte', 'Daytona') \G
*************************** 1\. row ***************************
         ID: 3818
       Name: Charlotte
CountryCode: USA
   District: North Carolina
       Info: {"Population": 540828}
*************************** 2\. row ***************************
         ID: 4080
       Name: Charlotte
CountryCode: USA
   District: North Carolina
       Info: {"Population": 792862, "Places_of_interest": [{"name": "NASCAR Hall of Fame"}, {"name": "Charlotte Motor Speedway"}]}
*************************** 3\. row ***************************
         ID: 4081
       Name: Daytona
CountryCode: USA
   District: Florida
       Info: {"Population": 590280, "Places_of_interest": [{"name": "Daytona Beach"}, {"name": "Motorsports Hall of Fame of America"}, {"name": "Daytona Motor Speedway"}]}

很有意思,但是没有回答我们想问的问题。也就是哪些城市有名胜古迹?为此,我们需要使用许多为 JSON 数据类型设计的特殊函数。所有函数都以名称JSON_*开始。让我们依次来看看这三种方法,首先是如何在 JSON 文档中搜索具有特定键的行。在这种情况下,我们选择有兴趣地点的行的所有数据。

为了确定 JSON 文档是否有特定的键,我们使用了JSON_CONTAINS_PATH()函数。回忆路径只是对文档中的键的解析。在这种情况下,我们想知道 JSON 文档是否包含Places_of_interest的路径。因为函数在没有匹配时返回 0,在至少有一个匹配时返回 1,所以我们检查它是否等于 1。你可以省略等式,但是在试验新的特性和命令时最好是学究式的。我们还使用‘all’选项告诉函数返回所有的匹配(值),而‘one’只返回第一个匹配。你也可以使用稍微正确一点的IS NOT NULL比较。

MySQL  localhost:33060+ ssl  SQL > SELECT * FROM city WHERE JSON_CONTAINS_PATH(info, 'all', '$.Places_of_interest') = 1 \G
*************************** 1\. row ***************************
         ID: 4080
       Name: Charlotte
CountryCode: USA
   District: North Carolina
       Info: {"Population": 792862, "Places_of_interest": [{"name": "NASCAR Hall of Fame"}, {"name": "Charlotte Motor Speedway"}]}
*************************** 2\. row ***************************
         ID: 4081
       Name: Daytona
CountryCode: USA
   District: Florida
       Info: {"Population": 590280, "Places_of_interest": [{"name": "Daytona Beach"}, {"name": "Motorsports Hall of Fame of America"}, {"name": "Daytona Motor Speedway"}]}
2 rows in set (0.00 sec)

现在,假设我们只想查看那些感兴趣的地方,而不是整个 JSON 文档。在这种情况下,我们需要使用JSON_EXTRACT()函数从文档中提取值。特别是,我们希望在info列中搜索数组Places_of_interest中的所有值。尽管这看起来很复杂,但正如你在下面看到的,这并不太糟糕。

MySQL  localhost:33060+ ssl  SQL > SELECT Name, District, JSON_EXTRACT(info, '$.Places_of_interest') as Sights FROM city WHERE JSON_EXTRACT(info, '$.Places_of_interest') IS NOT NULL \G
*************************** 1\. row ***************************
    Name: Charlotte
District: North Carolina
  Sights: [{"name": "NASCAR Hall of Fame"}, {"name": "Charlotte Motor Speedway"}]
*************************** 2\. row ***************************
    Name: Daytona
District: Florida
  Sights: [{"name": "Daytona Beach"}, {"name": "Motorsports Hall of Fame of America"}, {"name": "Daytona Motor Speedway"}]
2 rows in set (0.00 sec)

现在,如果我们只想检索Places_of_interest数组的值呢?在这种情况下,我们可以使用特殊格式的 JSON access 从数组中获取这些值。下面演示了这种技术。请注意以粗体突出显示的部分。

MySQL  localhost:33060+ ssl  SQL > SELECT Name, District, JSON_EXTRACT(info, '$.Places_of_interest[*].name') as Sights FROM city WHERE JSON_EXTRACT(info, '$.Places_of_interest') IS NOT NULL \G
*************************** 1\. row ***************************
    Name: Charlotte
District: North Carolina
  Sights: ["NASCAR Hall of Fame", "Charlotte Motor Speedway"]
*************************** 2\. row ***************************
    Name: Daytona
District: Florida
  Sights: ["Daytona Beach", "Motorsports Hall of Fame of America", "Daytona Motor Speedway"]
2 rows in set (0.00 sec)

好了,现在看起来容易多了,不是吗?这也是一个有点乱的 SQL 命令。如果这一切看起来有点痛苦,你是对的,的确如此。在 SQL 中处理 JSON 数据需要借助 JSON 函数,但是这是一个额外的步骤,在语法上可能有点混乱。有关每个 JSON_*函数的完整解释,请参阅在线 MySQL 参考手册。

如果您经常使用旧的 MySQL 客户端来查询具有宽行的数据,那么您可能已经使用了\G选项来以垂直格式显示结果,这使得读取数据更加容易。对于 shell,我们没有这个选项,但是我们可以使用--json选项来显示数据。虽然这个选项更容易阅读,但是它有点冗长。我们将在 Python 部分看到这一点。

最后,我们可以使用如下所示的DELETE SQL 命令删除这些行。

MySQL  localhost:33060+ ssl  SQL > DELETE FROM city WHERE Name in ('Charlotte', 'Daytona');
Query OK, 3 rows affected (0.00 sec)

现在,让我们看看使用 JavaScript 执行的相同操作。

Java Script 语言

为了在 JavaScript 中执行示例任务,我们将使用 X 协议会话对象启动 shell,并传入world_x模式来演示如何保存一个步骤。然后,我们将使用全局db对象(有时称为变量)的getTables()方法来获取world_x数据库中的表列表。清单 4-6 演示了这些命令。

$ mysqlsh -uroot -hlocalhost -mx --schema=world_x
Creating an X protocol session to 'root@localhost/world_x'
Enter password:
Your MySQL connection id is 15 (X protocol)
Server version: 8.0.11 MySQL Community Server (GPL)
Default schema `world_x` accessible through db.
MySQL Shell 8.0.11

Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type '\help' or '\?' for help; '\quit' to exit.

 MySQL  localhost:33060+ ssl  world_x  JS > db
<Schema:world_x>
 MySQL  localhost:33060+ ssl  world_x  JS > db.getTables();
[
    <Table:city>,
    <Table:country>,
    <Table:countrylanguage>
]

Listing 4-6Listing and Using Databases—JavaScript Mode

现在,让我们插入数据。注意,在清单 4-6 中,db.getTables()方法的结果显示了三个表。我们可以使用表名通过名称来引用表对象。例如,要访问城市表,我们使用db.city。为了插入数据,我们将使用如下所示的db.city.insert()方法。

 MySQL  localhost:33060+ ssl  world_x  JS > db.city.insert("Name", "CountryCode", "District", "Info").values('Charlotte', 'USA', 'North Carolina', '{"Population": 792862, "Places_of_interest": [{"name": "NASCAR Hall of Fame"}, {"name": "Charlotte Motor Speedway"}]}');
Query OK, 1 item affected (0.00 sec)

 MySQL  localhost:33060+ ssl  world_x  JS > db.city.insert("Name", "CountryCode", "District", "Info").values('Daytona', 'USA', 'Florida', '{"Population": 590280, "Places_of_interest": [{"name": "Daytona Beach"}, {"name": "Motorsports Hall of Fame of America"}, {"name": "Daytona Motor Speedway"}]}');
Query OK, 1 item affected (0.00 sec)

Note

当以交互方式运行代码时,您可以省略大多数创建、读取、更新和删除操作的execute()函数调用,因为 MySQL Shell 以交互方式显式执行这些语句。例如,insert()函数通常需要链接 execute()函数来完成操作,但是您可以在交互模式下省略它。

现在我们有了数据,让我们用下面的代码选择行。这里我们对TableSelect对象(从db.city.select返回的对象)使用了db.city.select()方法和where()方法。请注意,我们指定了方括号内引用和列出的列的列表。在这个列表中,我们可以使用列名和特殊的->操作符来提取一个键,从而在 JSON 文档中指定数据。在这种情况下,我们希望文档中的Places_of_interest键(path)存储在 Info 列中。

MySQL  localhost:33060+ ssl  world_x  JS > db.city.select(["Name", "District", "Info->'$.Places_of_interest'"]).where("Info->'$.Places_of_interest' IS NOT NULL");
+-----------+----------------+--------------------------------------------+
| Name      | District       | JSON_EXTRACT(`Info`,'$.Places_of_interest')|
+-----------+----------------+--------------------------------------------+
| Charlotte | North Carolina | [{"name": "NASCAR Hall of Fame"}, {"name": "Charlotte Motor Speedway"}]               |
| Daytona   | Florida        | [{"name": "Daytona Beach"}, {"name": "Motorsports Hall of Fame of America"}, {"name": "Daytona Motor Speedway"}]        |
+-----------+----------------+--------------------------------------------+
2 rows in set (0.00 sec)

注意结果中的列类型。是 JSON 函数!这意味着我们可以在代码中使用一个 JSON 函数,将结果列数据缩小到仅包含Places_of_interest数组的值,就像我们在 SQL 示例中所做的那样,如下所示。多酷啊。

MySQL  localhost:33060+ ssl  world_x  JS > db.city.select(["Name", "District", "JSON_EXTRACT(info, '$.Places_of_interest[*].name')"]).where("Info->'$.Places_of_interest' IS NOT NULL");
+-----------+----------------+--------------------------------------------+
| Name      | District       | JSON_EXTRACT(`info`,'$.Places_of_interest[*].name')                         |
+-----------+----------------+--------------------------------------------+
| Charlotte | North Carolina | ["NASCAR Hall of Fame", "Charlotte Motor Speedway"]                                 |
| Daytona   | Florida        | ["Daytona Beach", "Motorsports Hall of Fame of America", "Daytona Motor Speedway"]     |
+-----------+----------------+--------------------------------------------+
2 rows in set (0.00 sec)

现在,让我们删除为恢复数据而添加的行。

MySQL  localhost:33060+ ssl  world_x  JS > db.city.delete().where("Name in ('Charlotte', 'Daytona')");
Query OK, 2 items affected (0.00 sec)

好吧,没那么糟。如果您认为它似乎比 SQL 更具编程性,甚至可能更直观一点,那么您就对了。如果看起来有点奇怪,不要担心。在 shell 中使用脚本越多,就会变得越容易、越自然。这也是很好的实践,因为使用 MySQL 的未来是 MySQL Shell 和脚本语言!

现在,让我们看看与 Python 执行的相同脚本。

计算机编程语言

因为我们已经看到该任务演示了两次,所以我跳过了每个步骤的执行细节,并向您展示了我的 Python 会话的脚本。

您马上会注意到的一件事是,一旦我们从 db 对象获得了表,代码就与 JavaScript 示例相同,只是函数的名称拼写有点不同。这是故意的。因为 db 对象实际上是 shell 中的一个特殊变量,所以它在两种语言中具有相同的语法。当您开始使用 X DevAPI 对象时,您只会看到不同之处,我们将在第 5 章中看到更多细节。

Note

一般规则是,当函数由多个名称组成时,JavaScript 使用 camelCase,Python 使用下划线分隔的名称。比如分别是createCluster()create_cluster()。在功能是单个单词的情况下,名称是相同的,即,“select”、“insert”、“delete”。

清单 4-7 展示了使用 Python 运行任务的完整脚本。请注意,唯一的区别是获取表的调用。在这种情况下,我们使用 Python 中的db.get_tables()方法。它是具有相同功能的相同方法,只是根据典型的 Python 命名约定进行了不同的命名。

$ mysqlsh -uroot -hlocalhost -mx --py --schema=world_x
Creating an X protocol session to 'root@localhost/world_x'
Enter password:
Your MySQL connection id is 19 (X protocol)
Server version: 8.0.11 MySQL Community Server (GPL)
Default schema `world_x` accessible through db.
MySQL Shell 8.0.11

Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type '\help' or '\?' for help; '\quit' to exit.

 MySQL  localhost:33060+ ssl  world_x  Py > db
<Schema:world_x>
 MySQL  localhost:33060+ ssl  world_x  Py > db.get_tables()
[
    <Table:city>,
    <Table:country>,
    <Table:countrylanguage>
]
 MySQL  localhost:33060+ ssl  world_x  Py > db.city.insert("Name", "CountryCode", "District", "Info").values ('Charlotte', 'USA', 'North Carolina', '{"Population": 792862, "Places_of_interest": [{"name": "NASCAR Hall of Fame"}, {"name": "Charlotte Motor Speedway"}]}')
Query OK, 1 item affected (0.00 sec)
 MySQL  localhost:33060+ ssl  world_x  Py > db.city.insert("Name", "CountryCode", "District", "Info").values('Daytona', 'USA', 'Florida', '{"Population": 590280, "Places_of_interest": [{"name": "Daytona Beach"}, {"name": "Motorsports Hall of Fame of America"}, {"name": "Daytona Motor Speedway"}]}')
Query OK, 1 item affected (0.00 sec)
 MySQL  localhost:33060+ ssl  world_x  Py > db.city.select(["Name", "District", "JSON_EXTRACT(info, '$.Places_of_interest[*].name')"]).where("Info->'$.Places_of_interest' IS NOT NULL")
+-----------+----------------+--------------------------------------------+
| Name      | District       | JSON_EXTRACT(`info`,'$.Places_of_interest[*].name')                         |
+-----------+----------------+--------------------------------------------+
| Charlotte | North Carolina | ["NASCAR Hall of Fame", "Charlotte Motor Speedway"]                                 |
| Daytona   | Florida        | ["Daytona Beach", "Motorsports Hall of Fame of America", "Daytona Motor Speedway"]     |
+-----------+----------------+--------------------------------------------+
2 rows in set (0.00 sec)
 MySQL  localhost:33060+ ssl  world_x  Py > db.city.delete().where("Name in ('Charlotte', 'Daytona')")
Query OK, 2 items affected (0.00 sec)

Listing 4-7Listing, Inserting, Selecting, and Deleting in Databases—Python Mode

请注意代码与 JavaScript 版本的相似之处。这使得学习 X DevAPI 更容易,因为你可以使用你最喜欢的语言,甚至当你必须使用另一种语言时,一切都很熟悉。酷。

What About Other Languages?

虽然 shell 目前只支持 JavaScript 和 Python,但是 X DevAPI 并不局限于这些语言。其实也可以用 Java,。Net,以及 C++通过适当的连接器与 X DevAPI 一起工作。参见 http://dev.mysql.com/doc/ 上 X DevAPI 标题下的链接,了解更多关于使用各自的连接器用 X DevAPI 编写应用的信息。

摘要

对于 MySQL 客户端来说,MySQL Shell 是技术上的一次巨大飞跃。它不仅被设计成以更智能的方式与 MySQL 中的 SQL 一起工作;它还被设计成支持 JavaScript 和 Python 的原型。您可以使用任何您想要的语言,并在它们之间轻松切换,而不必重新启动应用或断开连接。这有多酷?

如果这还不够的话,X DevAPI 和内置对象的额外好处使得使用 shell 作为文档存储的前端意味着您不必编写单独的应用来管理您的数据。您只需选择适合您需求的模式(语言),切换到该语言,然后执行任务。正如我们在第 1 章中所了解到的,shell 还构成了最新特性的前端,包括 InnoDB 集群,为您提供了满足所有 MySQL 管理、编程和高可用性需求的一站式客户端。

在本章中,我们学习了如何使用 MySQL Shell,包括启动选项、Shell 命令、连接、会话,我们甚至学习了如何用 JavaScript 和 Python 编写一些交互式脚本。因此,本章是学习如何开始使用 MySQL Shell 以及使用 JSON 和关系数据的关键章节。虽然这一章并没有详尽地介绍 MySQL Shell 的所有特性,但是它提供了一个广泛的教程,介绍了如何使用它来完成最常见的任务。

在第 5 章中,我将更详细地探索 X DevAPI,包括更仔细地观察可用于编写应用和脚本的对象和工具。我讨论了用 JavaScript 和 Python 访问文档存储的完整脚本。

Footnotes 1

然而,如果您使用过 MySQL Fabric 或实用程序,使用 URI 进行连接会看起来非常熟悉。

  2

尽管您可以在 URI 中指定密码,但这是一种糟糕的安全做法。

  3

不,真的。你应该读一下。

  4

Python 大师经常以这种方式称呼自己。不要和说“你”的骑士混淆。——https://en.wikipedia.org/wiki/Knights_who_say_Ni

五、X 开发者 API

X Developer Application Programming Interface,简称 X DevAPI,是一个类库和方法库,为 MySQL 实现了一个新的 NoSQL 接口。具体来说,X DevAPI 旨在允许与 JSON 文档和关系数据轻松交互。X DevAPI 有专门支持这两个概念的类,允许开发者在他们的应用中使用其中一个(或两个)。X DevAPI 与 X 协议、X 插件以及为公开 X DevAPI 而编写的客户端一起,形成了新的 MySQL 8 文档存储特性。

正如我们将看到的,使用 X DevAPI 有许多方面。然而,一旦掌握了连接和请求对象实例、形成表达式以及使用 JSON 文档的基础知识,X DevAPI 就非常容易学习,并且对于编写文档存储或关系数据应用来说非常有效。

在本书中,我们已经看到了几个关于关系数据的 X DevAPI 的例子,因为大多数数据库管理员都熟悉这种形式的数据库交互。然而,我们还没有看到为文档存储提供的类和方法的完整列表。本章包含了 X DevAPI 中几乎所有可用的公共类和方法(为了简洁起见,省略了一些较少使用的类)。

尽管所有的 X DevAPI 客户端连接器都支持所有的类,但是在每个客户端实现 X DevAPI 的方式上还是有一些细微的差别。特别是,类和方法的名称稍有不同,以匹配该语言的开发实践。例如,一种语言的公认样式指南可能不鼓励使用驼峰式名称,而另一种语言的样式指南可能建议使用下划线,不使用大写。

当学习使用 X DevAPI 时,回顾其他语言的例子会很有帮助。尽管命名方案可能不同,语法可能大相径庭,但是基本的类和方法非常相似,您仍然可以学习使用什么方法。这是我使用 Python 例子的主要原因。您可以使用 Python 示例来了解如何使用这些类,尽管这些方法的命名方案可能略有不同,但从一种语言到另一种语言,方法和实践是相同的。此外,Python 易于阅读,您不需要大型复杂的开发工具(例如,C++或。Net 编译器)。你所需要的只是一个 Python 解释器,它几乎适用于所有平台。

尽管这一章包含了一些来自其他章节的类似信息,但它使用了一种逐步的方法,通过一系列代码示例来演示 X DevAPI。在为文档存储应用编写自己的代码时,包含一组描述主要类及其方法的表作为参考。

我首先全面概述 X DevAPI 的特性,然后详细介绍主要的类和方法。在这个过程中,我给出了许多使用 X DevAPI 的例子。我们不会看到属于 X DevAPI 的所有可能的类或方法,但是我们会看到编写文档存储应用需要掌握的主要组件(类和方法)。如果您需要关于不常用的类和方法的更多信息,请参见“更多信息”一节中的开发者文档参考。

概观

X DevAPI 中有几个强大的特性。在前几章中,我们已经看到了其中的大部分,但是现在我们将看到 X DevAPI 提供的特性。回想一下,这些特性是通过支持 X 协议和 X DevAPI 的客户机实现的。X DevAPI 中包含的特性如下。我们将在本章的后面看到这些特性以及它们是如何实现的。

  • MySQLX:一个模块,用于获取一个会话对象,该对象是通过 X 协议连接到 MySQL 服务器而产生的。
  • 会话:到 MySQL 服务器的连接。
  • 集合:存储 JSON 文档的组织抽象。
  • 文档:JSON 文档是集合中数据的主要存储机制。
  • CRUD 操作:创建、读取、更新和删除操作的简单方法。读操作简单易懂。
  • 关系数据:实现传统关系数据的 CRUD 操作,包括 SQL 语句执行和结果处理。
  • 表达式:使用现代实践和语法风格来摆脱传统的 SQL 字符串构建,以便在您的集合和文档中查找内容。
  • 并行执行:非阻塞、异步调用遵循常见的宿主语言模式。
  • 方法链接:构建 API 是为了让创建或检索(获取)对象的方法返回该对象的实例。这允许我们将几个方法结合在一起(称为方法链接)。尽管方法链接既不是新概念,也不是 X DevAPI 所独有的,但它是一种非常强大的机制,可以使我们的代码更具表现力,更易于阅读。

Note

X DevAPI 仅在使用 X 插件时可用。如果没有安装 X 插件,就不能使用 X DevAPI,只能通过支持 X 协议的客户机或数据库连接器来使用。

客户

X DevAPI 只能通过一个实现 X 协议的客户端获得。此外,要使用这些客户端中的任何一个,您还必须在您的服务器上安装和配置 X 插件。特别是以下任何一种:

Note

有些数据库连接器版本还没有正式发布。在这些情况下,您可以通过点击下载页面上的Development Releases选项卡找到正确的版本。只要您不在生产中使用它们,使用 DMR 版本应该没问题。如果您没有看到想要使用的组件的 GA 版本,请务必联系您的 MySQL 销售代表以寻求帮助。

目标语言整合

当您遇到像 X DevAPI 这样的新 API 时,通常情况下,您会希望不同语言的类名和方法名是相同的。也就是说,具有名为getSomething()的方法的类从一种语言到另一种语言的拼写是相同的。然而,遵守特定于平台和语言的命名约定是一种常见的(有些人会说是首选的)做法,这种做法牺牲了 API 中的通用性,以确保持续符合语言命名标准。如果你像我一样使用不同的编程语言,你会发现这是一个常见的地方,因此你知道对于同一个 API,从一种语言到另一种语言会有一些变化。

X DevAPI 支持这种实践,实现 API 的客户机符合它们的平台和语言标准。在大多数情况下,这可能只是名称中大写字母使用的变化,但也可能导致添加(或省略)下划线。我们已经发现,Connector/Python (C/Py)在名称中使用下划线,而不使用大写字母。Connector/Java (C/J),Connector/Node.js (C/Node.js),Connector/。Net (C/Net)和 Connector/C++ (C/C++)使用的大写略有不同。

不仅仅是方法名有不同的拼写。在处理方法结果或与对象交互的方式上也可能有细微的差别。也就是说,客户遵循这种语言对于常见构造和概念(比如迭代)的常规实践。例如,如果语言有一个返回多个项目的列表的概念(比如相对于一个数组),方法将返回一个列表。尽管随着你对 X DevAPI 的了解越来越多,这看起来有些奇怪,但它确实有好处。也就是说,您编写的结果代码符合您选择的语言标准。

为了演示这些差异,表 5-1 显示了 MySQL X 包方法名称在语言上的微小差异。注意,甚至包名在不同语言中的拼写也不同。Python 开发者会看到 Python 命名方案,并不认为它不寻常,但 Java 示例可能看起来很奇怪。

表 5-1

MySQL X Module

| 返回 | 名字 | 方法 | 语言 | 因素 | | :-- | :-- | :-- | :-- | :-- | | 会话对象 | `MysqlxSessionFactory` | `getSession()` | 爪哇 | 连接 URI 或连接属性 | | `mysqlx` | `getSession()` | Node.js | 连接 URI 或连接属性 | | `MySQLX` | `GetSession()` | 高级程序员 | 连接 URI 或连接数据对象 | | `mysqlx` | `get_session()` | 计算机编程语言 | 连接词典 |

在探索 X DevAPI 时,您可能会注意到另一个不同之处。实现 API 的客户端对于如何处理数据有一些非常不同的机制。在某些情况下,如 C/Net,一切都是一个类,通常使用类来包含数据,但在 C/Py 中,更喜欢使用列表和字典。因此,客户端(特别是数据库连接器)可能会以不同的方式实现一些迭代、检索和封装机制。然而,与命名约定一样,差异是为了开发者的利益,以便 X DevAPI 在目标语言中以它应该的方式“工作”。

让我们再看一个区别的例子。表 5-2 显示了可用于处理模式的方法。我包括了四种数据库连接器的四种语言(除了组 Java 和 JavaScript)以及对每种方法的任务、参数和返回类型的简短描述。

表 5-2

Session—Create Schema Method

| 描述 | 返回 | 语言 | 方法 | 因素 | | :-- | :-- | :-- | :-- | :-- | | 创建新模式 | 模式对象 | Java/Node.js | `createSchema()` | 字符串—模式名称 | | 高级程序员 | `CreateSchema()` | 字符串—模式名称 | | 计算机编程语言 | `create_schema()` | 字符串—模式名称 |

在下一节中,我们将研究 X DevAPI 的主要代码模块,名为mysqlx

Note

本章中的代码示例是作为使用连接器/Python 数据库连接器的脚本用 Python 编写的。因此,您需要安装连接器来使用这些示例。最后,要运行这些示例,可以使用 python 命令来执行它们,如下所示:python ./script1.py

mysqlx模块(有时称为包)与会话(X 协议)一起工作。还有一个用于使用 InnoDB Cluster 的模块(名为 dba ),以及几个公共类,包括用于列、行等的类。

Note

这一章包含了很多关于对象和类的信息。对象是代码类的实例(在执行时),而类只是代码构造。

MySQL X 模块

mysqlx模块是编写文档存储应用和与 X DevAPI 通信的入口点。我们使用该模块将连接信息以连接字符串或特定于语言的结构(例如 Python 中的字典)的形式传递给服务器,以将连接参数作为 URI 或连接字典(而不是两者)传递。回想一下,统一资源标识符(URI,一种特殊的字符串编码)使用以下格式:

ConnectURI ::= ' 'user_id' ':' 'user_password' '@' 'hostname' ':' 'port_number' '/' 'default_schema_name' '

请注意,密码、端口和模式是可选的,但用户和主机是必需的。在这种情况下,Schema 是连接时要使用的默认模式(数据库)。获取会话对象的方法如下所示。

get_session(<URI or connection dictionary>)

下面显示了使用连接选项字典获取会话对象实例和使用连接字符串(URI)获取会话对象实例的示例。

import mysqlx
mysqlx_session1 = mysqlx.get_session({'host': 'localhost', 'port': 33060, 'user': 'root', 'password': 'secret'})
mysqlx_session2 = mysqlx.get_session('root:secret@localhost:33060')

如果连接成功,结果变量将指向一个对象实例。如果失败,结果可能是一个错误或未初始化的连接。我们将在后面的章节中看到更多关于检查错误的内容。

在下一节中,我们将开始探索 X DevAPI 中的类和方法(组件)。

类别和方法

接下来的部分检查了mysqlx模块的每个主要类及其方法(特性)。这些类只能从 Session 对象访问——从 get_session()方法返回相同的对象。因为这本书是关于文档存储的,所以我们把重点放在 mysqlx 模块的那些类上。

我们将发现包括类在内的方法,以及使用模式(数据库)、管理事务、检查或关闭连接的方法。所介绍的材料包括最常用的类和方法,按用途或应用而不是严格的等级来分组。这使得概述更简短,并遵循探索 API 的更符合逻辑的路径。如果您想查看模块和类的所有细节以及代码的原始 Doxygen 文档,请参阅本章末尾的“更多信息”一节,获取每个数据库连接器的 API 文档的链接。我在这一章中包含了一些例子来说明所介绍的许多方法。

让我们从 mysqlx 模块的简要概述开始。表 5-3 显示了模块中可用的对象。使用此表作为 X DevAPI 的快速参考指南。

表 5-3

Objects in the mysqlx Module

| 面积 | 方法 | 描述 | | :-- | :-- | :-- | | 关系 | `Session` | 支持与支持 X 协议的 MySQL 产品进行交互 | | create, read, update, and delete | `Schema` | 数据库模式的客户端表示;提供对模式内容的访问 | | `Collection` | 表示架构上的文档集合 | | `Table` | 表示模式上的数据库表 | | `View` | 表示架构上的数据库视图 | | 结果 | `ColumnMetaData` | 返回列的元数据 | | `Row` | 表示从选择查询返回的行元素 | | `Result` | 允许检索对数据库执行的非查询操作的相关信息 | | `BufferingResult` | 为缓冲结果对象提供基本功能 | | `RowResult` | 允许遍历表返回的行对象。选择操作 | | `SqlResult` | 表示 SQL 语句的结果 | | 声明 | `DbDoc` | 表示 JSON 格式的通用文档 | | `Statement` | 为语句对象提供基本功能 | | `FilterableStatement` | 与可过滤语句一起使用的语句 | | `SqlStatement` | 用于 SQL 执行的语句 | | `FindStatement` | 集合上的语句文档选择 | | `AddStatement` | 对集合进行文档添加的语句 | | `RemoveStatement` | 从集合中删除文档的语句 | | `ModifyStatement` | 集合上文档更新操作的语句 | | `SelectStatement` | 对表进行记录检索操作的语句 | | `InsertStatement` | 对表执行插入操作的语句 | | `DeleteStatement` | 删除表的语句 | | `UpdateStatement` | 对表进行记录更新操作的语句 | | `CreateCollectionIndexStatement` | 在集合上创建索引的语句 | | `ReadStatement` | 为读取操作提供基本功能 | | `WriteStatement` | 提供常见的写操作属性 | | 错误 | `DataError` | 报告已处理数据问题的错误例外 | | `DatabaseError` | 与数据库相关的错误异常 | | `Error` | 作为所有其他错误异常的基类的异常 | | `IntegrityError` | 关于关系完整性的错误异常 | | `InterfaceError` | 与接口相关的错误异常 | | `InternalError` | 内部数据库错误异常 | | `NotSupportedError` | 使用不支持的数据库功能时出现异常错误 | | `OperationalError` | 与数据库操作相关的错误异常 | | `PoolError` | 连接池相关错误的异常 | | `ProgrammingError` | 错误异常编程错误 |

让我们从 Session 类开始我们的 X DevAPI 之旅。

会话类

Session 类是我们将用来开始处理文档存储的主要类。一旦我们有了连接,下一步就是获取会话对象。从那里,我们可以开始使用文档存储。下面是按领域和应用分组的类和方法的浏览。我们从模式方法开始。

模式方法

X DevAPI 使用术语 schema 来指代一组集合;集合是文档的集合。然而,当处理关系数据时,我们使用“数据库”来指代表和类似对象的集合。人们可能会认为“模式”是“数据库”的同义词,对于 MySQL 的旧版本来说,这是正确的。然而,当使用文档存储和 X DevAPI 时,应该使用“模式”,而当引用关系数据时,应该使用“数据库”

Schema or Database: Does It Matter?

从 MySQL 5.0.2 开始,这两个术语通过 SQL 命令CREATE DATABASECREATE SCHEMA成为同义词。然而,其他数据库系统做出了区分。也就是说,在某些情况下,模式是表的集合,而数据库是模式的集合。其他人认为模式是定义数据结构的东西。如果您使用其他数据库系统,请确保检查定义,以便正确使用术语。

当开始使用文档存储时,您需要做的第一件事是选择(获取)一个现有的模式,删除一个现有的模式,或者创建一个新的模式。您可能还想列出服务器上的模式。Session 类提供了几种执行这些操作的方法。表 5-4 列出了与模式相关的方法、参数和返回值。

表 5-4

Session Class—Schema Methods

| 方法 | 返回 | 描述 | | :-- | :-- | :-- | | `create_schema(str name)` | `Schema` | 在数据库上创建一个模式,并返回相应的对象 | | `get_schema(str name)` | `Schema` | 通过名称从当前会话中检索架构对象 | | `get_default_schema()` | `Schema` | 检索了配置为会话默认值的架构 | | `drop_schema(str name)` | `None` | 删除具有指定名称的架构 |

清单 5-1 展示了如何使用会话对象创建模式对象的例子。当我们检查更多的类和方法时,我们将再次扩展这个例子。在这种情况下,我们使用会话对象来处理模式。

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Get an unknown schema
schema1 = mysqlx_session.get_schema("not_there!")
# Does it exist?
print("Does not_there! exist? {0}".format(schema1.exists_in_database()))
# Create the schema
schema = mysqlx_session.create_schema("test_schema")
# Does it exist?
print("Does test_schema exist? {0}".format(schema.exists_in_database()))
mysqlx_session.close()
Listing 5-1Working with Schemas

记下检索不存在的模式的代码。我使用 schema 对象的一个方法来检查它是否存在,然后打印出结果。假设模式not_there!不存在,代码将显示“False”最后,我在代码末尾创建了模式test_schema。我们将在后面的章节中更详细地了解 schema 类,以及查看模式是否存在的更好方法。如果您将这段代码保存到一个名为listing5-1.py的文件中并执行它,您将看到如下输出。

$ python ./listing5-1.py
Does not_there! exist? False
Does test_schema exist? True

现在让我们来看看用于执行符合 ACID 的事务的事务方法。

事务方式

事务提供了一种机制,允许一组操作作为单个原子操作执行。例如,如果为一个银行机构建立一个数据库,将资金从一个账户转移到另一个账户的宏操作将优选地被完整地执行(资金从一个账户转移到另一个账户),而不会中断。

事务允许将这些操作封装在一个原子操作中,如果在所有操作完成之前发生错误,该原子操作将取消任何更改,从而避免数据从一个表中删除,并且永远不会进入下一个表。包含在事务命令中的 SQL 语句形式的一组示例操作如下:

START TRANSACTION;

UPDATE SavingsAccount SET Balance = Balance – 100
WHERE AccountNum = 123;
UPDATE CheckingAccount SET Balance = Balance + 100
WHERE AccountNum = 345;

COMMIT;

MySQL 的 InnoDB 存储引擎(默认存储引擎)支持确保数据完整性的 ACID 事务,能够在所有操作成功的情况下提交(保存)结果更改,或者在任何一个操作失败的情况下回滚(撤消)更改。

What is Acid?

酸代表原子性、一致性、隔离性和持久性。也许是数据库理论中最重要的概念之一,它定义了数据库系统必须表现出的行为,才能被认为是可靠的事务处理。

原子性意味着对于包含多个命令的事务,数据库必须允许在“全有或全无”的基础上修改数据。也就是说,每个事务都是原子的。如果命令失败,则整个事务失败,并且事务中到该点为止的所有更改都将被丢弃。这对于在高事务环境(如金融市场)中运行的系统尤其重要。考虑一下资金转移的后果。通常,借记一个账户和贷记另一个账户需要多个步骤。如果在借记步骤后事务失败,并且没有将钱贷记回第一个帐户,该帐户的所有者将会非常生气。在这种情况下,从借方到贷方的整个事务必须成功,否则都不会成功。

一致性意味着只有有效的数据才会存储在数据库中。也就是说,如果事务中的命令违反了一致性规则之一,则整个事务将被丢弃,数据将返回到事务开始之前的状态。另一方面,如果事务成功完成,它将以遵守数据库一致性规则的方式更改数据。

隔离意味着同时执行的多个事务不会相互干扰。这是并发性的真正挑战最明显的地方。数据库系统必须处理事务不能违反数据的情况(更改、删除等)。)正在另一个事务中使用。有很多方法可以解决这个问题。大多数系统使用一种称为锁定的机制,在第一个事务完成之前,防止数据被另一个事务使用。尽管隔离属性没有规定先执行哪个事务,但它确实确保了它们不会相互干扰。

持久性意味着事务不会导致数据丢失,也不会丢失事务期间创建或更改的任何数据。耐用性通常由强大的备份和恢复维护功能提供。一些数据库系统使用日志记录来确保任何未提交的数据可以在重启时恢复。

会话类实现了用于事务处理的方法,这些方法反映了前面显示的 SQL 命令。表 5-5 列出了事务方式。

表 5-5

Transaction Methods

| 方法 | 返回 | 描述 | | :-- | :-- | :-- | | `start_transaction()` | `None` | 在服务器上启动事务上下文 | | `commit()` | `None` | 提交调用`startTransaction()`后执行的所有操作 | | `rollback()` | `None` | 放弃调用`startTransaction()`后执行的所有操作 | | `set_savepoint(str name="")` | `str` | 创建或替换具有给定名称的事务保存点 | | `release_savepoint(str name)` | `None` | 删除在事务中定义的保存点 | | `rollback_to(str name)` | `None` | 将事务回滚到指定的保存点,而不终止事务 |

注意,最后三种方法允许您创建命名事务保存点,这是事务处理的一种高级形式。有关保存点和事务的更多信息,请参见在线 MySQL 参考手册。

我们将在本章后面看到一个事务的例子。现在,让我们看看与服务器连接相关的方法。

连接方法

下划线连接有两种方法。一个用于检查连接是否打开,另一个用于关闭连接。表 5-6 显示了会话类中剩余的可用实用方法。

表 5-6

Connection Methods

| 方法 | 返回 | 描述 | | :-- | :-- | :-- | | `close()` | `None` | 关闭会话 | | `is_open()` | `Bool` | 如果已知会话是打开的,则返回 true |

如果您想在应用中额外检查连接,下面显示了如何使用这些方法。

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection

if not mysqlx_session.is_open():

    print("Connection failed!")
else:
    print("Connection succeeded.")
# Close the connection

mysqlx_session.close()

Listing 5-2Working with Sessions

如果您将这段代码保存到一个名为listing5-2.py的文件中并执行它,您将看到如下所示的输出。

$ python ./listing5-2.py
Connection succeeded.

杂项方法

Session 类中还有几个实用方法。表 5-7 列出了附加功能。有关这些方法的更多信息,请参见在线 X DevAPI 参考。

表 5-7

Miscellaneous Methods

| 方法 | 返回 | 描述 | | :-- | :-- | :-- | | `Is_open()` | 弯曲件 | 如果连接处于打开和活动状态,则为 True | | `sql(str sql)` | sqlstatesment | 创建一个`SqlStatement`对象,允许在目标 MySQL 服务器上运行收到的 SQL 语句 |

CRUD 操作

X DevAPI 实现了一个创建、读取、更新和删除(CRUD)模型,用于处理模式中包含的对象。模式可以包含任意数量的集合、文档、表格、视图和其他关系数据对象(即触发器)。在本节中,我们将看到模式、集合、表(关系数据)和数据集的概述。CRUD 模型是为模式中的所有对象实现的,这些对象可以包含文档存储和关系数据的数据。

到目前为止,本书中的大多数示例都使用关系数据进行演示,因为大多数读者都熟悉使用 SQL。本章继续第 3 章的讨论,完成使用 X DevAPI 构建文档存储应用的介绍。

文档存储数据 CRUD 操作使用动词 add、find、modify 和 remove,而关系数据使用与等效 SQL 命令相匹配的术语。表 5-8 简要介绍了这些方法是如何命名的,以及每个方法的简要描述。此外,我们对文档存储数据使用 Collection 类,对关系数据使用Table类。

表 5-8

CRUD Operations for Document Store and Relational Data

| CRUD 操作 | 描述 | 文档存储 | 关系数据 | | :-- | :-- | :-- | :-- | | 创造 | 添加新项目/对象 | `collection.add()` | `table.insert()` | | 阅读 | 检索/搜索数据 | `collection.find()` | `table.select()` | | 更新 | 修改数据 | `collection.modify()` | `table.update()` | | 删除 | 移除项目/对象 | `collection.remove()` | `table.delete()` |

我们将在下面的章节中看到每个类特有的方法(SchemaCollectionTableView)。让我们从查看 Schema 类的细节开始。

模式类

模式是存储数据的对象的容器。回想一下,这可以是文档存储数据的集合,也可以是关系数据的表或视图。就像过去处理关系数据一样,您必须选择(或使用)一个模式来存储集合、表或视图中的数据。

虽然您可以混合使用文档存储数据(集合)和关系数据(表、视图),但是为了便于记忆,我们将从文档存储方法开始依次研究与它们相关的模式类方法。

Schema 类的文档存储方法包括创建集合、使用和查找集合的方法。表 5-9 显示了使用集合和表格的文档存储方法。注意,create 和 get 方法返回一个对象的实例。例如,get_collection()方法返回一个集合对象。这是如何使用 X DevAPI 将几个操作合并成一个语句的另一个例子。

表 5-9

Schema Class—Document Store and Table Methods

| 方法 | 返回 | 描述 | | :-- | :-- | :-- | | `get_tables()` | `List` | 返回该模式的表列表 | | `get_collections()` | `List` | 返回此架构的集合列表 | | `get_table(str name)` | `Table` | 返回该模式的给定名称的表 | | `get_collection(str name)` | `Collection` | 返回此架构的给定名称的集合 | | `get_collection_as_table(str name)` | `Table` | 返回一个代表数据库集合的 Table 对象 | | `create_collection(str name)` | `Collection` | 在当前架构中创建具有指定名称的新集合,并检索表示所创建的新集合的对象 |

现在,让我们继续我们的例子,展示一些使用集合的模式方法。清单 5-3 展示了如何创建一个模式和几个集合,然后在模式中列出这些集合。注意,我使用了集合对象的 name 属性。

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(-1)
# Get the schema
schema = mysqlx_session.create_schema("test_schema")
# Create a new collection
testCol = schema.create_collection('test_collection1', True)
# Create a new collection
testCol = schema.create_collection('test_collection2', True)
# Show the collections.
collections = schema.get_collections()
for col in collections:
    print(col.name)
mysqlx_session.close()
Listing 5-3Collection Methods

如果您将这段代码保存到一个名为listing5-3.py的文件中并执行它,您将看到如下所示的输出。

$ python ./listing5-3.py
test_collection1
test_collection2

请注意,在表中有一个方法可以将文档作为关系表进行检索。这个方法,get_collection_as_table()允许开发者将标准的 SQL 列存储在文档中,并将集合转换(造型)为表格。也就是说,可以将集合作为一个表对象提取,然后该表对象的行为就像一个普通的关系表。使用 CRUD 操作访问表对象中的数据使用以下语法。

doc->'$.field_name'

大多数连接器都支持这种语法。你也可以形成复杂的文档路径(就像我们在第三章中看到的那样)。

doc->'$.something_else.field_name.like[1].other_thing'

我们需要这种语法的原因是,作为表返回的集合会产生一个只有两个字段的表:doc_id,,其中doc是存储文档的位置,_id是文档 id。清单 5-4 展示了如何使用这个语法。

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Get the schema
schema = mysqlx_session.create_schema("test_schema")
# Create a new collection
pets = schema.create_collection("pets_json")
# Insert some documents
pets.add({'name': 'Violet', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
pets.add({'name': 'JonJon', 'age': 15, 'breed':'poodle', 'type':'dog'}).execute()
pets.add({'name': 'Mister', 'age': 4, 'breed':'siberian khatru', 'type':'cat'}).execute()
pets.add({'name': 'Spot', 'age': 7, 'breed':'koi', 'type':'fish'}).execute()
pets.add({'name': 'Charlie', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
# Fetch collection as Table
pets_tbl = schema.get_collection_as_table('pets_json')
# Now do a find operation to retrieve the inserted document
result = pets_tbl.select(["doc->'$.name'", "doc->'$.age'"]).execute()
record = result.fetch_one()
# Print the first row
print("Name : {0}, Age: {1}".format(record[0], record[1]))
# Drop the collection
schema.drop_collection("pets_json")
# Close the session
mysqlx_session.close()
Listing 5-4Collection as Table Example

如果您将这段代码保存到一个名为listing5-4.py的文件中并执行它,您将看到如下所示的输出。

$ python ./listing5-4.py
Name : "Violet", Age: 6

集合类

Collection 类用于存储文档(数据)。您可以将它视为与关系数据中的表相同的组织概念。因此,Collection类实现了对文档的 CRUD 操作以及一些实用方法,比如创建索引或对集合中的文档进行计数的方法。表 5-10 显示了集合类的方法。

表 5-10

Collection Class

| 方法 | 返回 | 描述 | | :-- | :-- | :-- | | `add(*values)` | `AddStatement` | 将一个或多个文档插入集合 | | `find(str search_condition)` | `FindStatement` | 从集合中检索符合指定条件的文档 | | `remove(str search_condition)` | `RemoveStatement` | 创建文档删除处理程序 | | `modify(str search_condition)` | `ModifyStatement` | 修改符合指定标准的文档 | | `drop_index(str name)` | `None` | 从集合中删除索引 | | `replace_one(str id, document doc)` | `Result` | 用新文档替换现有文档 | | `add_or_replace_one(str id, document doc)` | `Result` | 替换或添加集合中的文档 | | `remove_one(str id)` | `Result` | 移除具有给定`_id`值的文档 | | `get_one(str id)` | `Document` | 从集合中获取具有给定`_id`的文档 |

注意关于这个表的一件事,每个 CRUD 操作返回一个操作的对象实例。例如,find()方法返回一个FindStatement对象。正如您所猜测的,这意味着产生的对象实例具有我们可以用来对语句做更多事情的方法。接下来我们将看到这些类和方法。现在,让我们看一个使用基本 CRUD 操作的例子。

既然我们已经对 X DevAPI 有了足够的了解,我们可以开始回顾更完整的例子了。也就是用数据做一些事情的例子。清单 5-5 展示了一个完整的 Python 脚本,演示了如何使用集合。我包括了会话代码和连接错误处理,就像我们之前看到的那样。这个例子是一个简单的文档存储,用于记录关于宠物的信息。

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Create a schema.
schema = mysqlx_session.create_schema("animals")
# Create a new collection
pets = schema.create_collection("pets_json", True)
# Insert some documents
pets.add({'name': 'Violet', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
pets.add({'name': 'JonJon', 'age': 15, 'breed':'poodle', 'type':'dog'}).execute()
pets.add({'name': 'Mister', 'age': 4, 'breed':'siberian khatru', 'type':'cat'}).execute()
pets.add({'name': 'Spot', 'age': 7, 'breed':'koi', 'type':'fish'}).execute()
pets.add({'name': 'Charlie', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
# Do a find on the collection - find the fish
mydoc = pets.find("type = 'fish'").execute()
print(mydoc.fetch_one())
# Drop the collection
mysqlx_session.drop_schema("animals")
# Close the connection
mysqlx_session.close()
Listing 5-5CRUD Example Using a Collection

该脚本创建一个新的模式,然后创建一个名为animals的新集合,并在该模式中创建一个名为pets_json的集合。然后,该脚本将几个文档(pet)添加到集合中。为了演示查找操作,脚本调用 pets 集合上的find()方法来查找所有的鱼。也就是说,文档的类型等于“fish”。我们将在后面的章节中看到更多关于可以在find()方法中使用的表达式。

如果您将这段代码保存到一个名为listing5-5.py的文件中并执行它,您将看到如下所示的输出。我们找到鱼了!

$ python ./listing5-5.py
{"breed": "koi", "age": 7, "_id": "7c3c0201f5e24bd99f586e772aad0369", "type": "fish", "name": "Spot"}

您可以通过组合一个列表(数组)中的数据来同时添加多个文档,而不是为每个文档发布一个单独的add()方法。这就像对关系数据使用大容量插入选项一样。下面的代码相当于上面的五个add()方法调用。

# Insert some documents
pets.add([{'name': 'Violet', 'age': 6, 'breed':'dachshund', 'type':'dog'},
  {'name': 'JonJon', 'age': 15, 'breed':'poodle', 'type':'dog'},
  {'name': 'Mister', 'age': 4, 'breed':'siberian khatru', 'type':'cat'},
  {'name': 'Spot', 'age': 7, 'breed':'koi', 'type':'fish'},
  {'name': 'Charlie', 'age': 6, 'breed':'dachshund', 'type':'dog'}]).execute()

注意在add()方法中使用的语法。这是一种特殊的符号,所有文档存储类方法都使用它来指定 JSON 文档和列出表达式。在本例中,语法是可选语法,通常用于指定多个文档。也就是说,您将文档放在[]中,以逗号分隔,如下所示。在本例中,我用一个方法调用添加了两个文档。因此,对于一个文档来说,[]是可选的。

pets.add([
    {'name': 'whizzy', 'age': 2, 'breed':'carp', 'type':'fish'},
    {'name': 'blobby', 'age': 3, 'breed': 'carp', 'type': 'fish'},
]).execute()

虽然这稍微简化了代码,但是您可能有理由一次添加一个文档。例如,如果您需要使用从add()方法返回的结果对象来获得更多信息或检查警告,您可能希望一次添加一个文档。

从表 5-10 中回忆一下,CRUD 方法每个都返回一个类的对象实例。这些类有几个方法,您可以使用它们来处理适合该操作的语句。表 5-11 显示了这些类及其方法。

表 5-11

Classes for CRUD Operations for Document Store Data

| 班级 | 方法 | 返回 | 描述 | | :-- | :-- | :-- | :-- | | 添加状态 | 对集合进行文档添加的语句 | | `add(*values)` | `AddStatement` | 将文档列表添加到集合中 | | `execute()` | `Result` | 执行语句 | | `get_values()` | `list` | 返回值列表 | | `is_doc_based()` | `bool` | 检查它是否基于文档 | | `is_upsert()` | `bool` | 如果是向上插入,则返回 true | | `schema` | `Schema` | 架构对象 | | `target` | `object` | 数据库对象目标 | | `upsert(val=True)` |   | 将翻转标志设置为所提供值的布尔值 | | findstatesment | 在集合中查找文档 | | `bind(*args)` | `FilterableStatement` | 将值绑定到特定的占位符 | | `execute()` | `Result` | 执行语句 | | `fields(*fields)` | `FindStatement` | 设置文档字段过滤器 | | `get_binding_map()` | `dict` | 返回绑定映射字典 | | `get_bindings()` | `list` | 返回绑定列表 | | `get_grouping()` | `list` | 返回分组表达式列表 | | `get_having()` | `object` | 返回 having 表达式 | | `get_limit_offset()` | `int` | 返回极限偏移量 | | `get_limit_row_count()` | `int` | 返回限制行数 | | `get_projection_expr()` | `object` | 返回投影表达式 | | `get_sort_expr()` | `object` | 返回排序表达式 | | `get_where_expr()` | `object` | 返回 where 表达式 | | `group_by(*fields)` | `ReadStatement` | 为结果集设置分组标准 | | `having(condition)` | `ReadStatement` | 为聚合函数运算中要考虑的记录设置条件 | |   | `is_doc_based()` | `bool` | 检查它是否基于文档 | | `is_lock_exclusive()` | `bool` | 如果为`EXCLUSIVE LOCK`,则返回 true | | `is_lock_shared()` | `bool` | 如果为`SHARED LOCK`,则返回 true | | `limit(row_count, offset=0)` | `FilterableStatement` | 设置要返回的记录或文档的最大数量 | | `lock_exclusive()` | `ReadStatement` | 用`EXCLUSIVE LOCK`执行读操作;一次只能有一个锁处于活动状态 | | `lock_shared()` | `ReadStatement` | 用`SHARED LOCK`执行读操作;一次只能有一个锁处于活动状态 | | `schema` | `Schema` | 架构对象 | | `sort(*sort_clauses)` | `FilterableStatement` | 设置排序标准 | | `target` | `object` | 数据库对象目标 | | `where(condition)` | `FilterableStatement` | 设置要过滤的搜索条件 | | 修改状态 | 修改集合中的文档 | | `array_append(doc_path, value)` | `ModifyStatement` | 将值插入集合文档中数组属性的特定位置 | | `array_insert(field, value)` | `ModifyStatement` | 将值插入集合文档中的指定数组 | | `bind(*args)` | `FilterableStatement` | 将值绑定到特定的占位符 | | `change(doc_path, value)` | `ModifyStatement` | 将更新添加到将字段(如果它存在于文档路径中)设置为给定值的语句中 | | `execute()` | `Result` | 执行语句。 | | `get_binding_map()` | `dict` | 返回绑定映射字典 | | `get_bindings()` | `list` | 返回绑定列表 | | `get_grouping()` | `list` | 返回分组表达式列表 | | `get_having()` | `object` | 返回 having 表达式 | | `get_limit_offset()` | `int` | 返回极限偏移量 | | `get_limit_row_count()` | `int` | 返回限制行数 | | `get_projection_expr()` | `object` | 返回投影表达式 | | `get_sort_expr()` | `object` | 返回排序表达式 | |   | `get_update_ops()` | `list` | 返回更新操作的列表 | | `get_where_expr()` | `object` | 返回 where 表达式 | | `is_doc_based()` | `bool` | 检查它是否基于文档 | | `limit(row_count, offset=0)` | `FilterableStatement` | 设置要返回的记录或文档的最大数量 | | `patch(doc)` | `ModifyStatement` | 将值插入集合文档中数组属性的特定位置 | | `schema` | `Schema` | 架构对象 | | `set(doc_path, value)` | `ModifyStatement` | 设置或更新集合中文档的属性。 | | `sort(*sort_clauses)` | `FilterableStatement` | 设置排序标准。 | | `target` | `object` | 数据库对象目标 | | `unset(*doc_paths)` | `ModifyStatement` | 从集合中的文档移除属性 | | `where(condition)` | `FilterableStatement` | 设置要过滤的搜索条件 | | 移除状态 | 从集合中删除文档 | | `bind(*args)` | `FilterableStatement` | 将值绑定到特定的占位符 | | `execute()` | `Result` | 执行语句 | | `get_binding_map()` | `dict` | 返回绑定映射字典 | | `get_bindings()` | `list` | 返回绑定列表 | | `get_grouping()` | `list` | 返回分组表达式列表 | | `get_having()` | `object` | 返回 having 表达式 | | `get_limit_offset()` | `int` | 返回极限偏移量 | | `get_limit_row_count()` | `int` | 返回限制行数 | | `get_projection_expr()` | `object` | 返回投影表达式 | | `get_sort_expr()` | `object` | 返回排序表达式 | | `get_where_expr()` | `object` | 返回 where 表达式 | | `is_doc_based()` | `bool` | 检查它是否基于文档 | | `limit(row_count, offset=0)` | `FilterableStatement` | 设置要返回的记录或文档的最大数量 | | `schema` | `Schema` | 架构对象 | | `sort(*sort_clauses)` | `FilterableStatement` | 设置排序标准 | | `target` | `object` | 数据库对象目标 | | `where(condition)` | `FilterableStatement` | 设置要过滤的搜索条件 |

注意,我们现在看到,除了简单地调用add()find()modify()remove()方法之外,您还可以做更多的事情。因为它们都返回另一个类的对象实例,所以我们可以使用一个变量来存储对象实例,然后如果您需要为操作指定附加信息,我们可以调用新对象的适当方法。

事实上,许多返回的对象都能够链接其他方法来帮助过滤或修改搜索。表 5-12 列出了一些搜索文件的常用方法。可选方法如[]所示。还显示了可以使用它们的方法。

表 5-12

Common Methods for Searching Documents

| 方法 | 描述 | 使用人 | | :-- | :-- | :-- | | `[.fields(...)]` | 该函数设置要从匹配查找操作标准的每个文档中检索的字段。 | `find(),` | | `[.group_by(...)[.having(searchCondition)]]` | 为结果集设置分组标准。having 子句为聚合函数运算中要考虑的记录设置了一个条件。 | `find(),` | | `[.sort(...)]` | 如果使用,该操作将返回按照定义的标准排序的记录。 | `find(), remove(), modify()` | | `[.limit(numberOfRows)` | 如果使用,操作最多返回`numberOfRows`张单据。 | `find(), remove(), modify()` | | `[.bind(placeHolder, value)[.bind(...)]]` | 将值绑定到该对象上使用的特定占位符 | `find(), remove(), modify()` | | `execute()` | 使用所有配置的选项执行操作 | `add(), find(), remove(), modify()` | | `[.set(...)]` | 将一个操作添加到修改处理程序中,以设置包含在选择过滤器和限制中的文档的属性 | `modify()` | | `[.unset(String attribute)]` | 从集合中的文档移除属性 | `modify()` | | `[.patch(...)]` | 基于补丁 JSON 对象对文档执行修改 | `modify()` | | `[.array_insert(...)]` | 在修改处理程序中添加一个操作,将一个值插入到包含在选择过滤器和限制中的文档的数组属性中 | `modify()` | | `[.array_append(...)]` | 在修改处理程序中添加一个操作,将一个值追加到选择过滤器和限制中包含的文档的数组属性中 | `modify()` |

例如,假设我们想要限制清单 5-5 中使用的示例代码中find()调用的字段。也就是说,我们只想要符合标准的宠物的名字和品种。我们可以使用 FindStatement 类的fields()方法来投射正确的字段。清单 5-6 显示了完成这项工作的代码。

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Create a schema.
schema = mysqlx_session.create_schema("animals")
# Create a new collection
pets = schema.create_collection("pets_json", True)
# Insert some documents
pets.add({'name': 'Violet', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
pets.add({'name': 'JonJon', 'age': 15, 'breed':'poodle', 'type':'dog'}).execute()
pets.add({'name': 'Mister', 'age': 4, 'breed':'siberian khatru', 'type':'cat'}).execute()
pets.add({'name': 'Spot', 'age': 7, 'breed':'koi', 'type':'fish'}).execute()
pets.add({'name': 'Charlie', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
# Do a find on the collection - find the fish
find = pets.find("type = 'fish'")
filterable = find.fields(['name','type'])
mydoc = filterable.execute()
print(mydoc.fetch_one())
Listing 5-6Demonstration of the FindStatement Class

注意find()方法,这里我们再次看到使用[]来指定一个列表。在这种情况下,它是操作的字段列表。这是您将在许多 CRUD 方法中看到的常见语法。

如果您将这段代码保存到一个名为listing5-6.py的文件中并执行它,您将看到如下所示的输出。

$ python ./listing5-6.py
{"type": "fish", "name": "Spot"}

还要注意,我们已经设置了一个变量来接收来自每个方法的对象实例。然而,我们可以将这些方法链接成一行代码,如下所示。只需用一个链式方法调用替换清单 5-6 中的三行代码。

# Do a find on the collection - find the fish
mydoc = pets.find("type = 'fish'").fields(['name','type']).execute()
print(mydoc.fetch_one())

尽管这些新的类看起来有很多额外的工作,但是随着你越来越习惯于使用它们,它们会变得更加直观。事实上,如果您习惯于处理关系数据,有些方法在概念上可能看起来很熟悉。

还要注意,有些方法允许您传入条件,这些条件是您可以构建以形成操作标准的表达式。我们将在后面的章节中讨论表达式。现在,我们来看看Table类。

表格类

表是关系数据的主要组织机制。在 X DevAPI 中,表是我们都熟悉的相同的关系数据结构。X DevAPI 有一个Table(您也可以将它们用于视图)类,包含 CRUD 操作(选择、插入、更新和删除)以及用于计算行数或基对象是否是视图的其他方法。表 5-13 显示了Table类的方法。

表 5-13

Table Class

| 方法 | 返回 | 描述 | | :-- | :-- | :-- | | `am_i_real()` | `bool` | 验证该对象是否存在于数据库中 | | `count()` | `int` | 计算表格中的行数。 | | `delete(condition=None)` | `DeleteStatement` | 创建一个新的`mysqlx.DeleteStatement`对象 | | `exists_in_database()` | `bool` | 验证该对象是否存在于数据库中 | | `get_connection()` | `Connection` | 返回基础连接 | | `get_name()` | `String` | 返回该数据库对象的名称 | | `get_schema()` | `Schema` | 返回该数据库对象的架构对象 | | `insert(*fields)` | `InsertStatement` | 创建一个新的`mysqlx.InsertStatement`对象 | | `is_view()` | `bool` | 已确定基础对象是否为视图 | | `name` | `str` | 该数据库对象的名称 | | `schema` | `Schema` | `Schema`物体 | | `select(*fields)` | `SelectStatement` | 创建一个新的`mysqlx.SelectStatement`对象 | | `update()` | `UpdateStatement` | 创建一个新的`mysqlx.UpdateStatement`对象 | | `who_am_i()` | `String` | 返回该数据库对象的名称 |

注意,没有创建表的方法。我们必须使用CREATE TABLE sql 命令或 SQL()方法来执行 SQL 语句。事实上,没有创建任何关系数据对象的方法。您必须使用 SQL 发出适当的 create 语句来创建对象。例如,为了在前面的例子中为我们的 pets 数据创建一个表,我们可以使用下面的CREATE TABLE语句。

CREATE TABLE `animals`.`pets_sql` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` char(20) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `breed` char(20) DEFAULT NULL,
  `type` char(12) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Tip

没有创建表或视图的创建方法。您必须将 SQL 命令传递给sql()方法来创建这些(和其他关系数据)对象。

让我们从前面的文档存储示例中获取脚本,并将其重写为使用关系数据。在本例中,我在名为animals的模式中创建了一个名为pets_sql的新表,并插入几行,然后选择其中一行。清单 5-7 显示了这个例子的代码。

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Create a schema.
schema = mysqlx_session.create_schema("animals")
# Create a new table
mysqlx_session.sql("CREATE TABLE animals.pets_sql ("
            "`id` int auto_increment primary key, "
            "`name` char(20), "
            "`age` int, "
            "`breed` char(20), "
            "`type` char(12))").execute()
pets = schema.get_table("pets_sql", True)
# Insert some documents
pets.insert().values([None, 'Violet', 6, 'dachshund', 'dog']).execute()
pets.insert().values([None, 'JonJon', 15,'poodle', 'dog']).execute()
pets.insert().values([None, 'Mister', 4,'siberian khatru', 'cat']).execute()
pets.insert().values([None, 'Spot', 7,'koi', 'fish']).execute()
pets.insert().values([None, 'Charlie', 6,'dachshund', 'dog']).execute()
# Do a select (find) on the table - find el gato
mydoc = pets.select().where("type = 'cat'").execute()
print(", ".join("{0}".format(c.get_column_name()) for c in mydoc.columns))
print(", ".join("{0}".format(r) for r in mydoc.fetch_one()))
# Drop the collection
mysqlx_session.drop_schema("animals")
# Close the connection
mysqlx_session.close()
Listing 5-7CRUD Example Using a Table

如果您将这段代码保存到一个名为listing5-7.py的文件中并执行它,您将看到如下所示的输出。

$ python ./listing5-7.py
id, name, age, breed, type
3, Mister, 4, siberian khatru, cat

虽然我将CREATE TABLE语句放在示例代码中,但这样做并不是正常的做法。事实上,大多数开发者会在应用之外单独创建表。也就是说,他们将手动执行CREATE SQL 语句(或者可能通过 DevOps 2 工具)并且不将它们包含在应用中。但是,使用临时表有一些争议,在这种情况下,您可能会将临时表包含在应用中,但是一般来说,永久数据库对象是与应用分开创建的。下一个示例显示了如何从现有模式中获取表。

注意,有一些有趣的新方法调用。首先,与集合的add()方法不同,insert()方法使用额外的链接方法。在这种情况下,我们需要values()方法来添加值。这是因为insert()方法返回了InsertStatement类的一个实例。

这可能看起来很奇怪,直到您考虑 SQL INSERT语句的语法。特别是,这些操作在 SQL 中的等效语句如下。如你所见,我们有一个VALUES条款。

INSERT INTO animals.pets VALUES (Null, 'Violet', 6, 'dachshund', 'dog');
INSERT INTO animals.pets VALUES (Null, 'JonJon', 15,'poodle', 'dog');
INSERT INTO animals.pets VALUES (Null, 'Mister', 4,'siberian khatru', 'cat');
INSERT INTO animals.pets VALUES (Null, 'Spot', 7,'koi', 'fish');
INSERT INTO animals.pets VALUES (Null, 'Charlie', 6,'dachshund', 'dog');

对于select()方法也是如此,该方法返回一个 SelectStatement 对象,我们在其中链接了where()子句。正如您可能已经猜到的,同样的事情也发生在update()delete()方法上。对于那些习惯使用 SQL 语句的人来说,这很自然。表 5-14 列出了与关系数据的 CRUD 操作相关的每个类的方法。

表 5-14

Classes for CRUD Operations for Relational Data

| 班级 | 方法 | 返回 | 描述 | | :-- | :-- | :-- | :-- | | 选择状态 | 对表进行记录检索操作的语句。 | | `bind(*args)` | `FilterableStatement` | 将值绑定到特定的占位符 | | `execute()` | `Result` | 执行语句 | | `get_binding_map()` | `dict` | 返回绑定映射字典 | | `get_bindings()` | `list` | 返回绑定列表 | | `get_grouping()` | `list` | 返回分组表达式列表 | | `get_having()` | `object` | 返回 having 表达式 | | `get_limit_offset()` | `int` | 返回极限偏移量 | | `get_limit_row_count()` | `int` | 返回限制行数 | | `get_projection_expr()` | `object` | 返回投影表达式 | | `get_sort_expr()` | `object` | 返回排序表达式 | | `get_sql()` | `String` | 返回生成的 SQL | | `get_where_expr()` | `object` | 返回 where 表达式 | | `group_by(*fields)` | `ReadStatement` | 为结果集设置分组标准 | | `having(condition)` | `ReadStatement` | 为聚合函数运算中要考虑的记录设置条件 | |   | `is_doc_based()` | `bool` | 检查它是否基于文档 | | `is_lock_exclusive()` | `bool` | 如果为`EXCLUSIVE LOCK`,则返回 true | | `is_lock_shared()` | `bool` | 如果为`SHARED LOCK`,则返回 true | | `limit(row_count, offset=0)` | `FilterableStatement` | 设置要返回的记录或文档的最大数量 | | `lock_exclusive()` | `ReadStatement` | 用`EXCLUSIVE LOCK`执行读操作;一次只能有一个锁处于活动状态 | | `lock_shared()` | `ReadStatement` | 用`SHARED LOCK`执行读操作;一次只能有一个锁处于活动状态 | | `order_by(*clauses)` | `SelectStatement` | 按标准设置顺序。 | | `schema` | `Schema` | 架构对象 | | `sort(*sort_clauses)` | `FilterableStatement` | 设置排序标准 | | `target` | `object` | 数据库对象目标 | | `where(condition)` | `FilterableStatement` | 设置要过滤的搜索条件 | | 插入状态 | 对表执行插入操作的语句 | | `execute()` | `Result` | 执行语句 | | `get_values()` | `list` | 返回值列表 | | `is_doc_based()` | `bool` | 检查它是否基于文档 | | `is_upsert()` | `bool` | 如果是向上插入,则返回 true | | `schema` | `Schema` | 架构对象 | | `target` | `object` | 数据库对象目标 | | `upsert(val=True)` |   | 将 upsert 标志设置为所提供值的布尔值;此标志的设置允许用提供的值更新匹配的行/文档 | | `values(*values)` | `InsertStatement` | 设置要插入的值 | | 更新状态 | 对表进行记录更新操作的语句 | | `bind(*args)` | `FilterableStatement` | 将值绑定到特定的占位符 | | `execute()` | `Result` | 执行语句 | | `get_binding_map()` | `dict` | 返回绑定映射字典 | | `get_bindings()` | `list` | 返回绑定列表 | | `get_grouping()` | `list` | 返回分组表达式列表 | | `get_having()` | `object` | 返回 having 表达式 | | `get_limit_offset()` | `int` | 返回极限偏移量 | | `get_limit_row_count()` | `int` | 返回限制行数 | | `get_projection_expr()` | `object` | 返回投影表达式 | | `get_sort_expr()` | `object` | 返回排序表达式 | | `get_update_ops()` | `list` | 返回更新操作的列表 | | `get_where_expr()` | `object` | 返回 where 表达式 | | `is_doc_based()` | `bool` | 检查它是否基于文档 | | `limit(row_count, offset=0)` | `FilterableStatement` | 设置要返回的记录或文档的最大数量 | |   | `schema` | `Schema` | 架构对象 | | `set(field, value)` | `UpdateStatement` | 更新表中记录的列值 | | `sort(*sort_clauses)` | `FilterableStatement` | 设置排序标准 | | `target` | `object` | 数据库对象目标 | | `where(condition)` | `FilterableStatement` | 设置要过滤的搜索条件 | | 删除声明 | 删除表的语句 | | `bind(*args)` | `FilterableStatement` | 将值绑定到特定的占位符 | | `execute()` | `Result` | 执行语句 | | `get_binding_map()` | `dict` | 返回绑定映射字典 | | `get_bindings()` | `list` | 返回绑定列表 | | `get_grouping()` | `list` | 返回分组表达式列表 | | `get_having()` | `object` | 返回 having 表达式 | | `get_limit_offset()` | `int` | 返回极限偏移量 | | `get_limit_row_count()` | `int` | 返回限制行数 | |   | `get_projection_expr()` | `object` | 返回投影表达式 | | `get_sort_expr()` | `object` | 返回排序表达式 | | `get_where_expr()` | `object` | 返回 where 表达式 | | `is_doc_based()` | `bool` | 检查它是否基于文档 | | `limit(row_count, offset=0)` | `FilterableStatement` | 设置要返回的记录或文档的最大数量 | | `schema` | `Schema` | 架构对象 | | `sort(*sort_clauses)` | `FilterableStatement` | 设置排序标准 | | `target` | `object` | 数据库对象目标 | | `where(condition)` | `FilterableStatement` | 设置要过滤的搜索条件 |

在我们继续之前,让我们回顾一下执行本章剩余部分中的例子所需的样本数据。

What About Classicsession?

如果您已经阅读了 MySQL Shell 的文档,您可能会遇到一个名为mysqlx的全局对象,它是 mysqlx 模块的镜像。您还可能遇到过名为ClassicSession的会话对象,它存在于mysql全局对象中。这个对象只能通过 MySQL Shell 获得,不要与连接器/Python 代码中名为mysql的模块混淆,它们是不一样的。事实上,X DevAPI 没有任何名为ClassicSession的对象。

因为这本书关注的是 MySQL 文档库和 X DevAPI,所以我们给出了一个ClassicSession类中方法的简单列表。下面列出了常用的方法。

  • close():关闭此会话对象上的 MySQL 服务器的内部连接。
  • start_transaction():在服务器上启动一个事务上下文。
  • commit() :提交调用 startTransaction()后执行的所有操作。
  • rollback():放弃调用 startTransaction()后执行的所有操作。
  • get_uri():检索 URI 字符串。
  • run_sql (str query, list args=[]):执行查询并返回对应的 ClassicResult 对象。
  • query(str query, list args=[]):执行查询并返回相应的 ClassicResult 对象。
  • 如果会话是打开的,则返回 True。

同样,这些方法是针对ClassicSession类的,它只能通过MySQL Shell 获得。这个简短的边栏是为了完整性和澄清类的起源。

本章中使用的示例数据

本章其余部分的示例代码使用我们在前面的示例中创建的数据。为了方便起见,我将它包含在这里。更具体地说,我包括了用于创建关系数据的 SQL 语句和一个用于创建文档存储数据的简短脚本。清单 5-8 是创建示例文档存储所需的代码。

# Create a schema.
# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Create a schema.
schema = mysqlx_session.create_schema("animals")
# Create a new collection
pets = schema.create_collection("pets_json", True)
# Insert some documents
pets.add({'name': 'Violet', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
pets.add({'name': 'JonJon', 'age': 15, 'breed':'poodle', 'type':'dog'}).execute()
pets.add({'name': 'Mister', 'age': 4, 'breed':'siberian khatru', 'type':'cat'}).execute()
pets.add({'name': 'Spot', 'age': 7, 'breed':'koi', 'type':'fish'}).execute()
pets.add({'name': 'Charlie', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
# Close the connection
mysqlx_session.close()
Listing 5-8
Sample Document Store

您可能会注意到,这类似于前面的许多清单。但是,因为从现在开始我们将使用 animals 模式,所以我们在最后省略了 drop_schema()调用。

清单 5-9 包括创建样本关系数据的 SQL 语句。

CREATE TABLE `animals`.`pets_sql` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` char(20) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `breed` char(20) DEFAULT NULL,
  `type` char(12) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO animals.pets_sql VALUES (Null, 'Violet', 6, 'dachshund', 'dog');
INSERT INTO animals.pets_sql VALUES (Null, 'JonJon', 15,'poodle', 'dog');
INSERT INTO animals.pets_sql VALUES (Null, 'Mister', 4,'siberian khatru', 'cat');
INSERT INTO animals.pets_sql VALUES (Null, 'Spot', 7,'koi', 'fish');
INSERT INTO animals.pets_sql VALUES (Null, 'Charlie', 6,'dachshund', 'dog');

CREATE VIEW `animals`.`num_pets` AS
SELECT type as Type, COUNT(*) as Num
FROM animals.pets_sql
GROUP BY type;

Listing 5-9Sample Relational Data

尽管前面的示例创建了这些对象,但在本章后面的示例实验和运行示例时,您可能希望参考本节。

现在让我们来看看使用来自find()select()和其他返回结果的方法的结果和数据集的类。

使用数据集

到目前为止,我们已经看到了一些处理结果的简单例子,虽然看起来所有的结果都是同一个类,但是有几个结果类。返回的结果类的对象实例取决于操作。表 5-15 显示了原点操作返回的对象实例类型以及返回的数据类型。

表 5-15

Result Classes (Object Instances) Returned

| 对象实例 | 起源 | 描述 | 返回的内容 | | :-- | :-- | :-- | :-- | | 结果 | 创建、更新、删除 | 由`add().execute()`、`modify().execute()`、`remove().execute()`返回 | `affected_item_count`、`auto_increment_value`、`last_document_id` | | SqlResult | 会议 | 由`session.sql()`返回 | `auto_increment_value`、`affected_row_count`,取数据-数据集 | | RowResult | 关系数据选择 | 由`select().execute()`返回 | 提取的数据—数据集 |

请注意,内容列将结果或数据集显示为返回的内容。X DevAPI 使用术语数据集来指代从读取 CRUD 操作(find()select()sql()方法)返回的数据,结果 3 来指代从创建、更新和删除 CRUD 操作返回的数据。

另外,请注意,每个操作类都返回不同的对象。类RowResultSqlResult继承自基类(BaseResult),因此有很多相同的方法。将这些与从创建、更新和删除操作返回的Result类区别开来的是Result类不支持迭代器。这是因为结果对象包含从服务器返回的与创建、更新和删除操作相关的数据,这些操作不返回任何数据,但可能返回警告和类似的元数据,并且等同于从 MySQL 中的传统 SQL INSERTUPDATEDELETE语句返回的结果。

5-16 显示了您在处理数据集和结果时会遇到的所有类及其方法。

表 5-16

Classes and Methods for Working with Data Sets and Results

| 班级 | 方法 | 返回 | 描述 | | :-- | :-- | :-- | :-- | | RowResult | 允许遍历表返回的行对象。选择操作 | | `columns` | `list` | 列的列表 | | `count` | `int` | 项目总数 | | `fetch_all()` | `list` | 获取所有项目 | | `fetch_one()` | `mysqlx.Row or mysqlx.DbDoc` | 获取一个项目 | | `get_warnings()` | `list` | 返回警告 | | `get_warnings_count()` | `int` | 返回警告的数量 | | `index_of(col_name)` | `int` | 返回列的索引 | | set_closed(标志) |   | 设置结果集提取是否完成 | | 集合 _ 生成 _ 标识(生成 _ 标识) |   | 设置生成的 ID | | 集合 _ 有 _ 更多 _ 结果(标志) |   | 如果有更多结果集,则设置 | | set_rows_affected(合计) |   | 设置受影响的行数 | | SqlResult | 表示 SQL 语句的结果 | | `columns` | `list` | 列的列表 | | `count` | `int` | 项目总数 | | `fetch_all()` | `list` | 获取所有项目 | | `fetch_one()` | `mysqlx.Row or mysqlx.DbDoc` | 获取一个项目 | | `get_autoincrement_value()` | `string` | 返回插入的最后一条记录的标识符 | | `get_warnings()` | `list` | 返回警告 | | `get_warnings_count()` | `int` | 返回警告的数量 | | `index_of(col_name)` | `int` | 返回列的索引 | | `next_result()` | `bool` | 处理下一个结果 | | `set_closed(flag)` |   | 设置结果集提取是否完成 | | `set_generated_id(generated_id)` |   | 设置生成的 ID | | `set_has_more_results(flag)` |   | 如果有更多结果集,则设置 | | BufferingResult | 为缓冲结果对象提供基本功能 | | `count` | `int` | 项目总数 | | `fetch_all()` | `list` | 获取所有项目 | | `fetch_one()` | `mysqlx.Row or mysqlx.DbDoc` | 获取一个项目 | | `get_warnings()` | `list` | 返回警告 | | `get_warnings_count()` | `int` | 返回警告的数量 | | `index_of(col_name)` | `int` | 返回列的索引 | | `set_closed(flag)` |   | 设置结果集提取是否完成 | | `set_generated_id(generated_id)` |   | 设置生成的 ID | | `set_has_more_results(flag)` |   | 如果有更多结果集,则设置 | | `set_rows_affected(total)` |   | 设置受影响的行数 | | 结果 | 允许检索对数据库执行的非查询操作的相关信息 | | `append_warning(level, code, msg)` |   | 附加警告 | | `get_affected_items_count()` | `int` | 返回上一个操作中受影响的项目数 | | `get_autoincrement_value()` | `int` | 返回上次自动生成的插入 id | | `get_document_id()` | `String` | 返回插入集合中的最后一个文档的 ID | | `get_document_ids()` | `list` | 返回生成的文档 id 列表 | | `get_warnings()` | `list` | 返回警告 |

有迭代器的三个类实现了两个方法:fetch_one()fetch_all()。它们像您想象的那样工作,返回一个数据集或一组文档的一组对象。fetch_one()方法返回数据集中的下一个数据项,如果没有更多的数据项,则NULL返回所有的数据项。更具体地说,fetch_one()一次从服务器检索一个数据项,而fetch_all()一次从服务器检索所有数据。您将使用哪一个取决于数据集的大小以及您希望如何处理数据。

Note

一旦获取了数据项,就不能再获取它。也就是说,迭代器只能向前。

在我们研究如何访问数据集中的数据之前,让我们回顾一下文档标识符和自动递增列。

Tip

从这一点开始,在示例中,您应该按照清单 5-8 中的描述加载 JSON 数据,按照清单 5-9 中的描述加载关系数据。

文档标识符

回想一下,存储在文档存储集合中的每个文档都有一个文档标识符(文档 id 或文档 id),它是一个字符串,唯一地标识集合中的文档。 4 您不需要创建自己的文档 id,它们会自动分配给您。

有两种方法可以从Result类中检索文档 id(为创建、更新和删除操作返回的内容)。特别是,您可以使用get_document_id()方法检索最后一个分配的文档 id,或者使用get_document_ids()为上述add()方法的批量添加选项返回一个文档 id 列表。清单 5-10 演示了在添加文档时检索文档 id。

Note

从这一点开始的清单假设animals集合不存在。如果您计划一个接一个地运行代码示例,您应该添加清单 5-5 中所示的drop_schema()调用。

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Drop the collection
mysqlx_session.drop_schema("animals")
# Create a schema.
schema = mysqlx_session.create_schema("animals")
# Create a new collection
pets = schema.create_collection("pets_json")
# Insert some documents and get the document ids.
res = pets.add({'name': 'Violet', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
print("New document id = '{0}'".format(res.get_document_id()))
res = pets.add({'name': 'JonJon', 'age': 15, 'breed':'poodle', 'type':'dog'}).execute()
print("New document id = '{0}'".format(res.get_document_id()))
res = pets.add({'name': 'Mister', 'age': 4, 'breed':'siberian khatru', 'type':'cat'}).execute()
print("New document id = '{0}'".format(res.get_document_id()))
res = pets.add({'name': 'Spot', 'age': 7, 'breed':'koi', 'type':'fish'}).execute()
print("New document id = '{0}'".format(res.get_document_id()))
res = pets.add({'name': 'Charlie', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
print("New document id = '{0}'".format(res.get_document_id()))# Drop the collection
mysqlx_session.drop_schema("animals")
# Close the connection
mysqlx_session.close()
Listing 5-10Getting Document Ids

如果您运行代码片段,您将看到如下所示的文档 id。

New document id = '9801A79DE0939A8311E805FB3419B12B'
New document id = '9801A79DE093B93111E805FB341CC7B5'
New document id = '9801A79DE093AD4311E805FB341CF6D9'
New document id = '9801A79DE09397AD11E805FB341D1F87'
New document id = '9801A79DE09382E911E805FB341D4568'

自动增量

如果您正在处理关系数据并且已经指定了一个自动增量字段,那么您可以使用SqlResultResult类的get_autoincrement_value()方法来检索最后一个自动增量值。此方法返回生成的自动增量值,如果您需要检索由代理主键插入的最后一行,这会很有帮助。

访问数据集中的数据

让我们考虑访问数据集中的数据。在这种情况下,我们在一个集合上发出一个find()方法,返回几个由特定结果对象表示的文档。在这种情况下,我们有一组 DbDoc 对象要获取。

有三种方法可以访问数据项中的数据;我们可以简单地将数据项作为一个字符串(自然地),我们可以通过带有数据元素键名称的属性来访问数据元素,或者我们可以使用数组索引来查找带有键的数据元素。清单 5-11 显示了一个完整的脚本,其中包含每个机制的示例。请注意,您应该已经创建了模式和集合,并使用清单 5-8 用数据填充它。

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Get the collection.
pets = mysqlx_session.get_schema("animals").get_collection("pets_json")
# Do a find on the collection - find the dog
find = pets.find("type = 'dog'").execute()
res = find.fetch_one()
while (res):
    print("Get the data item as a string: {0}".format(res))
    print("Get the data elements: {0}, {1}, {2}".format(res.name, res.age, res['breed']))
    res = find.fetch_one()
# Close the connection
mysqlx_session.close()
Listing 5-11Reading Data from a Data Set

注意我是如何用find().execute()方法检索数据集的,它返回一个我可以迭代的对象。在本例中,我获取第一个数据项,然后用 while 循环遍历这些数据项。在 while 循环中,我打印了从 fetch 返回的字符串,并演示了如何通过属性(例如,res.ageres.name)或通过使用键名的数组索引(例如,res['breed'])来检索数据元素。

如果您将这段代码保存到一个名为listing5-11.py的文件中并执行它,您将看到如下所示的输出。

$ python ./listing5-11.py
Get the data item as a string: {"breed": "dachshund", "age": 6, "_id": "9801A79DE093B2B011E805FBCB1FAC51", "type": "dog", "name": "Violet"}
Get the data elements: Violet, 6, dachshund
Get the data item as a string: {"breed": "poodle", "age": 15, "_id": "9801A79DE093B43A11E805FBCB215AFA", "type": "dog", "name": "JonJon"}
Get the data elements: JonJon, 15, poodle
Get the data item as a string: {"breed": "dachshund", "age": 6, "_id": "9801A79DE093BFD511E805FBCB21CF30", "type": "dog", "name": "Charlie"}
Get the data elements: Charlie, 6, dachshund

现在让我们看看如何从关系数据查询中获取行。

访问结果中的元数据

当使用关系数据和表或视图时select()方法。这将返回一个 SQL 数据集,该数据集表示您期望从典型的 SQL SELECT查询中获得的行。然后,我们可以通过将列名作为属性、将列索引号作为数组索引或者将列名作为数组索引来访问行中的数据。清单 5-12 展示了从行中获取数据的两种方法。

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Get the collection.
pets = mysqlx_session.get_schema("animals").get_table("pets_sql")
# Do a select (find) on the table - find the dogs
res = pets.select().where("type = 'dog'").execute()
# Working with column properties
print("Get the data using column names as properties:")
for row in res.fetch_all():
    for col in res.columns:
        print(row.get_string(col.get_column_name())),
    print("")
# Working with column indexes
print("Get the data using column index by integer:")
for row in res.fetch_all():
    for i in range(0,len(res.columns)):
        print(row[i]),
    print("")
# Working with column names
print("Get the data using column index by name:")
for row in res.fetch_all():
    for col in res.columns:
        print(row[col.get_column_name()]),
    print("")
# Close the connection
mysqlx_session.close()
Listing 5-12Data Set Example—Relational Data

如果您将这段代码保存到一个名为listing5-12.py的文件中并执行它,您将看到如下输出。

$ python ./listing5-12.py
Get the data using column names as properties:
1 Violet 6 dachshund dog
2 JonJon 15 poodle dog
5 Charlie 6 dachshund dog
Get the data using column index by integer:
1 Violet 6 dachshund dog
2 JonJon 15 poodle dog
5 Charlie 6 dachshund dog
Get the data using column index by name:
1 Violet 6 dachshund dog
2 JonJon 15 poodle dog
5 Charlie 6 dachshund dog

注意我是如何用select().execute()方法检索数据集的,它返回一个我可以迭代的对象。在这种情况下,我使用 for 循环获取项目(行)。在 for 循环中,我使用 Row 对象的get_string()方法,该方法接受列的键名,在本例中为列名。我使用了一个小技巧来迭代嵌套 for 循环中的列。我将在下一节讨论如何处理列元数据。

列元数据

关系数据的两个结果类(RowResultSqlResult)支持列的概念,正如典型的 SQL SELECT查询所期望的那样。您可以使用columns()方法(columns属性)获取列,该方法返回列对象的列表。然后,您可以使用该对象中的属性来发现有关数据集中的列的更多信息。表 5-17 显示了ColumnMetaData类及其方法。

表 5-17

ColumnMetaData Class

| 方法 | 返回 | 描述 | | :-- | :-- | :-- | | `get_schema_name()` | `str` | 检索定义该列的架构的名称 | | `get_table_name()` | `str` | 检索定义列的表名 | | `get_table_label()` | `str` | 定义列的检索表别名 | | `get_column_name()` | `str` | 检索列名 | | `get_column_label()` | `str` | 检索到的列别名 | | `get_type()` | `Type` | 检索的列类型 | | `get_length()` | `int` | 检索到的列长度 | | `get_fractional_digits()` | `int` | 如果适用,检索小数位数 | | `is_number_signed()` | `bool` | 指示数字列是否有符号 | | `get_collation_name()` | `str` | 检索排序规则名称 | | `get_character_set_name()` | `str` | 检索字符集名称 |

请注意,有几种有趣的方法,包括发现类型、字符和排序、大小等的方法。注意还有一些获取列名或标签的方法。名称是操作中的名称,而标签是操作中指定的别名或替代标签。要了解区别,请考虑下面的 SQL 语句。

SELECT pet_name as name, age as years_young FROM animals.pets_sql

当您调用get_column_name()get_column_label()方法时,您将获得以下值。清单 5-13 展示了如何使用这些方法。

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
res = mysqlx_session.sql("SELECT name as pet_name, age as years_young FROM animals.pets_sql").execute()
cols = res.columns
for col in cols:
    print "name =", col.get_column_name(), "label =", col.get_column_label()
mysqlx_session.close()
Listing 5-13Working with Column Names and Labels

如果您将这段代码保存到一个名为listing5-13.py的文件中并执行它,您将看到如下所示的输出。

$ python ./listing5-13.py

name = name label = pet_name
name = age label = years_young

现在让我们讨论使用表达式来过滤数据。

公式

表达式是 X DevAPI 中的另一个元素,它是一个简单而强大的特性。表达式与我们在 SQL 语句中用来过滤 CRUD 语句中数据的子句同义。有几种表达形式。我们可以使用字符串、布尔表达式,或者嵌入等式或不等式等实际表达式。让我们逐一检查一下。

表达式字符串

表达式字符串是那些需要在运行时计算的字符串。通常,它们使用一个或多个变量“绑定”(称为参数绑定)到字符串中的占位符。这允许您在运行时为动态过滤赋值,而不是静态值,我们将在下一节中看到。我们将在后面的章节中看到更多关于参数绑定的内容。

清单 5-14 显示了一个例子,类似于我们在前面的例子中使用的在 pets_json 集合中寻找鱼的例子。然而,在这种情况下,我们使用一个参数来包含类型,该参数可能会在运行时被读取,从而允许我们的代码动态地过滤集合查找结果。

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Get the collection.
pets = mysqlx_session.get_schema("animals").get_collection("pets_json")
# Do a find on the collection - find the fish with an expression string and parameter binding
fish_type = 'fish'
mydoc = pets.find("type = :mytype").bind('mytype', fish_type).execute()
print(mydoc.fetch_one())
# Close the connection
mysqlx_session.close()
Listing 5-14Expression Strings

如果您将这段代码保存到一个名为listing5-14.py的文件中并执行它,您将看到如下所示的输出。

$ python ./listing5-14.py
{"breed": "koi", "age": 7, "_id": "9801A79DE0938FBD11E805FBCB21AB35", "type": "fish", "name": "Spot"}

布尔表达式字符串

这种形式的表达式使用一个字符串,就像我们在 SQL 语句的WHERE子句中使用的一样。也就是说,我们使用自然语言来表达过滤器,其中比较是真还是假。清单 5-15 是前面例子中的布尔表达式字符串。第一行是一个关系数据示例,在这个示例中,我们希望结果只包括那些类型列等于“dog”的项目第二个是一个文档存储示例,我们希望结果只包括那些 type 元素的值为“fish”的项目

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Get the collection.
pets_json = mysqlx_session.get_schema("animals").get_collection("pets_json")
# Get the table.
pets_sql = mysqlx_session.get_schema("animals").get_table("pets_sql")
res = pets_sql.select().where("type = 'dog'").limit(1).execute()
print("SQL result ="),
for row in res.fetch_all():
    for i in range(0,len(res.columns)):
       print("{0}".format(row[i])),
print("")
mydoc = pets_json.find("type = 'fish'").execute()
print("JSON result = {0}".format(mydoc.fetch_one()))
# Close the connection
mysqlx_session.close()
Listing 5-15Boolean Expression Strings

如果您将这段代码保存到一个名为listing5-15.py的文件中并执行它,您将看到如下所示的输出。

$ python ./listing5-15.py
SQL result = 1 Violet 6 dachshund dog
JSON result = {"breed": "koi", "age": 7, "_id": "9801A79DE0938FBD11E805FBCB21AB35", "type": "fish", "name": "Spot"}

Tip

你可以在 https://dev.mysql.com/doc/x-devapi-userguide/en/ 的 X DevAPI 用户指南中找到一套完整的表达式和方法链接的扩展巴克斯-纳尔形式 5 图。

警告和错误

我们需要花些时间了解的另一个领域是服务器发送的警告报告和 X DevAPI 的错误处理。幸运的是,X DevAPI 有获取警告的工具。然而,错误将需要更多的工作。让我们先来看看警告。

来自服务器的警告

处理警告很容易,因为 X DevAPI 内置了一种机制来帮助您获取警告信息。Warning类有三个属性,如下所示。如果出现警告,我们可以使用这些来获取警告。

  • 级别—警告的级别
  • 代码—警告代码
  • 消息—警告消息

Note

默认情况下,所有警告都从服务器发送到客户端。但是,您可以取消警告以节省带宽。使用 Session 类中的set_fetch_warnings()来控制警告是在服务器上被丢弃还是被发送到客户端。使用get_fetch_warnings()方法获取活动设置。

事实上,我们可以使用 get_warnings()方法来检查是否有需要处理的警告。但是,X DevAPI 会在每次发生警告时向客户端发送警告,因此如果您想要检查警告,必须在每次执行后进行。清单 5-16 展示了一种编写代码来处理错误的方法。这绝不是唯一的方法,但确实演示了Warning类方法。

Note

这个例子需要建立animals数据库。有关如何设置数据库,请参阅前面的“本章中使用的示例数据”一节。

#This method checks the result for warnings and prints them
# if any exist.
#
# result[in]     result object
def process_warnings(result):
    if result.get_warnings_count():
        for warning in result.get_warnings():
            print("WARNING: Type {0} (Code {1}): {2}".format(*warning))
    else:
        print "No warnings were returned."

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Get the animals schema.
schema = mysqlx_session.get_schema("animals")
# Try to create the table using a SQL string. It should throw a warning.
res = mysqlx_session.sql("CREATE TABLE IF NOT EXISTS animals.pets_sql ("
                         "`id` int auto_increment primary key, "
                         "`name` char(20), "
                         "`age` int, "
                         "`breed` char(20), "
                         "`type` char(12))").execute()
process_warnings(res)
# Close the connection
mysqlx_session.close()

Listing 5-16Processing Warnings

注意,我写了一个名为process_warnings()的方法,它接受一个结果对象,并通过调用get_warnings_count()方法来检查是否有错误。如果这个方法返回一个正整数,这意味着有警告,如果是这样,我从警告对象中获取类型、代码和消息,并打印数据。如果没有警告,我会打印一条消息,声明没有错误(但是您可能不想知道)。

如果您将这段代码保存到一个名为listing5-16.py的文件中并执行它,您将会看到下面的结果。请注意,如果您删除了animals集合,您可能需要再次运行它。

$ python ./listing5-16.py
WARNING: Type 1 (Code 1050): Table 'pets_sql' already exists

现在让我们看看如何处理来自 X DevAPI 的错误。

来自 X DevAPI 的错误

正如我提到的,X DevAPI 中没有实现专门用于处理错误的东西,但是我们可以使用一些工具。在这种情况下,我们将从数据库连接器中获得一些帮助。也就是说,数据库连接器实现了特定于语言的错误处理(异常处理)机制,使得处理来自 X DevAPI 方法的错误变得很自然。换句话说,它们实现了异常处理。 6

以 Python 为例,Python 语言实现了一个 try...异常块(有时称为 try 或异常块)。这种构造允许以raise()方法的形式“引发”异常的代码让调用代码(具有最近 try 块的代码)捕获异常。语法如下。

try:
    # some operation 1
    # some operation 2
    # some operation 3
    # some operation 4
    # some operation 5
except:
    # catch the exception
finally:
    # do this after the success or capture

这允许我们“尝试”一个(或多个)操作,如果它们因引发异常而失败,代码将跳过 try 段中的任何剩余操作,并跳到 except 段。

让我们看看当您不使用异常处理并且代码失败时会发生什么。也就是说,X DevAPI 抛出一个异常。清单 5-17 显示了一个带有错误的简单脚本。你能认出他们吗?提示:检查密码,当您试图创建一个已经存在的表时会发生什么?

# Import the MySQL X module
import mysqlx
import getpass
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:wrongpassworddude!@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Get the animals schema.
schema = mysqlx_session.get_schema("animals")
# Try to create the table using a SQL string. It should throw an
# error that it already exists.
res = mysqlx_session.sql("CREATE TABLE animals.pets_sql ("
                         "`id` int auto_increment primary key, "
                         "`name` char(20), "
                         "`age` int, "
                         "`breed` char(20), "
                         "`type` char(12))").execute()
# Close the connection
mysqlx_session.close()
Listing 5-17
Not Handling Errors

如果您将这段代码保存到一个名为listing5-17.py的文件中并执行它,您将会看到下面的结果(为了简洁起见,删除了无关的数据)。

$ python ./listing5-17.py
Traceback (most recent call last):
  File "./listing5-17.py", line 6, in <module>
    mysqlx_session = mysqlx.get_session("root:wrongpassworddude!@localhost:33060")
...
  File "/Library/Python/2.7/site-packages/mysqlx/protocol.py", line 129, in read_auth_ok
    raise InterfaceError(msg.msg)
mysqlx.errors.InterfaceError: Invalid user or password

哦,亲爱的,太可怕了!我们在这里得到的是一个回溯转储,这就是 Python 传达未处理异常的方式。我们应该注意的关键信息是,第一行显示了脚本中的代码行,该代码行启动了一系列方法调用,导致最后两行所示的异常抛出。这里我们看到,get_session()调用导致连接器中的 X 协议代码抛出一个mysqlx.errors.InterfaceError。这表明如果不使用异常处理,事情会变得多么糟糕。但是我们可以做得更好。

让我们看一个异常处理的例子。清单 5-18 显示了一个带有故意错误的脚本,这些错误将导致 X DevAPI 抛出异常。在这种情况下,将失败的是 CREATE TABLE SQL 语句。更具体地说,它将失败,因为该表已经存在。

如果您运行这个脚本并且没有失败,请确保该表已经存在。我们利用了表已经存在的事实,所以当执行 CREATE 时,我们将得到一个异常。正如您将看到的,异常也不容易理解。

# Import the MySQL X module
import mysqlx
try:
    # Get a session with a URI
    mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
    # Check the connection
    if not mysqlx_session.is_open():
        print("Connection failed!")
        exit(1)
    # Get the animals schema.
    schema = mysqlx_session.get_schema("animals")
    # Try to create the table using a SQL string. It should throw an error
    # that it already exists.
    res = mysqlx_session.sql("CREATE TABLE animals.pets_sql ("
                             "`id` int auto_increment primary key, "
                             "`name` char(20), "
                             "`age` int, "
                             "`breed` char(20), "
                             "`type` char(12))").execute()
except Exception as ex:
    print("ERROR: {0}:{1}".format(*ex))
# Close the connection
mysqlx_session.close()
Listing 5-18Handling Errors—Global Exception

当我们运行这段代码时,我们会得到一个更好的结果。如果您将这段代码保存到一个名为listing5-18.py的文件中并执行它,您将会看到下面的结果。请注意,您可以从这个改进的版本中获得预期的输出。它更容易阅读,信息量也更大。

$ python ./listing5-18.py
ERROR: -1: Table 'pets_sql' already exists

尽管对于可以在异常块中放置多少内容没有可靠的规则,但是应该保持异常块较小——比方说隔离到单个概念或进程——以避免调试代码时很难知道是几十个方法调用中的哪一个触发了异常的情况。如果您使用 Python 这样的语言来抛出调用堆栈跟踪,这可能并不困难,但是如果您的语言没有调用堆栈跟踪,或者重新运行代码来创建调用堆栈跟踪是不可能的,那么保持异常块较小可以帮助您隔离出现问题的代码。

清单 5-19 展示了一个在每个 X DevAPI 语句周围包含 try 块的例子。它还演示了如何捕获引发的特定异常。也就是说,except:语法允许您指定特定的异常。在本例中,我捕获了 X DevAPI 抛出的异常。

# Import the MySQL X module
import mysqlx
import getpass
# Get a session with a URI
mysqlx_session = None
try:
    mysqlx_session = mysqlx.get_session("root:wrongpassworddude!@localhost:33060")
except mysqlx.errors.InterfaceError as ex:
    print("ERROR: {0} : {1}".format(*ex))
    passwd = getpass.getpass("Wrong password, try again: ")
finally:
    mysqlx_session = mysqlx.get_session("root:{0}@localhost:33060".format(passwd))
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Demostrate error from get_schema()
schema = mysqlx_session.get_schema("animal")
if (not schema.exists_in_database()):
    print("Schema 'animal' doesn't exist.")
# Get the animals schema.
schema = mysqlx_session.get_schema("animals")
try:
    # Try to create the table using a SQL string. It should throw an
    # error that it already exists.
    res = mysqlx_session.sql("CREATE TABLE animals.pets_sql ("
                      "`id` int auto_increment primary key, "
                      "`name` char(20), "
                      "`age` int, "
                      "`breed` char(20), "
                      "`type` char(12))").execute()
except mysqlx.errors.OperationalError as ex:
    print("ERROR: {0} : {1}".format(*ex))
# Close the connection
if mysqlx_session:
    mysqlx_session.close()
Listing 5-19Handling Errors—Local Exceptions

如果您将这段代码保存到一个名为listing5-19.py的文件中并执行它,您将会看到下面的结果。出现提示时,请务必输入正确的密码。这是因为只有一个正确密码的测试。您的挑战是通过允许多次重试来确定改进代码的方法。提示:使用循环。

$ python ./listing5-19.py
ERROR: -1 : Invalid user or password
Wrong password, try again:
Schema 'animal' doesn't exist.
ERROR: -1 : Table 'pets_sql' already exists

该示例还展示了一种有趣的处理异常的方法——重试语句。通常,您会将想要重试的语句放在一个具有时间或尝试限制的循环或类似结构中。这里,我只是在提示用户输入密码时重试会话方法。

Tip

为了获得最佳结果,请使用较短的异常块封装您的代码,以便您可以轻松地隔离导致错误的代码。

现在让我们看看使用 X DevAPI 时可用的附加特性。

附加功能

既然我们已经看到了 X DevAPI 中所有可用的主要类和方法,现在让我们研究一下 X DevAPI 公开的一些特性;特别是参数绑定、链接方法、预准备语句和异步执行的例子。

Note

这个例子使用了world_x数据库,可以从 https://dev.mysql.com/doc/index-other.html 下载。只需下载压缩文件,解压缩,然后用\source命令或使用mysql客户端和source命令将其包含在 MySQL Shell 中。关于如何安装world_x数据库的演练,请参见第 4 章中的“安装示例数据库”一节。

参数绑定

参数绑定允许我们在运行时将值应用于表达式。参数绑定通常用于过滤器,并在执行操作之前完成(因此您会经常看到.bind().execute())。因此,参数绑定的好处是它允许您从表达式中分离值。这是通过所有支持参数绑定的类的bind()方法来完成的。

可以使用两种方法之一“绑定”参数:可以使用匿名参数,也可以使用命名参数。但是,对于何时可以使用每种都有限制。特别是,匿名参数只能用在 SQL 字符串(表达式)中,而命名参数用在 CRUD 操作中。让我们来看一个例子。

清单 5-20 展示了一个使用匿名参数的例子。匿名参数用问号表示。请注意我们在下面的 SQL 语句中是如何做到这一点的。

$ mysqlsh root@localhost:33060 --sql
Creating a session to 'root@localhost:33060'
Enter password:
Your MySQL connection id is 74 (X protocol)
Server version: 8.0.11 MySQL Community Server (GPL)
No default schema selected; type \use <schema> to set one.
MySQL Shell 8.0.11

Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type '\help' or '\?' for help; '\quit' to exit.

 MySQL  localhost:33060+ ssl  SQL > PREPARE STMT FROM 'SELECT * FROM world_x.city WHERE name like ? LIMIT ?';
Query OK, 0 rows affected (0.00 sec)
 MySQL  localhost:33060+ ssl  SQL > SET @name_wild = 'Ar%';
Query OK, 0 rows affected (0.00 sec)
 MySQL  localhost:33060+ ssl  SQL > SET @numrows = 1;
Query OK, 0 rows affected (0.00 sec)
 MySQL  localhost:33060+ ssl  SQL > EXECUTE STMT USING @name_wild, @numrows;
+----+--------+-------------+------------+------------------------+
| ID | Name   | CountryCode | District   | Info                   |
+----+--------+-------------+------------+------------------------+
| 18 | Arnhem | NLD         | Gelderland | {"Population": 138020} |
+----+--------+-------------+------------+------------------------+
1 row in set (0.00 sec)
 MySQL  localhost:33060+ ssl  SQL > \q
Bye!

Listing 5-20Parameter Binding Example (MySQL Shell)

我们可以从这个例子中得到一些东西。首先,匿名参数只在 SQL 语句中使用。第二,匿名参数按照它们在 SQL 语句中出现的顺序完成(提供值)。第三,也是最后一点,匿名参数可以用于预处理语句。 7

清单 5-21 展示了几个使用命名参数的例子。需要注意的关键点是参数是如何以冒号开头命名的。当调用bind()方法时,我们提供命名参数(没有冒号)及其值。

# Import the MySQL X module
import mysqlx
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
schema = mysqlx_session.get_schema("world_x")

# Collection.find() function with hardcoded values
myColl = schema.get_collection('countryinfo')
myRes1 = myColl.find("GNP >= 828").execute()
print(myRes1.fetch_one())

# Using the .bind() function to bind parameters
myRes2 = myColl.find('Name = :param1 and GNP = :param2').bind('param1','Aruba').bind('param2', '828').execute()
print(myRes2.fetch_one())

# Using named parameters
myColl.modify('Name = :param').set('GNP', '829').bind('param', 'Aruba').execute()

# Binding works for all CRUD statements except add()
myRes3 = myColl.find('Name LIKE :param').bind('param', 'Ar%').execute()
print(myRes3.fetch_one())

# Ok, now put the candle back...
myColl.modify('Name = :param').set('GNP', '828').bind('param', 'Aruba').execute()

# Close the connection
mysqlx_session.close()

Listing 5-21Parameter Binding Example

请注意我们如何传递多个要绑定的参数。在这种情况下,只要有参数要绑定,我们就调用bind()多次。由于下一节中描述的方法链接特性,这是可能的。也就是说,bind()方法返回它自身的一个实例,因此当我们调用下一个bind()方法时,它会重复调用,但是使用不同的参数和值。

Tip

命名参数不能以数字开头。例如,:1test不是有效的命名参数名。

如果您将这段代码保存到一个名为listing5-21.py的文件中并执行它,您将会看到下面的结果。

$ python ./listing5-21.py
{"GNP": "828", "Name": "Aruba", "government": {"GovernmentForm": "Nonmetropolitan Territory of The Netherlands", "HeadOfState": "Beatrix"}, "demographics": {"LifeExpectancy": 78.4000015258789, "Population": 103000}, "_id": "ABW", "IndepYear": null, "geography": {"SurfaceArea": 193, "Region": "Caribbean", "Continent": "North America"}}
{"GNP": "828", "Name": "Aruba", "government": {"GovernmentForm": "Nonmetropolitan Territory of The Netherlands", "HeadOfState": "Beatrix"}, "demographics": {"LifeExpectancy": 78.4000015258789, "Population": 103000}, "_id": "ABW", "IndepYear": null, "geography": {"SurfaceArea": 193, "Region": "Caribbean", "Continent": "North America"}}
{"GNP": "829", "Name": "Aruba", "government": {"GovernmentForm": "Nonmetropolitan Territory of The Netherlands", "HeadOfState": "Beatrix"}, "demographics": {"LifeExpectancy": 78.4000015258789, "Population": 103000}, "_id": "ABW", "IndepYear": null, "geography": {"SurfaceArea": 193, "Region": "Caribbean", "Continent": "North America"}}

现在让我们看看方法链接和它是如何工作的。

方法链接

方法链接(也称为命名参数习惯用法)是面向对象编程中的一种设计约束,其中每个方法(支持链接)返回一个对象实例。因此,只需将调用添加到第一个方法的末尾,就可以访问(调用)返回对象上的任何方法。

例如,如果一个类 X 有一个方法 a(),它用方法 b()返回对象 Y,我们可以如下将调用链接在一起。

x = something.get_x()
res = x.a().b()

在这种情况下,x.a()方法首先执行,然后当它返回一个 Y 对象实例时,它调用 Y 对象实例上的b()方法。

X DevAPI 中方法链接的亮点在于关系数据方法的实现。特别是那些支持 SQL CRUD 命令的类和方法。清单 5-22 是一个复杂的表格SELECT操作的例子。

# Import the MySQL X module
import mysqlx
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Get the table
city = mysqlx_session.get_schema("world_x").get_table("city")
# Perform a complex select

res = city.select(['Name', 'District']).where("Name LIKE :param1").order_by(["District", "Name"]).bind('param1', 'X%').limit(1).execute()

# Show results
print("SQL result ="),
for row in res.fetch_all():
    for i in range(0,len(res.columns)):
       print("{0}".format(row[i])),
print("")
# Close the connection
mysqlx_session.close()
Listing 5-22Method Chaining

如果您将这段代码保存到一个名为listing5-22.py的文件中并执行它,您将会看到下面的结果。

$ python ./listing5-22.py
SQL result = Xuangzhou Anhui

这里我们看到两行代码和几个使用中的对象实例以及一系列方法。在第二行代码中(忽略注释),我们使用一个 mysqlx 会话对象来获取一个模式对象,然后通过调用Schema类方法get_table()来链接它,该方法返回一个表对象实例。

在第三行代码中,我们使用 table 对象实例调用select()方法,它返回 SelectStatement 对象实例,我们通过调用它的where()方法链接它,它返回相同的 SelectStatement 对象,我们调用它的order_by()方法,它返回相同的 SelectStatement 对象,然后我们将参数与返回相同 SelectStatement 对象的bind()方法绑定,最后我们调用返回 SqlResult 对象的execute()方法。哇哦!

如果您认为方法链隐藏了许多关于对象的细节,避免了在变量中存储对象实例的重复代码,那么您就对了!这正是我们正在做的。

正如你所看到的,方法链接允许我们在代码中更清楚地表达概念,旧的类风格和方法不返回对象实例(甚至旧的风格简单地返回 0 或 1 来指示成功或失败 8 )。掌握 X DevAPI 意味着掌握如何将方法链接在一起,以简化并使代码更容易阅读和理解。酷吧。

有关方法链接概念的更多信息,请参见 https://en.wikipedia.org/wiki/Method_chaining

CRUD 准备语句

准备好的 CRUD 语句是我们在调用execute()方法之前想要对一个对象执行一些操作的情况。这样,我们就“准备”了对象实例(语句)来执行。也就是说,不是通过链接bind()execute()或简单地execute()来直接绑定和执行 CRUD 操作,我们可以操纵 CRUD 操作,将过滤器和其他标准之类的东西存储在一个变量中,供以后执行。

这样做的好处是,我们可以将几个参数或变量集绑定到表达式。这给了我们更好的性能,因为我们可以提前“准备”变量,稍后再执行它们。这可以让我们在执行许多类似操作时获得更好的性能。

您可能认为 CRUD 准备语句在概念上类似于 SQL 准备语句。这是真的,但与 SQL 预准备语句不同,CRUD 预准备语句是在类方法中实现的,因此可以轻松地集成到我们的代码中。

让我们看一个例子。清单 5-23 展示了一个准备 CRUD 语句的例子。在本例中,我们使用一个参数准备一个 find()语句,并将结果(FindStatement 对象)保存到一个变量中。当我们想要执行这个语句时,我们使用变量调用 bind()方法提供一个值,然后使用execute()方法执行 FindStatement。

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysql_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysql_session.is_open():
    print("Connection failed!")
    exit(1)
# Create a schema.
schema = mysql_session.get_schema("animals")
# Create a new collection
pets = schema.get_collection("pets_json")
# Prepare a CRUD statement.
find_pet = pets.find("name = :param")
# Now execute the CRUD statement different ways.
mydoc = find_pet.bind('param', 'JonJon').execute()
print(mydoc.fetch_one())
mydoc = find_pet.bind('param', 'Charlie').execute()
print(mydoc.fetch_one())
mydoc = find_pet.bind('param', 'Spot').execute()
print(mydoc.fetch_one())
# Close the connection
mysql_session.close()
Listing 5-23
CRUD Prepared Statements

注意三个find_pet.bind(方法调用。这里我们执行 find 语句三次;一次用于我们想要找到的每个宠物的名字。显然,这只是一个小例子,但是展示了使用 CRUD 准备语句的强大功能。

如果您将这段代码保存到一个名为listing5-23.py的文件中并执行它,您将会看到下面的结果。

$ python ./listing5-23.py
{"breed": "poodle", "age": 15, "_id": "9801A79DE093B43A11E805FBCB215AFA", "type": "dog", "name": "JonJon"}
{"breed": "dachshund", "age": "6", "_id": "9801A79DE093BFD511E805FBCB21CF30", "type": "dog", "name": "Charlie"}
{"breed": "koi", "age": 7, "_id": "9801A79DE0938FBD11E805FBCB21AB35", "type": "fish", "name": "Spot"}

异步执行

对于那些支持异步编程的客户端,比如 C/J、C/Node.js 和 C/Net,X DevAPI 允许使用异步机制,比如回调、async()调用等等。这些机制使得允许一个操作与其他操作并行运行成为可能。让我们看一个来自 Java 的例子。

Note

目前,C/Py 和 C/C++都不允许异步执行,但将来可能会。检查这些连接器的新版本是否有更新。

Table employees = db.getTable("employee");
// execute the query asynchronously, obtain a future
CompletableFuture<RowResult> rowsFuture = employees.select("name", "age").where("name like :name").orderBy("name").bind("name", "m%").executeAsync();

这里我们看到了executeAsync()方法,这是 Java 连接器允许异步执行execute()方法的方式。也就是说,select()异步运行,当它返回(完成)时,它触发由CompletableFuture模板/类(或者 Java 中的泛型类 9 )定义的未来。

Note

根据您使用的语言,X DevAPI 可能会实现一个类似于executeAsync()的函数,作为对execute()的补充或替代。查看所选连接器的 X DevAPI 文档,了解正确的方法名称和用法。

有关异步执行的更多信息,请参见 X DevAPI 指南中与您选择的语言相匹配的连接器。

更多信息

如果您想了解关于数据库连接器和 MySQL Shell 中 X DevAPI 实现的更多详细信息,请访问以下链接,获取所有类、方法、属性和帮助函数的描述和列表。这些网站以开发者为中心,可能不包括详细的解释或例子。

Note

这些组件的某些文档可能与本章开头列出的版本号不匹配。如果文档是针对较新的版本,那么您应该安装最新的版本。然而,在撰写本文时,MySQL Shell 用户指南正在更新中。定期检查以确保您使用的是最新的可用文档。

摘要

X DevAPI 是 NoSQL 与 MySQL 服务器接口简化程度的奇迹。X DevAPI 引入了一种新的、现代的、易于学习的数据处理方式。

X DevAPI 是构建文档存储应用的主要机制。虽然 X DevAPI 不是一个独立的库——您必须使用一个通过 X 协议公开 X DevAPI 的客户端——但 X DevAPI 仍然是改变您与 MySQL 交互方式的一个主要努力。现在,我们第一次同时拥有了 MySQL 的 SQL 和 NoSQL 接口。

在这一章中,我们探索了 X DevAPI,研究了用于连接 MySQL 服务器、创建集合、处理结果甚至如何处理关系数据的主要类和方法。最后,我们还看到了一组快速参考表,您可以将其用作开发文档存储应用的主要参考。

在第 6 章中,我们深入探讨了 X 插件,这将让你更好地理解 X 插件做什么,如何配置它,以及如何最好地将它作为正常数据库管理任务的一部分来管理。在这一章之后,我们将看到 X 协议的细节,以及一个文档存储应用的工作示例。

Footnotes 1

当前版本的 Connector/Python 不支持该语法。

  2

https://en.wikipedia.org/wiki/DevOps

  3

遗憾的是,在文档和博客中,这有时被称为结果集,这可能会引起混淆,因为结果集是关系数据中的一个常用术语,与数据集意思相同。使用 X DevAPI 时,最好将结果集和数据集视为同义词。

  4

原则上相当于主键(如自动递增列)。

  5

扩展的 Backus-Naar 形式是一种用于记录上下文无关语法的图表样式。 https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form

  6

https://en.wikipedia.org/wiki/Exception_handling

  7

有关匿名参数的更多信息,请参见 MySQL 在线参考手册中的选择语法一节。

  8

我不喜欢服务器中的旧代码的一点是,大多数方法返回 0 或 1,通过指针传递对象和变量来返回数据。对于快速编写应用来说,方法链更加优雅和有用。

  9

https://docs.oracle.com/javase/tutorial/java/generics/types.html